├── .github
├── ISSUE_TEMPLATE.md
├── ISSUE_TEMPLATE
│ ├── 1-bug-report.md
│ └── 2-feature-request.md
├── PULL_REQUEST_TEMPLATE.md
├── hooks
│ ├── commit-msg
│ └── pre-commit
├── scripts
│ ├── replace_template.sh
│ └── setup_hooks.sh
└── workflows
│ ├── codeql.yml
│ ├── gh-pages.yml
│ ├── publish.yml
│ ├── quality-check.yml
│ └── scripts
│ ├── quality.sh
│ └── replace_template.sh
├── .gitignore
├── .gitmodules
├── .lintstagedrc
├── .markdownlint.json
├── .prettierrc.json
├── .puppeteerrc.cjs
├── .releaserc.yaml
├── .stylelintignore
├── .stylelintrc.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── USAGE.md
├── angular.json
├── bun.lock
├── commitlint.config.js
├── cypress.config.ts
├── cypress
├── fixture
│ └── example.json
└── support
│ ├── component-index.html
│ └── component.ts
├── eslint.config.js
├── package.json
├── postcss.config.js
├── projects
└── ngx-mask-lib
│ ├── ng-package.json
│ ├── ng-package.prod.json
│ ├── package.json
│ ├── src
│ ├── index.ts
│ ├── lib
│ │ ├── custom-keyboard-event.ts
│ │ ├── ngx-mask-applier.service.ts
│ │ ├── ngx-mask-expression.enum.ts
│ │ ├── ngx-mask.config.ts
│ │ ├── ngx-mask.directive.ts
│ │ ├── ngx-mask.pipe.ts
│ │ ├── ngx-mask.providers.ts
│ │ └── ngx-mask.service.ts
│ └── test
│ │ ├── add-prefix.spec.ts
│ │ ├── add-suffix.spec.ts
│ │ ├── allow-few-mask.cy-spec.ts
│ │ ├── allow-negative-numbers.spec.ts
│ │ ├── basic-logic.spec.ts
│ │ ├── clear-if-not-match-the-mask.spec.ts
│ │ ├── complete-mask.spec.ts
│ │ ├── copy-paste.spec.ts
│ │ ├── cursor.cy-spec.ts
│ │ ├── custom-date.spec.ts
│ │ ├── custom-patterns.spec.ts
│ │ ├── custom-symbol-regexp.spec.ts
│ │ ├── default-config.spec.ts
│ │ ├── delete.cy-spec.ts
│ │ ├── delete.spec.ts
│ │ ├── drop-special-charaters.spec.ts
│ │ ├── dynamic.spec.ts
│ │ ├── emit-events.cy-spec.ts
│ │ ├── export-as.spec.ts
│ │ ├── forms.spec.ts
│ │ ├── inputTransformFn.spec.ts
│ │ ├── keep-character-position.cy-spec.ts
│ │ ├── mask.pipe.spec.ts
│ │ ├── percent.spec.ts
│ │ ├── place-holder-character.spec.ts
│ │ ├── provide-ngx-mask.spec.ts
│ │ ├── repeat-mask.spec.ts
│ │ ├── secure-mask.spec.ts
│ │ ├── separator-non-en-locale.spec.ts
│ │ ├── separator.cy-spec.ts
│ │ ├── separator.spec.ts
│ │ ├── show-mask-typed.cy-spec.ts
│ │ ├── show-mask-typed.spec.ts
│ │ ├── test-sufix.spec.ts
│ │ ├── time-mask.spec.ts
│ │ ├── trigger-on-mask-change.cy-spec.ts
│ │ ├── trigger-on-mask-change.spec.ts
│ │ ├── type-number.spec.ts
│ │ ├── utils
│ │ ├── cypress-test-component.component.ts
│ │ ├── cypress-test-trigger-on-mask-change.component.ts
│ │ ├── test-component.component.ts
│ │ └── test-functions.component.ts
│ │ └── validation.spec.ts
│ ├── tsconfig.cypress.json
│ ├── tsconfig.lib.json
│ ├── tsconfig.lib.prod.json
│ └── tsconfig.spec.json
├── src
├── app
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── options
│ │ ├── options.component.html
│ │ ├── options.component.scss
│ │ └── options.component.ts
│ └── shared
│ │ └── card-content
│ │ ├── card-content.component.html
│ │ ├── card-content.component.scss
│ │ └── card-content.component.ts
├── assets
│ ├── .gitkeep
│ ├── content
│ │ ├── common-cases.ts
│ │ ├── lists.ts
│ │ ├── optional.ts
│ │ ├── other.ts
│ │ ├── parser-and-formatter.ts
│ │ └── separators.ts
│ └── images
│ │ ├── content
│ │ └── scroll-shadow.svg
│ │ ├── open-source
│ │ ├── accordion
│ │ │ ├── common-cases-active.svg
│ │ │ ├── common-cases.svg
│ │ │ ├── options-active.svg
│ │ │ ├── options.svg
│ │ │ ├── other-active.svg
│ │ │ ├── other.svg
│ │ │ ├── parser-and-formatter-active.svg
│ │ │ ├── parser-and-formatter.svg
│ │ │ ├── separator-active.svg
│ │ │ ├── separator.svg
│ │ │ ├── white-chevron-down.svg
│ │ │ ├── yellow-chevron-down.svg
│ │ │ └── yellow-chevron-up.svg
│ │ ├── header
│ │ │ ├── burger.svg
│ │ │ ├── close.svg
│ │ │ ├── logo-white.svg
│ │ │ └── logo.svg
│ │ ├── options
│ │ │ ├── hand-box.svg
│ │ │ └── input-vector.svg
│ │ └── visit-btn
│ │ │ └── button-chevron.svg
│ │ └── shared
│ │ └── github.svg
├── favicon.ico
├── index.html
├── main.ts
└── styles.scss
├── tailwind.config.ts
├── tsconfig.app.json
├── tsconfig.eslint.json
├── tsconfig.json
└── tsconfig.spec.json
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | 🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
2 |
3 | Please help us process issues more efficiently by filing an
4 | issue using one of the following templates:
5 |
6 | https://github.com/angular/angular/issues/new/choose
7 |
8 | Thank you!
9 |
10 | 🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/1-bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41E Bug report"
3 | about: Report a bug in the ngx-mask package
4 | ---
5 |
6 |
14 |
15 | # 🐞 bug report
16 |
17 | ### Is this a regression?
18 |
19 |
20 | Yes, the previous version in which this bug was not present was: ....
21 |
22 | ### Description
23 |
24 | A clear and concise description of the problem...
25 |
26 | ## 🔬 Minimal Reproduction
27 |
28 |
31 | https://stackblitz.com/...
32 |
33 |
42 |
43 | ## 🔥 Exception or Error
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | ## 🌍 Your Environment
52 |
53 | **Angular Version:**
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | **Anything else relevant?**
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/2-feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F680 Feature request"
3 | about: Suggest a feature for ngx-mask package
4 | ---
5 |
6 |
14 |
15 | # 🚀 feature request
16 |
17 | ### Description
18 |
19 | A clear and concise description of the problem or missing capability...
20 |
21 | ### Describe the solution you'd like
22 |
23 | If you have a solution in mind, please describe it.
24 |
25 | ### Describe alternatives you've considered
26 |
27 | Have you considered any alternative solutions or workarounds?
28 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## PR Checklist
2 |
3 | Please check if your PR fulfills the following requirements:
4 |
5 | - [ ] The commit message follows our guidelines: https://github.com/JsDaddy/ngx-mask/blob/develop/CONTRIBUTING.md#commit
6 | - [ ] Tests for the changes have been added (for bug fixes / features)
7 | - [ ] Docs have been added / updated (for bug fixes / features)
8 |
9 | ## PR Type
10 |
11 | What kind of change does this PR introduce?
12 |
13 |
14 |
15 | - [ ] Bugfix
16 | - [ ] Feature
17 | - [ ] Code style update (formatting, local variables)
18 | - [ ] Refactoring (no functional changes, no api changes)
19 | - [ ] Build related changes
20 | - [ ] CI related changes
21 | - [ ] Documentation content changes
22 | - [ ] Other... Please describe:
23 |
24 | ## What is the current behavior?
25 |
26 |
27 |
28 | Issue Number: N/A
29 |
30 | ## What is the new behavior?
31 |
32 | ## Does this PR introduce a breaking change?
33 |
34 | - [ ] Yes
35 | - [ ] No
36 |
37 |
38 |
39 | ## Other information
40 |
--------------------------------------------------------------------------------
/.github/hooks/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | commit_msg=$(cat .git/COMMIT_EDITMSG)
6 | echo "$commit_msg" | bun commitlint
7 |
--------------------------------------------------------------------------------
/.github/hooks/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | bun lint-staged
6 |
7 | # bun run snyk:test
8 |
9 | output=$(bun run type-coverage)
10 | if echo "$output" | grep -q "lower than "; then
11 | echo "$output"
12 | exit 1 # Terminate the hook script with a non-zero exit code
13 | else
14 | echo "Type coverage is good! 🎉"
15 | fi
16 |
17 | bun run test
18 |
19 | bun run cypress:bash
20 |
21 | bun run build
22 |
23 | bun run build:lib
24 |
--------------------------------------------------------------------------------
/.github/scripts/replace_template.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Check if the correct number of arguments are provided
4 | if [ $# -ne 1 ]; then
5 | echo "Usage: $0 "
6 | exit 1
7 | fi
8 |
9 | # Assign arguments to variables
10 | custom_string="$1"
11 |
12 | # Perform the replacement and save to output file
13 | sed "s/<%version%>/$custom_string/g" "angular.json" > "angular.json.tmp" && mv "angular.json.tmp" "angular.json"
14 |
15 | echo "Template string replaced successfully. 🎉"
--------------------------------------------------------------------------------
/.github/scripts/setup_hooks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Define the directory containing sample hooks
4 | SAMPLE_HOOKS_DIR=".github/hooks"
5 |
6 | # Define the target directory for Git hooks
7 | GIT_HOOKS_DIR=".git/hooks"
8 |
9 | # Function to copy or replace hooks
10 | copy_or_replace_hooks() {
11 | for hook in "$SAMPLE_HOOKS_DIR"/*; do
12 | hook_name=$(basename "$hook")
13 | target_hook="$GIT_HOOKS_DIR/$hook_name"
14 | if [ -f "$target_hook" ]; then
15 | echo "Replacing existing hook: $hook_name"
16 | else
17 | echo "Copying new hook: $hook_name"
18 | fi
19 | cp "$hook" "$target_hook"
20 | chmod ug+x "$target_hook" # Ensure executable permission is set
21 | done
22 | }
23 |
24 | # Main function
25 | main() {
26 | # Check if .git/hooks directory exists
27 | if [ ! -d "$GIT_HOOKS_DIR" ]; then
28 | echo "Error: .git/hooks directory not found. Are you in a Git repository?"
29 | exit 1
30 | fi
31 |
32 | # Copy or replace hooks
33 | copy_or_replace_hooks
34 |
35 | echo "Git hooks setup complete."
36 | }
37 |
38 | # Run the main function
39 | main
40 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: 'CodeQL'
13 |
14 | on: [push]
15 |
16 | jobs:
17 | analyze:
18 | name: Analyze
19 | runs-on: ubuntu-latest
20 | permissions:
21 | actions: read
22 | contents: read
23 | security-events: write
24 |
25 | strategy:
26 | fail-fast: false
27 | matrix:
28 | language: ['javascript', 'typescript']
29 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
30 | # Use only 'java' to analyze code written in Java, Kotlin or both
31 | # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
32 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
33 |
34 | steps:
35 | - name: Checkout repository
36 | uses: actions/checkout@v4
37 |
38 | # Initializes the CodeQL tools for scanning.
39 | - name: Initialize CodeQL
40 | uses: github/codeql-action/init@v3
41 | with:
42 | languages: ${{ matrix.language }}
43 | # If you wish to specify custom queries, you can do so here or in a config file.
44 | # By default, queries listed here will override any specified in a config file.
45 | # Prefix the list here with "+" to use these queries and those in the config file.
46 |
47 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
48 | # queries: security-extended,security-and-quality
49 |
50 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
51 | # If this step fails, then you should remove it and run the build manually (see below)
52 | - name: Autobuild
53 | uses: github/codeql-action/autobuild@v3
54 |
55 | # ℹ️ Command-line programs to run using the OS shell.
56 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
57 |
58 | # If the Autobuild fails above, remove it and uncomment the following three lines.
59 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
60 |
61 | # - run: |
62 | # echo "Run, Build Application using script"
63 | # ./location_of_script_within_repo/buildscript.sh
64 |
65 | - name: Perform CodeQL Analysis
66 | uses: github/codeql-action/analyze@v3
67 | with:
68 | category: '/language:${{matrix.language}}'
69 |
--------------------------------------------------------------------------------
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: GitHub pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - develop
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: write
13 | steps:
14 | - uses: actions/checkout@v4
15 | with:
16 | submodules: true
17 |
18 | - uses: oven-sh/setup-bun@v2
19 | with:
20 | bun-version: latest
21 |
22 | - uses: actions/setup-node@v4
23 | with:
24 | node-version: 22
25 |
26 | - name: Install deps
27 | run: |
28 | bun i
29 |
30 | - name: Semantic Release
31 | id: semantic-release
32 | env:
33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34 | run: |
35 | npx semantic-release
36 |
37 | - name: Build demo app
38 | env:
39 | RELEASE_VERSION: ${{ steps.semantic-release.outputs.new-release-version }}
40 | run: |
41 | echo '********'
42 | echo "RELEASE_VERSION: $RELEASE_VERSION"
43 | echo '********'
44 | bash .github/workflows/scripts/replace_template.sh $RELEASE_VERSION
45 | bun run build
46 |
47 | - name: Deploy demo
48 | uses: peaceiris/actions-gh-pages@v3
49 | with:
50 | github_token: ${{ secrets.GITHUB_TOKEN }}
51 | publish_dir: ./dist/ngx-mask/browser
52 |
53 | outputs:
54 | version: ${{ steps.semantic-release.outputs.new-release-version }}
55 |
56 | slack_notification:
57 | needs:
58 | - build
59 | runs-on: ubuntu-latest
60 | steps:
61 | - name: Post to a Slack channel
62 | id: slack
63 | uses: slackapi/slack-github-action@v2.0.0
64 | with:
65 | method: chat.postMessage
66 | token: ${{ secrets.SLACK_BOT_TOKEN }}
67 | payload: |
68 | {
69 | "text": "GitHub Action build result: ${{ job.status == 'success' && ':white_check_mark:' || ':x:' }}",
70 | "channel": "deployments",
71 | "blocks": [
72 | {
73 | "type": "section",
74 | "text": {
75 | "type": "mrkdwn",
76 | "text": "GitHub Action build result: ${{ job.status == 'success' && ':white_check_mark:' || ':x:' }}"
77 | }
78 | },
79 | {
80 | "type": "section",
81 | "text": {
82 | "type": "mrkdwn",
83 | "text": "Project: `${{ github.event.repository.name }}`"
84 | }
85 | },
86 | {
87 | "type": "section",
88 | "text": {
89 | "type": "mrkdwn",
90 | "text": "Version: `${{ needs.build.outputs.version || 'TBA' }}`"
91 | }
92 | },
93 | {
94 | "type": "section",
95 | "text": {
96 | "type": "mrkdwn",
97 | "text": "Commit/PR URL: ${{ github.event.pull_request.html_url || github.event.head_commit.url }}"
98 | }
99 | },
100 | {
101 | "type": "section",
102 | "text": {
103 | "type": "mrkdwn",
104 | "text": "Website URL: ${{ secrets.WEBSITE_URL || 'TBA' }}"
105 | }
106 | }
107 | ]
108 | }
109 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish npm
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | outputs:
12 | version: ${{ steps.get_version.outputs.version }}
13 | steps:
14 | - uses: actions/checkout@v4
15 | with:
16 | submodules: true
17 | - uses: oven-sh/setup-bun@v2
18 | with:
19 | bun-version: latest
20 | - uses: actions/setup-node@v4
21 | with:
22 | node-version: 22
23 | registry-url: https://registry.npmjs.org/
24 | - name: Build library
25 | run: |
26 | bun i
27 | bun run build:lib
28 | - name: Setup npm token
29 | run: |
30 | echo "//registry.npmjs.org/:_authToken=${{ secrets.npm_token }}" > dist/ngx-mask-lib/.npmrc
31 | - name: Publish library
32 | run: bun publish:lib
33 | env:
34 | NODE_AUTH_TOKEN: ${{secrets.npm_token}}
35 | - name: Extract version
36 | id: get_version
37 | run: |
38 | VERSION=$(node -p "require('./dist/ngx-mask-lib/package.json').version")
39 | echo "version=$VERSION" >> $GITHUB_OUTPUT
40 |
41 | slack_notification:
42 | needs:
43 | - build
44 | runs-on: ubuntu-latest
45 | steps:
46 | - name: Post to a Slack channel
47 | id: slack
48 | uses: slackapi/slack-github-action@v2.0.0
49 | with:
50 | method: chat.postMessage
51 | token: ${{ secrets.SLACK_BOT_TOKEN }}
52 | payload: |
53 | {
54 | "text": "GitHub Action build result: ${{ job.status == 'success' && ':white_check_mark:' || ':x:' }}",
55 | "channel": "deployments",
56 | "blocks": [
57 | {
58 | "type": "section",
59 | "text": {
60 | "type": "mrkdwn",
61 | "text": "GitHub Action build result: ${{ job.status == 'success' && ':white_check_mark:' || ':x:' }}"
62 | }
63 | },
64 | {
65 | "type": "section",
66 | "text": {
67 | "type": "mrkdwn",
68 | "text": "Project: `${{ github.event.repository.name }}`"
69 | }
70 | },
71 | {
72 | "type": "section",
73 | "text": {
74 | "type": "mrkdwn",
75 | "text": "Version: `${{ needs.build.outputs.version || 'TBA' }}`"
76 | }
77 | },
78 | {
79 | "type": "section",
80 | "text": {
81 | "type": "mrkdwn",
82 | "text": "Commit/PR URL: ${{ github.event.pull_request.html_url || github.event.head_commit.url }}"
83 | }
84 | },
85 | {
86 | "type": "section",
87 | "text": {
88 | "type": "mrkdwn",
89 | "text": "Website URL: ${{ secrets.NPM_PACKAGE_URL || 'TBA' }}"
90 | }
91 | }
92 | ]
93 | }
94 | env:
95 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
96 |
--------------------------------------------------------------------------------
/.github/workflows/quality-check.yml:
--------------------------------------------------------------------------------
1 | name: build and quality
2 |
3 | on:
4 | push:
5 | branches:
6 | - '**'
7 | pull_request:
8 | branches:
9 | - main
10 | - develop
11 | types: [opened, synchronize, reopened]
12 |
13 | jobs:
14 | should-run:
15 | runs-on: ubuntu-latest
16 | outputs:
17 | should-run-check: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository }}
18 | steps:
19 | - run: echo "Checking if workflow should run"
20 |
21 | quality-check:
22 | needs: should-run
23 | if: needs.should-run.outputs.should-run-check == 'true'
24 | runs-on: ubuntu-latest
25 | permissions:
26 | contents: write
27 | pull-requests: write
28 | steps:
29 | - uses: actions/checkout@v4
30 | with:
31 | submodules: true
32 | ref: ${{ github.event.pull_request.head.sha }}
33 | fetch-depth: 0
34 | - uses: oven-sh/setup-bun@v2
35 | with:
36 | bun-version: latest
37 | - uses: actions/setup-node@v4
38 | with:
39 | node-version: 22
40 | - name: Check quality
41 | run: |
42 | bun i
43 | bash .github/workflows/scripts/quality.sh
44 |
--------------------------------------------------------------------------------
/.github/workflows/scripts/quality.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin
2 |
3 | set -e
4 |
5 | bun lint
6 |
7 |
8 | output=$(bun run type-coverage)
9 | if echo "$output" | grep -q "lower than "; then
10 | echo "$output"
11 | exit 1 # Terminate the hook script with a non-zero exit code
12 | else
13 | echo "Type coverage is good! 🎉"
14 | fi
15 |
16 | bun run test
17 | bun run cypress:bash
18 |
19 | bun run build
20 |
21 | bun run build:lib
22 |
--------------------------------------------------------------------------------
/.github/workflows/scripts/replace_template.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Check if the correct number of arguments are provided
4 | if [ $# -ne 1 ]; then
5 | echo "Usage: $0 "
6 | exit 1
7 | fi
8 |
9 | # Assign arguments to variables
10 | custom_string="$1"
11 |
12 | # Perform the replacement and save to output file
13 | sed "s/<%version%>/$custom_string/g" "angular.json" > "angular.json.tmp" && mv "angular.json.tmp" "angular.json"
14 |
15 | echo "Template string replaced successfully. 🎉"
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | # IDEs and editors
12 | /.idea
13 | /.vscode
14 | .project
15 | .classpath
16 | *.launch
17 | .settings/
18 | .nx
19 |
20 | # misc
21 | /.angular/cache
22 | /.cache
23 | /.sass-cache
24 | /connect.lock
25 | /coverage/*
26 | /src/coverage/*
27 | /projects/ngx-mask-lib/coverage/*
28 | /libpeerconnection.log
29 | npm-debug.log
30 | testem.log
31 | /typings
32 | /cypress/*
33 | .nx
34 | # e2e
35 | /e2e/*.js
36 | /e2e/*.map
37 |
38 | #System Files
39 | .DS_Store
40 | Thumbs.db
41 |
42 | /build
43 |
44 | .ng_pkg_build/
45 | dist.tgz
46 | yarn.lock
47 |
48 | test-reports/
49 |
50 | /projects/ngx-mask-lib/cypress/videos
51 | /projects/ngx-mask-lib/cypress/screenshots
52 | /projects/ngx-mask-lib/coverage
53 |
54 | .angular
55 |
56 | # Snyk
57 | .dccache
58 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "src/libraries"]
2 | path = src/libraries
3 | branch = main
4 | url = https://github.com/JsDaddy/libraries.git
5 |
--------------------------------------------------------------------------------
/.lintstagedrc:
--------------------------------------------------------------------------------
1 | {
2 | "*.{ts,js,json}": [
3 | "eslint --report-unused-disable-directives --max-warnings 0 --no-warn-ignored --fix",
4 | "prettier . --write"
5 | ],
6 | "*.scss": "stylelint --fix"
7 | }
8 |
--------------------------------------------------------------------------------
/.markdownlint.json:
--------------------------------------------------------------------------------
1 | {
2 | "default": true,
3 | "MD013": false,
4 | "MD033": false,
5 | "MD041": false,
6 | "MD014": false,
7 | "MD024": false
8 | }
9 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "useTabs": false,
4 | "singleQuote": true,
5 | "semi": true,
6 | "bracketSpacing": true,
7 | "trailingComma": "es5",
8 | "bracketSameLine": true,
9 | "printWidth": 100,
10 | "endOfLine": "lf",
11 | "overrides": [
12 | {
13 | "files": "*.html",
14 | "options": {
15 | "parser": "html"
16 | }
17 | },
18 | {
19 | "files": "*.component.html",
20 | "options": {
21 | "parser": "angular"
22 | }
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/.puppeteerrc.cjs:
--------------------------------------------------------------------------------
1 | const { join } = require('path');
2 |
3 | /**
4 | * @type {import("puppeteer").Configuration}
5 | */
6 | module.exports = {
7 | // Changes the cache location for Puppeteer.
8 | cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
9 | };
10 |
--------------------------------------------------------------------------------
/.releaserc.yaml:
--------------------------------------------------------------------------------
1 | branches:
2 | - develop
3 |
4 | plugins:
5 | - '@semantic-release/commit-analyzer'
6 | - '@semantic-release/github'
7 | - 'semantic-release-export-data'
8 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | # Compiled output
2 | /dist
3 | /tmp
4 | /out-tsc
5 | /bazel-out
6 | /.angular
7 | /test-reports
8 | /src/coverage
9 |
10 | /projects/ngx-mask-lib/coverage
11 |
12 | # Node
13 | /node_modules
14 |
15 | # IDEs and editors
16 | .idea/
17 |
18 | # Visual Studio Code
19 | .vscode
20 | .history
21 |
22 | # Miscellaneous
23 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["stylelint-config-recommended-scss", "stylelint-prettier/recommended"],
3 | "plugins": ["stylelint-scss", "stylelint-prettier"],
4 | "customSyntax": "postcss-scss",
5 | "rules": {
6 | "no-empty-source": null,
7 | "scss/comment-no-empty": null,
8 | "no-descending-specificity": null,
9 | "at-rule-no-unknown": null,
10 | "at-rule-no-deprecated": null,
11 | "selector-type-no-unknown": [
12 | true,
13 | {
14 | "ignoreTypes": ["/^mat-/", ":host", ":root"]
15 | }
16 | ],
17 | "scss/at-rule-no-unknown": [
18 | true,
19 | {
20 | "ignoreAtRules": ["tailwind"]
21 | }
22 | ]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | - Demonstrating empathy and kindness toward other people
21 | - Being respectful of differing opinions, viewpoints, and experiences
22 | - Giving and gracefully accepting constructive feedback
23 | - Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | - Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | - The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | - Trolling, insulting or derogatory comments, and personal or political attacks
33 | - Public or private harassment
34 | - Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | - Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | .
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Issues
4 |
5 | When submitting an issue, please include a reproducible case that we can actually run. Protractor has a test Angular application available at `http://www.protractortest.org/testapp` which you can use for the reproducible test case. If there's an error, please include the error text.
6 |
7 | Please format code and markup in your issue using [github markdown](https://help.github.com/articles/github-flavored-markdown).
8 |
9 | ## Contributing to Source Code (Pull Requests)
10 |
11 | - If your PR changes any behavior or fixes an issue, it should have an associated test.
12 | - New features should be general and as simple as possible.
13 | - Breaking changes should be avoided if possible.
14 | - All pull requests require review. No PR will be merged without a comment from a team member stating LGTM (Looks good to me).
15 |
16 | ## Protractor specific rules
17 |
18 | - JavaScript style should generally follow the [Google JS style guide](https://google.github.io/styleguide/javascriptguide.xml).
19 | - Document public methods with jsdoc.
20 | - Be consistent with the code around you!
21 |
22 | ## Commit Messages
23 |
24 | Please write meaningful commit messages - they are used to generate the changelog, so the commit message should tell a user everything they need to know about a commit. Protractor follows AngularJS's [commit message format](https://docs.google.com/a/google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#heading=h.z8a3t6ehl060).
25 |
26 | In summary, this style is
27 |
28 | ():
29 |
30 |
31 |
32 | Where `` is one of [feat, fix, docs, refactor, test, chore, deps] and
33 | `` is a quick descriptor of the location of the change, such as cli, clientSideScripts, element.
34 |
35 | ## Testing your changes
36 |
37 | When you submit a PR, tests will be automatically run on the Continuous Integration environment. If any of your tests fail, review the logs and address the issues before requesting a review.
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 JS Daddy
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 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "ngx-mask": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "prefix": "app",
11 | "schematics": {},
12 | "architect": {
13 | "build": {
14 | "builder": "@angular-devkit/build-angular:application",
15 | "options": {
16 | "security": { "autoCsp": true },
17 | "outputPath": "dist/ngx-mask",
18 | "index": "src/index.html",
19 | "browser": "src/main.ts",
20 | "tsConfig": "tsconfig.app.json",
21 | "assets": ["src/assets", "src/favicon.ico"],
22 | "styles": [
23 | "node_modules/highlight.js/styles/github.css",
24 | "src/styles.scss"
25 | ],
26 | "scripts": [],
27 | "extractLicenses": false,
28 | "sourceMap": true,
29 | "optimization": false,
30 | "namedChunks": true
31 | },
32 | "configurations": {
33 | "production": {
34 | "define": {
35 | "VERSION": "'<%version%>'"
36 | },
37 | "budgets": [
38 | {
39 | "type": "initial",
40 | "maximumWarning": "3mb",
41 | "maximumError": "3mb"
42 | },
43 | {
44 | "type": "anyComponentStyle",
45 | "maximumWarning": "15kb",
46 | "maximumError": "15kb"
47 | }
48 | ],
49 | "outputHashing": "all"
50 | },
51 | "development": {
52 | "optimization": false,
53 | "extractLicenses": false,
54 | "sourceMap": true
55 | }
56 | },
57 | "defaultConfiguration": "production"
58 | },
59 | "serve": {
60 | "builder": "@angular-devkit/build-angular:dev-server",
61 | "options": {
62 | "buildTarget": "ngx-mask:build"
63 | },
64 | "configurations": {
65 | "production": {
66 | "buildTarget": "ngx-mask:build:production"
67 | }
68 | }
69 | },
70 | "test": {
71 | "builder": "@angular-devkit/build-angular:web-test-runner",
72 | "options": {
73 | "tsConfig": "tsconfig.spec.json",
74 | "inlineStyleLanguage": "scss",
75 | "assets": ["src/favicon.ico", "src/assets"],
76 | "polyfills": ["zone.js", "zone.js/testing"],
77 | "styles": ["src/styles.scss"],
78 | "scripts": []
79 | }
80 | },
81 | "lint": {
82 | "builder": "@angular-eslint/builder:lint",
83 | "options": {
84 | "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
85 | }
86 | }
87 | }
88 | },
89 | "ngx-mask-lib": {
90 | "root": "projects/ngx-mask-lib",
91 | "sourceRoot": "projects/ngx-mask-lib/src",
92 | "projectType": "library",
93 | "prefix": "app",
94 | "architect": {
95 | "build": {
96 | "builder": "@angular-devkit/build-angular:ng-packagr",
97 | "options": {
98 | "tsConfig": "projects/ngx-mask-lib/tsconfig.lib.json",
99 | "project": "projects/ngx-mask-lib/ng-package.json"
100 | },
101 | "configurations": {
102 | "production": {
103 | "project": "projects/ngx-mask-lib/ng-package.prod.json",
104 | "tsConfig": "projects/ngx-mask-lib/tsconfig.lib.prod.json"
105 | }
106 | }
107 | },
108 | "lint": {
109 | "builder": "@angular-eslint/builder:lint",
110 | "options": {
111 | "lintFilePatterns": ["**/*.js", "**/*.ts", "**/*.html", "**/*.json"]
112 | }
113 | },
114 | "ct": {
115 | "builder": "@nrwl/cypress:cypress",
116 | "options": {
117 | "cypressConfig": "projects/ngx-mask-lib/cypress.json",
118 | "tsConfig": "projects/ngx-mask-lib/tsconfig.cypress.json",
119 | "testingType": "component"
120 | }
121 | }
122 | }
123 | }
124 | },
125 | "cli": {
126 | "warnings": {
127 | "typescriptMismatch": false
128 | },
129 | "analytics": "682218d2-280d-47b1-b385-b2c8c903f9a9"
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | rules: {
4 | 'scope-empty': [2, 'never'],
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/cypress.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'cypress';
2 |
3 | export default defineConfig({
4 | projectId: 'qhyo66',
5 |
6 | component: {
7 | devServer: {
8 | framework: 'angular',
9 | bundler: 'webpack',
10 | },
11 | specPattern: 'projects/ngx-mask-lib/src/test/**/*.cy-spec.ts',
12 | },
13 |
14 | defaultCommandTimeout: 10000,
15 | });
16 |
--------------------------------------------------------------------------------
/cypress/fixture/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/cypress/support/component-index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Components App
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/cypress/support/component.ts:
--------------------------------------------------------------------------------
1 | import { mount } from 'cypress/angular';
2 |
3 | declare global {
4 | // eslint-disable-next-line @typescript-eslint/no-namespace
5 | namespace Cypress {
6 | // eslint-disable-next-line @typescript-eslint/naming-convention
7 | interface Chainable {
8 | mount: typeof mount;
9 | }
10 | }
11 | }
12 |
13 | Cypress.Commands.add('mount', mount);
14 |
15 | // Example use:
16 | // cy.mount(MyComponent)
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-mask",
3 | "version": "19.0.7",
4 | "description": "Awesome ngx mask",
5 | "license": "MIT",
6 | "engines": {
7 | "node": ">=16.0.0"
8 | },
9 | "angular-cli": {},
10 | "keywords": [
11 | "ng2-mask",
12 | "ngx-mask",
13 | "ng2",
14 | "angular-mask",
15 | "mask",
16 | "angular",
17 | "angular2",
18 | "angular2-mask",
19 | "ng2mask",
20 | "jsdaddy"
21 | ],
22 | "private": true,
23 | "scripts": {
24 | "ng": "ng",
25 | "build": "ng build --base-href /ngx-mask/ --configuration production",
26 | "build:lib": "ng build --configuration production ngx-mask-lib && bun run copy-libdocs",
27 | "ci": "bun run lint && bun run build:lib && bun run test:all && bun run build",
28 | "copy-libdocs": "cp README.md LICENSE dist/ngx-mask-lib",
29 | "lint:scss": "stylelint \"**/*.scss\"",
30 | "lint:scss:fix": "stylelint \"**/*.scss\" --fix",
31 | "lint": "ng lint && bun run lint:scss",
32 | "lint:fix": "ng lint --fix && bun run lint:scss:fix",
33 | "lint:markdown": "markdownlint -i projects/ngx-mask-lib/coverage -i node_modules -i CHANGELOG.md ./",
34 | "pack:lib": "cd dist/ngx-mask-lib && npm pack",
35 | "precommit-msg": "echo 'Please wait while we do our pre-commit checks...' && exit 0",
36 | "prettier": "prettier './src/**/*.ts' './projects/**/*.ts' --write",
37 | "publish:lib": "cd dist/ngx-mask-lib && bun publish",
38 | "release:major": "bun run version:major && bun run build:lib && bun run pack:lib && bun run publish:lib",
39 | "release:minor": "bun run version:minor && bun run build:lib && bun run pack:lib && bun run publish:lib",
40 | "release:patch": "bun run version:patch && bun run build:lib && bun run pack:lib && bun run publish:lib",
41 | "start": "ng serve",
42 | "start-prod": "angular-http-server --path dist/ngx-mask --p 3000 --silent",
43 | "test": "ng test",
44 | "cypress:open": "cypress open",
45 | "cypress:bash": "npx cypress run --component",
46 | "test:all": "bun run test:app && bun run test:lib",
47 | "test:app": "ng test ngx-mask",
48 | "test:lib": "ng test ngx-mask-lib",
49 | "test:lib:ct": "ngcc && ng run ngx-mask-lib:ct",
50 | "version:major": "npm --no-git-tag-version version major && cd projects/ngx-mask-lib && npm --no-git-tag-version version major",
51 | "version:minor": "npm --no-git-tag-version version minor && cd projects/ngx-mask-lib && npm --no-git-tag-version version minor",
52 | "version:patch": "npm --no-git-tag-version version patch && cd projects/ngx-mask-lib && npm --no-git-tag-version version patch",
53 | "type-coverage": "type-coverage",
54 | "init:git:hooks": ".github/scripts/setup_hooks.sh",
55 | "snyk:auth": "snyk auth",
56 | "snyk:test": "snyk test && snyk code test"
57 | },
58 | "repository": {
59 | "type": "git",
60 | "url": "https://github.com/JsDaddy/ngx-mask.git"
61 | },
62 | "dependencies": {
63 | "@angular/animations": "19.2.11",
64 | "@angular/common": "19.2.11",
65 | "@angular/compiler": "19.2.11",
66 | "@angular/core": "19.2.11",
67 | "@angular/forms": "19.2.11",
68 | "@angular/platform-browser": "19.2.11",
69 | "@angular/platform-browser-dynamic": "19.2.11",
70 | "@angular/router": "19.2.11",
71 | "@types/jest": "29.5.14",
72 | "@types/mocha": "10.0.10",
73 | "cypress": "14.3.2",
74 | "highlight.js": "11.11.1",
75 | "ngx-highlightjs": "14.0.0",
76 | "ngxtension": "5.0.0",
77 | "rxjs": "7.8.2",
78 | "semantic-release": "24.2.4",
79 | "semantic-release-export-data": "1.1.0",
80 | "snyk": "1.1297.1"
81 | },
82 | "devDependencies": {
83 | "@angular-devkit/build-angular": "19.2.12",
84 | "@angular-eslint/builder": "19.4.0",
85 | "@angular-eslint/eslint-plugin": "19.4.0",
86 | "@angular-eslint/eslint-plugin-template": "19.4.0",
87 | "@angular-eslint/schematics": "19.4.0",
88 | "@angular-eslint/template-parser": "19.4.0",
89 | "@angular/cli": "19.2.12",
90 | "@angular/compiler-cli": "19.2.11",
91 | "@angular/language-service": "19.2.11",
92 | "@commitlint/cli": "19.8.1",
93 | "@commitlint/config-conventional": "19.8.1",
94 | "@jscutlery/cypress-angular": "0.9.22",
95 | "@types/highlight.js": "9.12.4",
96 | "@types/jasmine": "5.1.8",
97 | "@types/node": "22.15.18",
98 | "@typescript-eslint/eslint-plugin": "8.32.1",
99 | "@typescript-eslint/parser": "8.32.1",
100 | "@web/test-runner": "0.20.1",
101 | "angular-cli-ghpages": "2.0.3",
102 | "angular-http-server": "1.12.0",
103 | "eslint": "9.27.0",
104 | "eslint-config-prettier": "10.1.5",
105 | "eslint-plugin-json": "4.0.1",
106 | "eslint-plugin-prettier": "5.4.0",
107 | "jasmine-core": "5.7.1",
108 | "jasmine-spec-reporter": "7.0.0",
109 | "lint-staged": "16.0.0",
110 | "markdownlint-cli": "0.45.0",
111 | "ng-packagr": "19.2.2",
112 | "npm-check-updates": "18.0.1",
113 | "prettier": "3.5.3",
114 | "puppeteer": "24.8.2",
115 | "stylelint": "16.19.1",
116 | "stylelint-config-prettier": "9.0.5",
117 | "stylelint-config-recommended-scss": "15.0.0",
118 | "stylelint-prettier": "5.0.3",
119 | "type-coverage": "2.29.7",
120 | "typescript": "5.8.3",
121 | "angular-eslint": "19.4.0",
122 | "typescript-eslint": "8.32.1",
123 | "tailwindcss": "3.4.16",
124 | "bun-types": "1.2.13",
125 | "postcss": "8.5.3",
126 | "postcss-nesting": "13.0.1",
127 | "cssnano": "7.0.7",
128 | "postcss-scss": "4.0.9"
129 | },
130 | "typeCoverage": {
131 | "atLeast": 91,
132 | "ignoreObject": true,
133 | "ignoreAsAssertion": true,
134 | "ignoreTypeAssertion;": true,
135 | "strict": true,
136 | "suppressError": true,
137 | "reportSemanticError": true,
138 | "ignoreCatch": true,
139 | "ignoreFiles": [
140 | "server/**/*.ts",
141 | "server.ts"
142 | ]
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | 'postcss-nesting': {},
4 | tailwindcss: {},
5 | autoprefixer: {},
6 | cssnano: { preset: 'default' },
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/ngx-mask-lib",
4 | "deleteDestPath": false,
5 | "lib": {
6 | "entryFile": "src/index.ts"
7 | },
8 | "allowedNonPeerDependencies": []
9 | }
10 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/ng-package.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/ngx-mask-lib",
4 | "lib": {
5 | "entryFile": "src/index.ts"
6 | },
7 | "allowedNonPeerDependencies": []
8 | }
9 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-mask",
3 | "version": "19.0.7",
4 | "description": "awesome ngx mask",
5 | "keywords": [
6 | "ng2-mask",
7 | "ngx-mask",
8 | "ng2",
9 | "angular-mask",
10 | "mask",
11 | "angular",
12 | "angular2",
13 | "angular2-mask",
14 | "ng2mask",
15 | "jsdaddy"
16 | ],
17 | "license": "MIT",
18 | "author": "ngx-mask",
19 | "contributors": [
20 | "ngx-mask"
21 | ],
22 | "homepage": "https://github.com/JsDaddy/ngx-mask",
23 | "repository": {
24 | "type": "git",
25 | "url": "git@github.com:JsDaddy/ngx-mask.git"
26 | },
27 | "bugs": {
28 | "url": "https://github.com/JsDaddy/ngx-mask/issues"
29 | },
30 | "peerDependencies": {
31 | "@angular/common": ">=14.0.0",
32 | "@angular/core": ">=14.0.0",
33 | "@angular/forms": ">=14.0.0"
34 | },
35 | "dependencies": {}
36 | }
37 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './lib/ngx-mask.providers';
2 | export * from './lib/ngx-mask.directive';
3 | export * from './lib/ngx-mask.pipe';
4 | export * from './lib/ngx-mask.service';
5 | export * from './lib/ngx-mask.config';
6 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/lib/custom-keyboard-event.ts:
--------------------------------------------------------------------------------
1 | declare let global: any;
2 |
3 | const commonjsGlobal =
4 | typeof globalThis !== 'undefined'
5 | ? globalThis
6 | : typeof window !== 'undefined'
7 | ? window
8 | : typeof global !== 'undefined'
9 | ? global
10 | : typeof self !== 'undefined'
11 | ? self
12 | : {};
13 |
14 | (function () {
15 | if (!commonjsGlobal.KeyboardEvent) {
16 | // eslint-disable-next-line @typescript-eslint/no-empty-function
17 | commonjsGlobal.KeyboardEvent = function (_eventType: any, _init: any) {};
18 | }
19 | })();
20 |
21 | export type CustomKeyboardEvent = KeyboardEvent;
22 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/lib/ngx-mask-expression.enum.ts:
--------------------------------------------------------------------------------
1 | export const enum MaskExpression {
2 | SEPARATOR = 'separator',
3 | PERCENT = 'percent',
4 | IP = 'IP',
5 | CPF_CNPJ = 'CPF_CNPJ',
6 | MONTH = 'M',
7 | MONTHS = 'M0',
8 | MINUTE = 'm',
9 | HOUR = 'h',
10 | HOURS = 'H',
11 | MINUTES = 'm0',
12 | HOURS_HOUR = 'Hh',
13 | SECONDS = 's0',
14 | HOURS_MINUTES_SECONDS = 'Hh:m0:s0',
15 | EMAIL_MASK = 'A*@A*.A*',
16 | HOURS_MINUTES = 'Hh:m0',
17 | MINUTES_SECONDS = 'm0:s0',
18 | DAYS_MONTHS_YEARS = 'd0/M0/0000',
19 | DAYS_MONTHS = 'd0/M0',
20 | DAYS = 'd0',
21 | DAY = 'd',
22 | SECOND = 's',
23 | LETTER_S = 'S',
24 | DOT = '.',
25 | COMMA = ',',
26 | CURLY_BRACKETS_LEFT = '{',
27 | CURLY_BRACKETS_RIGHT = '}',
28 | MINUS = '-',
29 | OR = '||',
30 | HASH = '#',
31 | EMPTY_STRING = '',
32 | SYMBOL_STAR = '*',
33 | SYMBOL_QUESTION = '?',
34 | SLASH = '/',
35 | WHITE_SPACE = ' ',
36 | NUMBER_ZERO = '0',
37 | NUMBER_NINE = '9',
38 | BACKSPACE = 'Backspace',
39 | DELETE = 'Delete',
40 | ARROW_LEFT = 'ArrowLeft',
41 | ARROW_UP = 'ArrowUp',
42 | DOUBLE_ZERO = '00',
43 | }
44 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/lib/ngx-mask.config.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter, InjectionToken } from '@angular/core';
2 | import { MaskExpression } from './ngx-mask-expression.enum';
3 |
4 | export type InputTransformFn = (value: unknown) => string | number;
5 |
6 | export type OutputTransformFn = (value: string | number | undefined | null) => unknown;
7 |
8 | export type NgxMaskConfig = {
9 | suffix: string;
10 | prefix: string;
11 | thousandSeparator: string;
12 | decimalMarker: '.' | ',' | ['.', ','];
13 | clearIfNotMatch: boolean;
14 | showMaskTyped: boolean;
15 | placeHolderCharacter: string;
16 | shownMaskExpression: string;
17 | specialCharacters: string[] | readonly string[];
18 | dropSpecialCharacters: boolean | string[] | readonly string[];
19 | hiddenInput: boolean;
20 | validation: boolean;
21 | instantPrefix: boolean;
22 | separatorLimit: string;
23 | apm: boolean;
24 | allowNegativeNumbers: boolean;
25 | leadZeroDateTime: boolean;
26 | leadZero: boolean;
27 | triggerOnMaskChange: boolean;
28 | keepCharacterPositions: boolean;
29 | inputTransformFn: InputTransformFn;
30 | outputTransformFn: OutputTransformFn;
31 | maskFilled: EventEmitter;
32 | patterns: Record<
33 | string,
34 | {
35 | pattern: RegExp;
36 | optional?: boolean;
37 | symbol?: string;
38 | }
39 | >;
40 | };
41 |
42 | export type NgxMaskOptions = Partial;
43 | export const NGX_MASK_CONFIG = new InjectionToken('ngx-mask config');
44 | export const NEW_CONFIG = new InjectionToken('new ngx-mask config');
45 | export const INITIAL_CONFIG = new InjectionToken('initial ngx-mask config');
46 |
47 | export const initialConfig: NgxMaskConfig = {
48 | suffix: '',
49 | prefix: '',
50 | thousandSeparator: ' ',
51 | decimalMarker: ['.', ','],
52 | clearIfNotMatch: false,
53 | showMaskTyped: false,
54 | instantPrefix: false,
55 | placeHolderCharacter: '_',
56 | dropSpecialCharacters: true,
57 | hiddenInput: false,
58 | shownMaskExpression: '',
59 | separatorLimit: '',
60 | allowNegativeNumbers: false,
61 | validation: true,
62 | specialCharacters: ['-', '/', '(', ')', '.', ':', ' ', '+', ',', '@', '[', ']', '"', "'"],
63 | leadZeroDateTime: false,
64 | apm: false,
65 | leadZero: false,
66 | keepCharacterPositions: false,
67 | triggerOnMaskChange: false,
68 | inputTransformFn: (value: unknown) => value as string | number,
69 | outputTransformFn: (value: string | number | undefined | null) => value,
70 | maskFilled: new EventEmitter(),
71 | patterns: {
72 | '0': {
73 | pattern: new RegExp('\\d'),
74 | },
75 | '9': {
76 | pattern: new RegExp('\\d'),
77 | optional: true,
78 | },
79 | X: {
80 | pattern: new RegExp('\\d'),
81 | symbol: '*',
82 | },
83 | A: {
84 | pattern: new RegExp('[a-zA-Z0-9]'),
85 | },
86 | S: {
87 | pattern: new RegExp('[a-zA-Z]'),
88 | },
89 | U: {
90 | pattern: new RegExp('[A-Z]'),
91 | },
92 | L: {
93 | pattern: new RegExp('[a-z]'),
94 | },
95 | d: {
96 | pattern: new RegExp('\\d'),
97 | },
98 | m: {
99 | pattern: new RegExp('\\d'),
100 | },
101 | M: {
102 | pattern: new RegExp('\\d'),
103 | },
104 | H: {
105 | pattern: new RegExp('\\d'),
106 | },
107 | h: {
108 | pattern: new RegExp('\\d'),
109 | },
110 | s: {
111 | pattern: new RegExp('\\d'),
112 | },
113 | },
114 | };
115 |
116 | export const timeMasks: string[] = [
117 | MaskExpression.HOURS_MINUTES_SECONDS,
118 | MaskExpression.HOURS_MINUTES,
119 | MaskExpression.MINUTES_SECONDS,
120 | ];
121 |
122 | export const withoutValidation: string[] = [
123 | MaskExpression.PERCENT,
124 | MaskExpression.HOURS_HOUR,
125 | MaskExpression.SECONDS,
126 | MaskExpression.MINUTES,
127 | MaskExpression.SEPARATOR,
128 | MaskExpression.DAYS_MONTHS_YEARS,
129 | MaskExpression.DAYS_MONTHS,
130 | MaskExpression.DAYS,
131 | MaskExpression.MONTHS,
132 | ];
133 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/lib/ngx-mask.pipe.ts:
--------------------------------------------------------------------------------
1 | import type { PipeTransform } from '@angular/core';
2 | import { inject, Pipe } from '@angular/core';
3 |
4 | import type { NgxMaskConfig } from './ngx-mask.config';
5 | import { NGX_MASK_CONFIG } from './ngx-mask.config';
6 | import { NgxMaskService } from './ngx-mask.service';
7 | import { MaskExpression } from './ngx-mask-expression.enum';
8 |
9 | @Pipe({
10 | name: 'mask',
11 | pure: true,
12 | standalone: true,
13 | })
14 | export class NgxMaskPipe implements PipeTransform {
15 | private readonly defaultOptions = inject(NGX_MASK_CONFIG);
16 |
17 | private readonly _maskService = inject(NgxMaskService);
18 |
19 | private _maskExpressionArray: string[] = [];
20 |
21 | private mask = '';
22 |
23 | public transform(
24 | value: string | number,
25 | mask: string,
26 | { patterns, ...config }: Partial = {} as Partial
27 | ): string {
28 | let processedValue: string | number = value;
29 |
30 | const currentConfig = {
31 | maskExpression: mask,
32 | ...this.defaultOptions,
33 | ...config,
34 | patterns: {
35 | ...this._maskService.patterns,
36 | ...patterns,
37 | },
38 | };
39 |
40 | Object.entries(currentConfig).forEach(([key, val]) => {
41 | (this._maskService as any)[key] = val;
42 | });
43 |
44 | if (mask.includes('||')) {
45 | const maskParts = mask.split('||');
46 | if (maskParts.length > 1) {
47 | this._maskExpressionArray = maskParts.sort(
48 | (a: string, b: string) => a.length - b.length
49 | );
50 | this._setMask(`${processedValue}`);
51 | return this._maskService.applyMask(`${processedValue}`, this.mask);
52 | } else {
53 | this._maskExpressionArray = [];
54 | return this._maskService.applyMask(`${processedValue}`, this.mask);
55 | }
56 | }
57 |
58 | if (mask.includes(MaskExpression.CURLY_BRACKETS_LEFT)) {
59 | return this._maskService.applyMask(
60 | `${processedValue}`,
61 | this._maskService._repeatPatternSymbols(mask)
62 | );
63 | }
64 |
65 | if (mask.startsWith(MaskExpression.SEPARATOR)) {
66 | if (config.decimalMarker) {
67 | this._maskService.decimalMarker = config.decimalMarker;
68 | }
69 | if (config.thousandSeparator) {
70 | this._maskService.thousandSeparator = config.thousandSeparator;
71 | }
72 | if (config.leadZero) {
73 | this._maskService.leadZero = config.leadZero;
74 | }
75 |
76 | processedValue = String(processedValue);
77 | const localeDecimalMarker = this._maskService.currentLocaleDecimalMarker();
78 |
79 | if (!Array.isArray(this._maskService.decimalMarker)) {
80 | processedValue =
81 | this._maskService.decimalMarker !== localeDecimalMarker
82 | ? (processedValue as string).replace(
83 | localeDecimalMarker,
84 | this._maskService.decimalMarker
85 | )
86 | : processedValue;
87 | }
88 |
89 | if (
90 | this._maskService.leadZero &&
91 | processedValue &&
92 | this._maskService.dropSpecialCharacters !== false
93 | ) {
94 | processedValue = this._maskService._checkPrecision(mask, processedValue as string);
95 | }
96 |
97 | if (this._maskService.decimalMarker === MaskExpression.COMMA) {
98 | processedValue = (processedValue as string).replace(
99 | MaskExpression.DOT,
100 | MaskExpression.COMMA
101 | );
102 | }
103 |
104 | this._maskService.isNumberValue = true;
105 | }
106 |
107 | if (processedValue === null || typeof processedValue === 'undefined') {
108 | return this._maskService.applyMask('', mask);
109 | }
110 |
111 | return this._maskService.applyMask(`${processedValue}`, mask);
112 | }
113 |
114 | private _setMask(value: string) {
115 | if (this._maskExpressionArray.length > 0) {
116 | this._maskExpressionArray.some((mask): boolean | void => {
117 | const test =
118 | this._maskService.removeMask(value)?.length <=
119 | this._maskService.removeMask(mask)?.length;
120 | if (value && test) {
121 | this.mask = mask;
122 | return test;
123 | } else {
124 | this.mask =
125 | this._maskExpressionArray[this._maskExpressionArray.length - 1] ??
126 | MaskExpression.EMPTY_STRING;
127 | }
128 | });
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/lib/ngx-mask.providers.ts:
--------------------------------------------------------------------------------
1 | import type { EnvironmentProviders, Provider } from '@angular/core';
2 | import { inject, makeEnvironmentProviders } from '@angular/core';
3 |
4 | import type { NgxMaskOptions } from './ngx-mask.config';
5 | import { NGX_MASK_CONFIG, INITIAL_CONFIG, initialConfig, NEW_CONFIG } from './ngx-mask.config';
6 | import { NgxMaskService } from './ngx-mask.service';
7 |
8 | /**
9 | * @internal
10 | */
11 | function _configFactory(): NgxMaskOptions {
12 | const initConfig = inject(INITIAL_CONFIG);
13 | const configValue = inject NgxMaskOptions)>(NEW_CONFIG);
14 |
15 | return configValue instanceof Function
16 | ? { ...initConfig, ...configValue() }
17 | : { ...initConfig, ...configValue };
18 | }
19 |
20 | export function provideNgxMask(configValue?: NgxMaskOptions | (() => NgxMaskOptions)): Provider[] {
21 | return [
22 | {
23 | provide: NEW_CONFIG,
24 | useValue: configValue,
25 | },
26 | {
27 | provide: INITIAL_CONFIG,
28 | useValue: initialConfig,
29 | },
30 | {
31 | provide: NGX_MASK_CONFIG,
32 | useFactory: _configFactory,
33 | },
34 | NgxMaskService,
35 | ];
36 | }
37 |
38 | export function provideEnvironmentNgxMask(
39 | configValue?: NgxMaskOptions | (() => NgxMaskOptions)
40 | ): EnvironmentProviders {
41 | return makeEnvironmentProviders(provideNgxMask(configValue));
42 | }
43 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/add-suffix.spec.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentFixture } from '@angular/core/testing';
2 | import { TestBed } from '@angular/core/testing';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 |
5 | import { TestMaskComponent } from './utils/test-component.component';
6 | import { equal } from './utils/test-functions.component';
7 | import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
8 |
9 | describe('Directive: Mask (Add suffix)', () => {
10 | let fixture: ComponentFixture;
11 | let component: TestMaskComponent;
12 |
13 | beforeEach(() => {
14 | TestBed.configureTestingModule({
15 | imports: [ReactiveFormsModule, NgxMaskDirective, TestMaskComponent],
16 | providers: [provideNgxMask()],
17 | });
18 | fixture = TestBed.createComponent(TestMaskComponent);
19 | component = fixture.componentInstance;
20 | fixture.detectChanges();
21 | });
22 |
23 | it('should have a suffix', () => {
24 | component.mask.set('00-000-000-00');
25 | component.suffix.set('$');
26 | equal('6', '6$', fixture);
27 | });
28 |
29 | it('should have a suffix if first letter entered is y', () => {
30 | component.mask.set('L{80}');
31 | component.suffix.set('.sh');
32 | equal('y', 'y.sh', fixture);
33 | });
34 |
35 | it('should have a suffix if the first character entered same as the last letter of the suffix', () => {
36 | component.mask.set('L{80}');
37 | component.suffix.set('.sh');
38 | equal('h', 'h.sh', fixture);
39 | });
40 |
41 | it('should display suffix at the end with showMaskTyped mask 0 000', () => {
42 | component.mask.set('0 000');
43 | component.suffix.set('$');
44 | component.showMaskTyped.set(true);
45 | equal('1', '1 ___$', fixture);
46 | equal('12', '1 2__$', fixture);
47 | equal('123', '1 23_$', fixture);
48 | equal('1234', '1 234$', fixture);
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/allow-few-mask.cy-spec.ts:
--------------------------------------------------------------------------------
1 | import { CypressTestMaskComponent } from './utils/cypress-test-component.component';
2 | import { signal } from '@angular/core';
3 |
4 | describe('Test Date Hh:m0', () => {
5 | it('dynamic mask after backspace should have right cursor position (000) 000-0000||+000000000000000', () => {
6 | cy.mount(CypressTestMaskComponent, {
7 | componentProperties: {
8 | mask: signal('(000) 000-0000||+000000000000000'),
9 | },
10 | });
11 |
12 | cy.get('#masked').type('11111111111').should('have.value', '+11111111111');
13 | cy.get('#masked')
14 | .type('{backspace}')
15 | .should('have.value', '(111) 111-1111')
16 | .should('have.prop', 'selectionStart', 14);
17 | });
18 |
19 | it('dynamic mask after backspace should have right cursor position', () => {
20 | cy.mount(CypressTestMaskComponent, {
21 | componentProperties: {
22 | mask: signal('(000) 000-0000||+000000000000000'),
23 | },
24 | });
25 |
26 | cy.get('#masked')
27 | .type('1234567890')
28 |
29 | .should('have.value', '(123) 456-7890')
30 | .type('{leftArrow}'.repeat(7))
31 | .type('{backspace}')
32 | .should('have.prop', 'selectionStart', 5);
33 | });
34 |
35 | it('dynamic mask after backspace should have right cursor position (00) 00000000||+00 (00) 00000000', () => {
36 | cy.mount(CypressTestMaskComponent, {
37 | componentProperties: {
38 | mask: signal('(00) 00000000||+00 (00) 00000000'),
39 | },
40 | });
41 |
42 | cy.get('#masked').type('111').should('have.value', '(11) 1');
43 | cy.get('#masked').type('{backspace}').should('have.prop', 'selectionStart', 4);
44 | });
45 |
46 | it('dynamic mask after backspace should have right cursor position 00) 000-00-00||00) 000-00-00; 0 (000) 000-00-00', () => {
47 | cy.mount(CypressTestMaskComponent, {
48 | componentProperties: {
49 | mask: signal('00) 000-00-00||00) 000-00-00; 0 (000) 000-00-00'),
50 | showMaskTyped: signal(true),
51 | dropSpecialCharacters: signal(false),
52 | prefix: signal('8 (0'),
53 | specialCharacters: signal(['(', ')', '-', ';', '.', ' ']),
54 | },
55 | });
56 |
57 | cy.get('#masked')
58 | .type('123456789 11')
59 | .should('have.value', '8 (012) 345-67-89; 1 (1__) ___-__-__')
60 | .type('{leftArrow}'.repeat(6))
61 | .type('{backspace}')
62 | .should('have.prop', 'selectionStart', 16)
63 | .should('have.value', '8 (012) 345-67-81; 1 (___) ___-__-__')
64 | .type('{backspace}')
65 | .should('have.prop', 'selectionStart', 17)
66 | .should('have.value', '8 (012) 345-67-11');
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/allow-negative-numbers.spec.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentFixture } from '@angular/core/testing';
2 | import { TestBed } from '@angular/core/testing';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 |
5 | import { TestMaskComponent } from './utils/test-component.component';
6 | import { equal } from './utils/test-functions.component';
7 | import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
8 |
9 | describe('Directive: Mask (Allow negative numbers)', () => {
10 | let fixture: ComponentFixture;
11 | let component: TestMaskComponent;
12 |
13 | beforeEach(() => {
14 | TestBed.configureTestingModule({
15 | imports: [ReactiveFormsModule, NgxMaskDirective, TestMaskComponent],
16 | providers: [provideNgxMask()],
17 | });
18 | fixture = TestBed.createComponent(TestMaskComponent);
19 | component = fixture.componentInstance;
20 | fixture.detectChanges();
21 | });
22 |
23 | it('FormControl or NgModel should not allow negative numbers (default functionality)', () => {
24 | component.mask.set('separator.2');
25 | component.thousandSeparator.set(',');
26 | component.allowNegativeNumbers.set(false);
27 | component.dropSpecialCharacters.set(true);
28 | equal('-10,000.00', '10,000.00', fixture);
29 |
30 | expect(component.form.value).toBe('10000.00');
31 |
32 | component.form.setValue(-123456);
33 | equal('-123456.00', '123,456.00', fixture);
34 | expect(component.form.value).toBe(123456);
35 | });
36 |
37 | it('FormControl and NgModel should be filled with negative values', () => {
38 | component.mask.set('separator.2');
39 | component.thousandSeparator.set(',');
40 | component.allowNegativeNumbers.set(true);
41 | component.dropSpecialCharacters.set(true);
42 | component.form.setValue(-123456);
43 |
44 | expect(component.form.value).toBe(-123456);
45 | equal('-123456.00', '-123,456.00', fixture);
46 | });
47 |
48 | it('allowNegativeNumber to mask', () => {
49 | component.mask.set('000.00');
50 | component.allowNegativeNumbers.set(true);
51 |
52 | equal('-123.00', '-123.00', fixture);
53 | equal('-311.9', '-311.9', fixture);
54 | equal('-311', '-311', fixture);
55 | equal('123.00', '123.00', fixture);
56 |
57 | component.mask.set('0000');
58 | component.allowNegativeNumbers.set(true);
59 |
60 | equal('-1230', '-1230', fixture);
61 | equal('-3119', '-3119', fixture);
62 | equal('-311', '-311', fixture);
63 | equal('-31', '-31', fixture);
64 | equal('-3', '-3', fixture);
65 | equal('1230', '1230', fixture);
66 | });
67 |
68 | it('allowNegativeNumber to percent', () => {
69 | component.mask.set('percent');
70 | component.allowNegativeNumbers.set(true);
71 |
72 | equal('-101', '-10', fixture);
73 | equal('-100', '-100', fixture);
74 | equal('-999', '-99', fixture);
75 | equal('-20', '-20', fixture);
76 |
77 | component.mask.set('percent.2');
78 | component.allowNegativeNumbers.set(true);
79 |
80 | equal('-100.00', '-100.00', fixture);
81 | equal('-100.02', '-100.0', fixture);
82 | equal('-999', '-99', fixture);
83 | equal('-99.10', '-99.10', fixture);
84 | equal('-11.11', '-11.11', fixture);
85 | equal('-12.30', '-12.30', fixture);
86 |
87 | component.mask.set('percent.3');
88 | component.allowNegativeNumbers.set(true);
89 |
90 | equal('-100.000', '-100.000', fixture);
91 | equal('-99.001', '-99.001', fixture);
92 | equal('-999', '-99', fixture);
93 | equal('-99.10', '-99.10', fixture);
94 | equal('-11.11', '-11.11', fixture);
95 | equal('-12.30', '-12.30', fixture);
96 | });
97 |
98 | it('allowNegativeNumber to separator', () => {
99 | component.mask.set('separator');
100 | component.allowNegativeNumbers.set(true);
101 |
102 | equal('-101', '-101', fixture);
103 | equal('-100', '-100', fixture);
104 | equal('-999', '-999', fixture);
105 | equal('-3000', '-3 000', fixture);
106 |
107 | component.mask.set('separator.2');
108 | component.allowNegativeNumbers.set(true);
109 |
110 | equal('-100.00', '-100.00', fixture);
111 | equal('-100.02', '-100.02', fixture);
112 | equal('-999', '-999', fixture);
113 | equal('-44.10', '-44.10', fixture);
114 | equal('-91.11', '-91.11', fixture);
115 | equal('-1112.30', '-1 112.30', fixture);
116 |
117 | component.mask.set('separator.3');
118 | component.allowNegativeNumbers.set(true);
119 |
120 | equal('-100.000', '-100.000', fixture);
121 | equal('-99.001', '-99.001', fixture);
122 | equal('-999', '-999', fixture);
123 | equal('-99.100', '-99.100', fixture);
124 | equal('-11.110', '-11.110', fixture);
125 | equal('-12.301', '-12.301', fixture);
126 | });
127 | });
128 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/clear-if-not-match-the-mask.spec.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentFixture } from '@angular/core/testing';
2 | import { TestBed } from '@angular/core/testing';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 | import { TestMaskComponent } from './utils/test-component.component';
5 | import { equal } from './utils/test-functions.component';
6 | import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
7 |
8 | describe('Directive: Mask', () => {
9 | let fixture: ComponentFixture;
10 | let component: TestMaskComponent;
11 |
12 | beforeEach(() => {
13 | TestBed.configureTestingModule({
14 | imports: [ReactiveFormsModule, NgxMaskDirective, TestMaskComponent],
15 | providers: [provideNgxMask()],
16 | });
17 | fixture = TestBed.createComponent(TestMaskComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should clear if mask is not matched', async () => {
23 | component.mask.set('000.000-00');
24 | component.clearIfNotMatch.set(true);
25 | equal('', '', fixture, true);
26 | equal('2578989', '', fixture, true);
27 | equal('2578989888988', '257.898-98', fixture);
28 | equal('111.111-11', '111.111-11', fixture);
29 | });
30 |
31 | it('should clear if mask is not matched with prefix', async () => {
32 | component.mask.set('000-000-00');
33 | component.prefix.set('+5');
34 | component.clearIfNotMatch.set(true);
35 | equal('', '', fixture, true);
36 | equal('2578989', '', fixture, true);
37 | equal('25789898', '+5257-898-98', fixture);
38 | });
39 |
40 | it('should clear if mask is not matched with another placeholderCharacter *', async () => {
41 | component.mask.set('0000');
42 | component.placeHolderCharacter.set('*');
43 | component.clearIfNotMatch.set(true);
44 | equal('', '', fixture, true);
45 | equal('333', '', fixture, true);
46 | equal('22', '', fixture, true);
47 | equal('2222', '2222', fixture);
48 | });
49 |
50 | it('should clear if mask is not matched with another placeholderCharacter X', async () => {
51 | component.mask.set('00000');
52 | component.placeHolderCharacter.set('X');
53 | component.clearIfNotMatch.set(true);
54 | equal('', '', fixture, true);
55 | equal('333', '', fixture, true);
56 | equal('22', '', fixture, true);
57 | equal('2222', '', fixture, true);
58 | equal('12345', '12345', fixture);
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/complete-mask.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component, signal } from '@angular/core';
2 | import type { ComponentFixture } from '@angular/core/testing';
3 | import { TestBed } from '@angular/core/testing';
4 | import { FormControl, ReactiveFormsModule } from '@angular/forms';
5 | import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
6 |
7 | @Component({
8 | selector: 'jsdaddy-open-source-test',
9 | standalone: true,
10 | imports: [ReactiveFormsModule, NgxMaskDirective],
11 | template: ` `,
12 | })
13 | class TestMaskComponent {
14 | public form: FormControl = new FormControl('');
15 |
16 | public isMaskFilled = signal(false);
17 |
18 | public maskFilled(): void {
19 | this.isMaskFilled.set(true);
20 | }
21 | }
22 |
23 | describe('Directive: Mask (Function maskFilled)', () => {
24 | let fixture: ComponentFixture;
25 | let component: TestMaskComponent;
26 | let maskFilledSpy: jasmine.Spy;
27 |
28 | beforeEach(() => {
29 | TestBed.configureTestingModule({
30 | imports: [ReactiveFormsModule, NgxMaskDirective, TestMaskComponent],
31 | providers: [provideNgxMask()],
32 | });
33 | fixture = TestBed.createComponent(TestMaskComponent);
34 | component = fixture.componentInstance;
35 | fixture.detectChanges();
36 | maskFilledSpy = spyOn(component, 'maskFilled').and.callThrough();
37 | });
38 |
39 | it('should call function maskFilled and isMaskFilled should be true', () => {
40 | const inputElement: HTMLInputElement = fixture.nativeElement.querySelector('input');
41 |
42 | inputElement.value = '9999';
43 | inputElement.dispatchEvent(new Event('input'));
44 | fixture.detectChanges();
45 |
46 | expect(component.isMaskFilled()).toBeTrue();
47 | expect(maskFilledSpy).toHaveBeenCalledOnceWith();
48 | });
49 |
50 | it('isMaskFilled should be false', () => {
51 | const inputElement: HTMLInputElement = fixture.nativeElement.querySelector('input');
52 |
53 | inputElement.value = '999';
54 | inputElement.dispatchEvent(new Event('input'));
55 | fixture.detectChanges();
56 |
57 | expect(component.isMaskFilled()).toBeFalse();
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/copy-paste.spec.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentFixture } from '@angular/core/testing';
2 | import { TestBed } from '@angular/core/testing';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 |
5 | import { TestMaskComponent } from './utils/test-component.component';
6 | import { By } from '@angular/platform-browser';
7 | import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
8 |
9 | describe('Event: paste', () => {
10 | let fixture: ComponentFixture;
11 | let component: TestMaskComponent;
12 |
13 | beforeEach(() => {
14 | TestBed.configureTestingModule({
15 | imports: [ReactiveFormsModule, NgxMaskDirective, TestMaskComponent],
16 | providers: [provideNgxMask()],
17 | });
18 | fixture = TestBed.createComponent(TestMaskComponent);
19 | component = fixture.componentInstance;
20 | fixture.detectChanges();
21 | });
22 |
23 | it('After paste to control cursor should be on the end of input)', () => {
24 | component.mask.set('00 - 0000 - 00000');
25 | fixture.detectChanges();
26 |
27 | const inputDebuggerElement = fixture.debugElement.query(By.css('#mask'));
28 |
29 | const pasteData = new DataTransfer();
30 | pasteData.setData('text', '123456789');
31 | inputDebuggerElement.triggerEventHandler('paste', pasteData);
32 |
33 | inputDebuggerElement.nativeElement.value = pasteData.getData('text/plain');
34 | inputDebuggerElement.triggerEventHandler('input', {
35 | target: inputDebuggerElement.nativeElement,
36 | });
37 |
38 | fixture.detectChanges();
39 |
40 | expect(inputDebuggerElement.nativeElement.value).toBe('12 - 3456 - 789');
41 |
42 | expect(inputDebuggerElement.nativeElement.selectionStart).toBe(15);
43 | });
44 | it('After paste to control cursor should be on the end of input for mask with separator', () => {
45 | component.mask.set('separator.0');
46 | component.thousandSeparator.set(',');
47 | fixture.detectChanges();
48 |
49 | const inputDebuggerElement = fixture.debugElement.query(By.css('#mask'));
50 |
51 | const pasteData = new DataTransfer();
52 | pasteData.setData('text', '1234567');
53 | inputDebuggerElement.triggerEventHandler('paste', pasteData);
54 |
55 | inputDebuggerElement.nativeElement.value = pasteData.getData('text/plain');
56 | inputDebuggerElement.triggerEventHandler('input', {
57 | target: inputDebuggerElement.nativeElement,
58 | });
59 |
60 | fixture.detectChanges();
61 |
62 | expect(inputDebuggerElement.nativeElement.value).toBe('1,234,567');
63 |
64 | expect(inputDebuggerElement.nativeElement.selectionStart).toBe(9);
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/custom-date.spec.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentFixture } from '@angular/core/testing';
2 | import { TestBed } from '@angular/core/testing';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 | import { TestMaskComponent } from './utils/test-component.component';
5 | import { equal } from './utils/test-functions.component';
6 | import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
7 |
8 | describe('Directive: Mask (Custom date)', () => {
9 | let fixture: ComponentFixture;
10 | let component: TestMaskComponent;
11 |
12 | beforeEach(() => {
13 | TestBed.configureTestingModule({
14 | imports: [ReactiveFormsModule, NgxMaskDirective, TestMaskComponent],
15 | providers: [provideNgxMask()],
16 | });
17 | fixture = TestBed.createComponent(TestMaskComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('repeat mask', () => {
23 | component.mask.set('d0/m0/0000');
24 |
25 | equal('18', '18', fixture);
26 | equal('11111111', '11/11/1111', fixture);
27 | });
28 |
29 | it('should keep the cursor position after deleting a character', () => {
30 | // Set the initial input value and trigger an input event
31 | const inputElement = fixture.nativeElement.querySelector('input');
32 | component.mask.set('Hh:m0:s0');
33 | inputElement.value = '12:34:56';
34 | inputElement.dispatchEvent(new Event('input'));
35 | fixture.detectChanges();
36 |
37 | inputElement.setSelectionRange(3, 3);
38 |
39 | inputElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete' }));
40 | fixture.detectChanges();
41 |
42 | expect(inputElement.selectionStart).toBe(3);
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/custom-symbol-regexp.spec.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentFixture } from '@angular/core/testing';
2 | import { TestBed } from '@angular/core/testing';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 |
5 | import { TestMaskComponent } from './utils/test-component.component';
6 | import { equal } from './utils/test-functions.component';
7 | import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
8 |
9 | describe('Directive: Mask', () => {
10 | let fixture: ComponentFixture;
11 | let component: TestMaskComponent;
12 |
13 | beforeEach(() => {
14 | TestBed.configureTestingModule({
15 | imports: [ReactiveFormsModule, NgxMaskDirective, TestMaskComponent],
16 | providers: [provideNgxMask()],
17 | });
18 | fixture = TestBed.createComponent(TestMaskComponent);
19 | component = fixture.componentInstance;
20 | fixture.detectChanges();
21 | });
22 |
23 | it('custom patterns', () => {
24 | component.mask.set('00*.00');
25 | equal('22222.333333', '22222.33', fixture);
26 | equal('22212323232', '22212323232', fixture);
27 | });
28 |
29 | it('custom with symbols Á, á', () => {
30 | const testPattern = {
31 | S: { pattern: new RegExp('[A-Za-z-Áá]') },
32 | };
33 | component.mask.set('S*');
34 | component.patterns.set(testPattern);
35 | equal('Fernándos', 'Fernándos', fixture);
36 | equal('Ánton', 'Ánton', fixture);
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/default-config.spec.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentFixture } from '@angular/core/testing';
2 | import { TestBed } from '@angular/core/testing';
3 | import { ReactiveFormsModule, FormControl } from '@angular/forms';
4 | import { TestMaskComponent } from './utils/test-component.component';
5 | import { provideEnvironmentNgxMask, NgxMaskDirective } from 'ngx-mask';
6 | import type { NgxMaskOptions } from 'ngx-mask';
7 | import { By } from '@angular/platform-browser';
8 |
9 | function createComponentWithDefaultConfig(
10 | defaultConfig?: NgxMaskOptions
11 | ): ComponentFixture {
12 | TestBed.configureTestingModule({
13 | imports: [ReactiveFormsModule, NgxMaskDirective, TestMaskComponent],
14 | providers: [provideEnvironmentNgxMask(defaultConfig)],
15 | });
16 | const fixture = TestBed.createComponent(TestMaskComponent);
17 | return fixture;
18 | }
19 |
20 | describe('Default config', () => {
21 | it('default config - decimalMarker and thousandSeparator', () => {
22 | const fixture = createComponentWithDefaultConfig({
23 | decimalMarker: ',',
24 | thousandSeparator: '.',
25 | });
26 | const component = fixture.componentInstance;
27 | component.mask.set('separator');
28 | component.form = new FormControl(1234.56);
29 | fixture.detectChanges();
30 | fixture.whenRenderingDone().then(() => {
31 | expect(fixture.nativeElement.querySelector('input').value).toBe('1.234,56');
32 | });
33 | });
34 |
35 | it('default config - decimalMarker and thousandSeparator', () => {
36 | const fixture = createComponentWithDefaultConfig({
37 | decimalMarker: '.',
38 | thousandSeparator: ' ',
39 | });
40 | const component = fixture.componentInstance;
41 | component.mask.set('separator');
42 | component.form = new FormControl(1234.56);
43 | fixture.detectChanges();
44 | fixture.whenRenderingDone().then(() => {
45 | expect(fixture.nativeElement.querySelector('input').value).toBe('1 234.56');
46 | });
47 | });
48 |
49 | it('default config overriden - decimalMarker and thousandSeparator', () => {
50 | const fixture = createComponentWithDefaultConfig({
51 | decimalMarker: '.',
52 | thousandSeparator: ' ',
53 | });
54 | const component = fixture.componentInstance;
55 | component.mask.set('separator');
56 | // Override default decimalMarker and thousandSeparator
57 | component.decimalMarker.set(',');
58 | component.thousandSeparator.set('.');
59 | component.form = new FormControl(1234.56);
60 | component.specialCharacters.set(['/']); // Explicit set needed to prevent bug in ngx-mask.directive.ts OnChanges event (if specialCharacters is undefined, OnChanges function will return prematurely and won't apply provided thousandSeparator and decimalMarker)
61 | fixture.detectChanges();
62 | fixture.whenRenderingDone().then(() => {
63 | expect(fixture.nativeElement.querySelector('input').value).toBe('1.234,56');
64 | });
65 | });
66 |
67 | it('default config overriden - decimalMarker and thousandSeparator and leadZero', () => {
68 | const fixture = createComponentWithDefaultConfig({
69 | thousandSeparator: '.',
70 | decimalMarker: ',',
71 | leadZero: true,
72 | separatorLimit: '100',
73 | });
74 | const component = fixture.componentInstance;
75 | component.mask.set('separator.2');
76 |
77 | component.form = new FormControl(123);
78 | fixture.detectChanges();
79 | fixture.whenRenderingDone().then(() => {
80 | expect(fixture.nativeElement.querySelector('input').value).toBe('123,00');
81 | });
82 | });
83 |
84 | it('default config overriden - decimalMarker and thousandSeparator and leadZero and suffix', () => {
85 | const fixture = createComponentWithDefaultConfig({
86 | suffix: ' €',
87 | thousandSeparator: ' ',
88 | decimalMarker: ',',
89 | leadZero: true,
90 | });
91 | const component = fixture.componentInstance;
92 | component.mask.set('separator.2');
93 |
94 | component.form = new FormControl(15000.33);
95 | fixture.detectChanges();
96 | fixture.whenRenderingDone().then(() => {
97 | expect(fixture.nativeElement.querySelector('input').value).toBe('15 000,33 €');
98 | });
99 | });
100 |
101 | it('default config - triggerOnMaskChange', async () => {
102 | const fixture = createComponentWithDefaultConfig({
103 | triggerOnMaskChange: true,
104 | });
105 | const component = fixture.componentInstance;
106 | component.mask.set('');
107 | fixture.detectChanges();
108 |
109 | component.form.setValue('7912345678');
110 | fixture.detectChanges();
111 | await fixture.whenStable();
112 | let inputEl = fixture.debugElement.query(By.css('input'));
113 | expect(inputEl.nativeElement.value).toEqual('7912345678');
114 | expect(component.form.value).toEqual('7912345678');
115 |
116 | component.mask.set('00 000 00 00');
117 | fixture.detectChanges();
118 | await fixture.whenStable();
119 | inputEl = fixture.debugElement.query(By.css('input'));
120 | expect(inputEl.nativeElement.value).toEqual('79 123 45 67');
121 | expect(component.form.value).toEqual('791234567');
122 | });
123 |
124 | it('default config overridden - triggerOnMaskChange', async () => {
125 | const fixture = createComponentWithDefaultConfig({
126 | triggerOnMaskChange: false,
127 | });
128 | const component = fixture.componentInstance;
129 | // Override default triggerOnMaskChange
130 | component.triggerOnMaskChange.set(true);
131 | component.mask.set('');
132 | fixture.detectChanges();
133 |
134 | component.form.setValue('7912345678');
135 | fixture.detectChanges();
136 | await fixture.whenStable();
137 | let inputEl = fixture.debugElement.query(By.css('input'));
138 | expect(inputEl.nativeElement.value).toEqual('7912345678');
139 | expect(component.form.value).toEqual('7912345678');
140 |
141 | component.mask.set('00 000 00 00');
142 | fixture.detectChanges();
143 | await fixture.whenStable();
144 | inputEl = fixture.debugElement.query(By.css('input'));
145 | expect(inputEl.nativeElement.value).toEqual('79 123 45 67');
146 | expect(component.form.value).toEqual('791234567');
147 | });
148 | });
149 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/emit-events.cy-spec.ts:
--------------------------------------------------------------------------------
1 | import { CypressTestMaskComponent } from './utils/cypress-test-component.component';
2 | import { signal } from '@angular/core';
3 |
4 | describe('Directive: Mask (emit-events)', () => {
5 | it('should emit event only when mask is correct', () => {
6 | cy.mount(CypressTestMaskComponent, {
7 | componentProperties: {
8 | mask: signal('00 00 00'),
9 | },
10 | });
11 |
12 | cy.get('#masked').type('1dd').type('dd');
13 | cy.get('#pre').should('have.text', '2');
14 |
15 | cy.get('#masked').type('121212').type('dd');
16 | cy.get('#pre').should('have.text', '7');
17 | });
18 |
19 | it('should emit event only when mask is correct with hiddenINput', () => {
20 | cy.mount(CypressTestMaskComponent, {
21 | componentProperties: {
22 | mask: signal('XX-XX-XX'),
23 | hiddenInput: signal(true),
24 | },
25 | });
26 |
27 | cy.get('#masked').type('1dd').type('dd');
28 | cy.get('#pre').should('have.text', '2');
29 |
30 | cy.get('#masked').type('121212').type('dd');
31 | cy.get('#pre').should('have.text', '7');
32 | });
33 |
34 | it('should add trailing zero when mask="separator.1" and leadZero="true"', () => {
35 | cy.mount(CypressTestMaskComponent, {
36 | componentProperties: {
37 | mask: signal('separator.1'),
38 | leadZero: signal(true),
39 | },
40 | });
41 |
42 | cy.get('#masked').type('9').blur().should('have.value', '9.0');
43 | });
44 |
45 | it('should keep trailing decimal when mask="separator.1" and leadZero="true"', () => {
46 | cy.mount(CypressTestMaskComponent, {
47 | componentProperties: {
48 | mask: signal('separator.1'),
49 | leadZero: signal(true),
50 | },
51 | });
52 |
53 | cy.get('#masked').type('7.7').blur().should('have.value', '7.7');
54 | });
55 |
56 | it('should emit event only when mask is correct with suffix separator2', () => {
57 | cy.mount(CypressTestMaskComponent, {
58 | componentProperties: {
59 | mask: signal('separator.2'),
60 | leadZero: signal(true),
61 | suffix: signal(' $'),
62 | },
63 | });
64 |
65 | cy.get('#masked').type('10').blur().should('have.value', '10.00 $');
66 | cy.get('#pre').should('have.text', '3');
67 | });
68 |
69 | it('should emit event only when mask is correct with suffix separator.3', () => {
70 | cy.mount(CypressTestMaskComponent, {
71 | componentProperties: {
72 | mask: signal('separator.3'),
73 | leadZero: signal(true),
74 | suffix: signal(' $'),
75 | },
76 | });
77 |
78 | cy.get('#masked').type('10.0').blur().should('have.value', '10.000 $');
79 | cy.get('#pre').should('have.text', '5');
80 | });
81 |
82 | it('should emit event only when mask is correct with separator2', () => {
83 | cy.mount(CypressTestMaskComponent, {
84 | componentProperties: {
85 | mask: signal('separator.2'),
86 | leadZero: signal(true),
87 | },
88 | });
89 |
90 | cy.get('#masked').type('10').blur().should('have.value', '10.00');
91 | cy.get('#pre').should('have.text', '3');
92 | });
93 |
94 | it('should emit event only when mask is correct with separator.3', () => {
95 | cy.mount(CypressTestMaskComponent, {
96 | componentProperties: {
97 | mask: signal('separator.3'),
98 | leadZero: signal(true),
99 | },
100 | });
101 |
102 | cy.get('#masked').type('10').blur().should('have.value', '10.000');
103 | cy.get('#pre').should('have.text', '3');
104 | });
105 |
106 | it('should emit event only when mask is correct with SS000', () => {
107 | cy.mount(CypressTestMaskComponent, {
108 | componentProperties: {
109 | mask: signal('SS000'),
110 | },
111 | });
112 |
113 | cy.get('#masked').type('SS11111DDDD11').blur().should('have.value', 'SS111');
114 | cy.get('#pre').should('have.text', '6');
115 | });
116 |
117 | it("inputTransformFn should not break if it's null", () => {
118 | cy.mount(CypressTestMaskComponent, {
119 | componentProperties: {
120 | mask: signal('00000||00000-0000'),
121 | inputTransformFn: signal(null),
122 | },
123 | });
124 |
125 | cy.get('#masked').type('123456789').blur().should('have.value', '12345-6789');
126 | cy.get('#pre').should('have.text', '10');
127 | });
128 |
129 | it('inputTransformFn should not change input form status', () => {
130 | cy.mount(CypressTestMaskComponent, {
131 | componentProperties: {
132 | mask: signal('Hh:m0'),
133 | dropSpecialCharacters: signal(false),
134 | inputTransformFn: signal((value: unknown): string => {
135 | if (typeof value !== 'object') {
136 | return String(value);
137 | }
138 | const date = new Date('12-12-2012 11:14:01');
139 | return `${String(date.getHours()).padStart(2, '0')}${String(
140 | date.getMinutes()
141 | ).padStart(2, '0')}`;
142 | }),
143 | outputTransformFn: signal((value: string | number | undefined | null) => {
144 | if (value) {
145 | const fetch = new Date('12-12-2012 11:14:01');
146 | const values = String(value).split(':');
147 | if (values.length >= 2) {
148 | const hour = Number(values[0]);
149 | const minuts = Number(values[1]);
150 | fetch.setHours(hour);
151 | fetch.setMinutes(minuts);
152 | }
153 | fetch.setSeconds(0);
154 | return fetch.toString();
155 | }
156 | return;
157 | }),
158 | },
159 | })
160 | .wait(10)
161 | .then((wrapper) => {
162 | wrapper.component.form.reset();
163 | });
164 |
165 | cy.get('#pristine').should('have.text', 'true');
166 | cy.get('#pre').should('have.text', '2');
167 | });
168 | });
169 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/export-as.spec.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentFixture } from '@angular/core/testing';
2 | import { TestBed } from '@angular/core/testing';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 |
5 | import { Component, viewChild } from '@angular/core';
6 | import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
7 |
8 | @Component({
9 | selector: 'jsdaddy-open-source-test',
10 | standalone: true,
11 | imports: [NgxMaskDirective],
12 | template: ` `,
13 | })
14 | export class TestMaskComponent {
15 | public refNgxMask = viewChild('refNgxMask');
16 | public refMask = viewChild('refMask');
17 | }
18 |
19 | describe('Directive: Mask export As', () => {
20 | let fixture: ComponentFixture;
21 | let component: TestMaskComponent;
22 |
23 | beforeEach(() => {
24 | TestBed.configureTestingModule({
25 | imports: [ReactiveFormsModule, NgxMaskDirective, TestMaskComponent],
26 | providers: [provideNgxMask()],
27 | });
28 | fixture = TestBed.createComponent(TestMaskComponent);
29 | component = fixture.componentInstance;
30 | fixture.detectChanges();
31 | });
32 |
33 | it('should export directive instance with exportAs api by name equal mask', () => {
34 | expect(component.refMask()).toBeInstanceOf(NgxMaskDirective);
35 | });
36 | it('should export directive instance with exportAs api by name equal ngxMask', () => {
37 | expect(component.refNgxMask()).toBeInstanceOf(NgxMaskDirective);
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/forms.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import type { ComponentFixture } from '@angular/core/testing';
3 | import { TestBed } from '@angular/core/testing';
4 | import { FormControl, ReactiveFormsModule } from '@angular/forms';
5 | import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
6 |
7 | @Component({
8 | selector: 'jsdaddy-open-source-test',
9 | imports: [ReactiveFormsModule, NgxMaskDirective],
10 | template: ``,
11 | })
12 | class TestMaskComponent {
13 | public form: FormControl = new FormControl('');
14 | }
15 |
16 | describe('Directive: Forms', () => {
17 | let fixture: ComponentFixture;
18 | let component: TestMaskComponent;
19 |
20 | beforeEach(() => {
21 | TestBed.configureTestingModule({
22 | imports: [NgxMaskDirective],
23 | providers: [provideNgxMask()],
24 | });
25 | fixture = TestBed.createComponent(TestMaskComponent);
26 | component = fixture.componentInstance;
27 | fixture.detectChanges();
28 | });
29 |
30 | it('should propagate masked value to the form control value', () => {
31 | component.form.setValue('A1234Z');
32 | expect(component.form.value).toBe('1234');
33 | });
34 |
35 | it('should propagate masked value to the form control valueChanges observable', () => {
36 | component.form.valueChanges.subscribe((newValue) => {
37 | expect(newValue).toEqual('1234');
38 | });
39 |
40 | component.form.setValue('A1234Z');
41 | });
42 |
43 | it('should mask values when multiple calls to setValue() are made', () => {
44 | component.form.setValue('A1234Z');
45 | expect(component.form.value).toBe('1234');
46 | component.form.setValue('A1234Z');
47 | expect(component.form.value).toBe('1234');
48 | component.form.setValue('A1234Z');
49 | expect(component.form.value).toBe('1234');
50 | });
51 |
52 | it('should propagate masked value to the form control valueChanges observable when multiple calls to setValue() are made', () => {
53 | component.form.valueChanges.subscribe((newValue) => {
54 | expect(newValue).toEqual('1234');
55 | });
56 |
57 | component.form.setValue('A1234Z');
58 | component.form.setValue('A1234Z');
59 | component.form.setValue('A1234Z');
60 | });
61 |
62 | it('should not emit to valueChanges if the masked value has not changed with emitEvent: true', () => {
63 | let emissionsToValueChanges = 0;
64 |
65 | component.form.valueChanges.subscribe(() => {
66 | emissionsToValueChanges++;
67 | });
68 |
69 | component.form.setValue('1234', { emitEvent: true });
70 | component.form.setValue('1234', { emitEvent: true });
71 |
72 | // Expect to emit 3 times, once for the first setValue() call, once by ngx-mask, and once for the second setValue() call.
73 | // There is not fourth emission for when ngx-mask masks the value for a second time.
74 | expect(emissionsToValueChanges).toBe(3);
75 | });
76 |
77 | it('should not emit to valueChanges if the masked value has not changed with emitEvent: false', () => {
78 | let emissionsToValueChanges = 0;
79 |
80 | component.form.valueChanges.subscribe(() => {
81 | emissionsToValueChanges++;
82 | });
83 |
84 | component.form.setValue('1234', { emitEvent: false });
85 | component.form.setValue('1234', { emitEvent: false });
86 |
87 | // Expect to only have emitted once, only by ngx-mask.
88 | // There is no second emission for when ngx-mask masks the value for a second time.
89 | expect(emissionsToValueChanges).toBe(1);
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/separator-non-en-locale.spec.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentFixture } from '@angular/core/testing';
2 | import { fakeAsync, TestBed, tick } from '@angular/core/testing';
3 | import type { DebugElement } from '@angular/core';
4 | import { LOCALE_ID } from '@angular/core';
5 | import { ReactiveFormsModule } from '@angular/forms';
6 | import { TestMaskComponent } from './utils/test-component.component';
7 | import { equal, typeTest } from './utils/test-functions.component';
8 | import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
9 | import { By } from '@angular/platform-browser';
10 |
11 | // FR locale uses comma as decimal marker
12 | describe('Separator: Mask with FR locale', () => {
13 | let fixture: ComponentFixture;
14 | let component: TestMaskComponent;
15 |
16 | beforeEach(() => {
17 | TestBed.configureTestingModule({
18 | imports: [ReactiveFormsModule, NgxMaskDirective, TestMaskComponent],
19 | providers: [provideNgxMask(), { provide: LOCALE_ID, useValue: 'fr' }],
20 | });
21 | fixture = TestBed.createComponent(TestMaskComponent);
22 | component = fixture.componentInstance;
23 | fixture.detectChanges();
24 | });
25 |
26 | it('Should work right when reset decimalMarker', () => {
27 | component.mask.set('separator.2');
28 | component.decimalMarker.set('.');
29 | equal('1000000.00', '1 000 000.00', fixture);
30 | });
31 |
32 | it('separator precision 2 with thousandSeparator (.) decimalMarker (,) for 12345.67', () => {
33 | component.mask.set('separator.2');
34 | component.thousandSeparator.set(',');
35 | component.decimalMarker.set('.');
36 | equal('12,345.67', '12,345.67', fixture);
37 | });
38 |
39 | it('separator precision 2 with thousandSeparator (.) decimalMarker (,) for 12345.67', () => {
40 | component.mask.set('separator.2');
41 | component.thousandSeparator.set(',');
42 | component.decimalMarker.set('.');
43 | equal('12345.67', '12,345.67', fixture);
44 | });
45 |
46 | it('check formControl value to be number when decimalMarker is dot', () => {
47 | component.mask.set('separator.2');
48 | component.thousandSeparator.set(' ');
49 | component.decimalMarker.set('.');
50 |
51 | typeTest('12 345.67', fixture);
52 | expect(component.form.value).toBe('12345.67');
53 | });
54 |
55 | it('check formControl value to be number when decimalMarker is array', () => {
56 | component.mask.set('separator.2');
57 | component.thousandSeparator.set(' ');
58 | component.decimalMarker.set(['.', ',']);
59 |
60 | typeTest('12 345,67', fixture);
61 | expect(component.form.value).toBe('12345.67');
62 |
63 | typeTest('123 456.78', fixture);
64 | expect(component.form.value).toBe('123456.78');
65 | });
66 |
67 | it('should show - at input', fakeAsync(() => {
68 | component.mask.set('separator.2');
69 | component.thousandSeparator.set(' ');
70 | component.decimalMarker.set(',');
71 | component.allowNegativeNumbers.set(true);
72 | const debugElement: DebugElement = fixture.debugElement.query(By.css('input'));
73 | const inputTarget: HTMLInputElement = debugElement.nativeElement as HTMLInputElement;
74 | spyOnProperty(document, 'activeElement').and.returnValue(inputTarget);
75 | fixture.detectChanges();
76 |
77 | component.form.setValue(-78);
78 | tick();
79 | expect(inputTarget.value).toBe('-78');
80 | equal('-78', '-78', fixture);
81 | }));
82 | });
83 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/show-mask-typed.cy-spec.ts:
--------------------------------------------------------------------------------
1 | import { CypressTestMaskComponent } from './utils/cypress-test-component.component';
2 | import { signal } from '@angular/core';
3 |
4 | describe('Directive: Mask (Delete)', () => {
5 | it('should place cursor in right place mask (000) 000-0000', () => {
6 | cy.mount(CypressTestMaskComponent, {
7 | componentProperties: {
8 | mask: signal('(000) 000-0000'),
9 | showMaskTyped: signal(true),
10 | },
11 | });
12 |
13 | cy.get('#masked').click().should('have.prop', 'selectionStart', 1);
14 | });
15 |
16 | it('should place cursor in right place mask ((000)) 000-0000', () => {
17 | cy.mount(CypressTestMaskComponent, {
18 | componentProperties: {
19 | mask: signal('((000)) 000-0000'),
20 | showMaskTyped: signal(true),
21 | },
22 | });
23 |
24 | cy.get('#masked').click().should('have.prop', 'selectionStart', 2);
25 | });
26 |
27 | it('should place cursor in right place mask 000 000-0000', () => {
28 | cy.mount(CypressTestMaskComponent, {
29 | componentProperties: {
30 | mask: signal('000 000-0000'),
31 | showMaskTyped: signal(true),
32 | },
33 | });
34 |
35 | cy.get('#masked').click().should('have.prop', 'selectionStart', 0);
36 | });
37 |
38 | it('should place cursor in right place mask (000) 000-0000', () => {
39 | cy.mount(CypressTestMaskComponent, {
40 | componentProperties: {
41 | mask: signal('(000) 000-0000'),
42 | showMaskTyped: signal(true),
43 | prefix: signal('+380 '),
44 | },
45 | });
46 |
47 | cy.get('#masked').click().should('have.prop', 'selectionStart', 6);
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/test-sufix.spec.ts:
--------------------------------------------------------------------------------
1 | import type { DebugElement } from '@angular/core';
2 | import type { ComponentFixture } from '@angular/core/testing';
3 | import { TestBed } from '@angular/core/testing';
4 | import { ReactiveFormsModule } from '@angular/forms';
5 | import { By } from '@angular/platform-browser';
6 | import { TestMaskComponent } from './utils/test-component.component';
7 | import { equal } from './utils/test-functions.component';
8 | import { provideNgxMask, NgxMaskDirective } from 'ngx-mask';
9 |
10 | describe('Directive: Mask (Suffix)', () => {
11 | let fixture: ComponentFixture;
12 | let component: TestMaskComponent;
13 |
14 | beforeEach(() => {
15 | TestBed.configureTestingModule({
16 | imports: [ReactiveFormsModule, NgxMaskDirective, TestMaskComponent],
17 | providers: [provideNgxMask()],
18 | });
19 | fixture = TestBed.createComponent(TestMaskComponent);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | });
23 |
24 | it('should clear if not match the mask!!!!', () => {
25 | component.mask.set('0000');
26 | component.suffix.set(' $');
27 | equal('', '', fixture);
28 | equal('123', '123 $', fixture);
29 | equal('1234', '1234 $', fixture);
30 | });
31 |
32 | it('should clear if not match the mask!!!!', () => {
33 | component.mask.set('0*.00');
34 | component.suffix.set(' $');
35 | equal('', '', fixture);
36 | equal('12345', '12345 $', fixture);
37 | equal('12344.44', '12344.44 $', fixture);
38 | });
39 |
40 | it('should work correct with suffix .com', () => {
41 | component.mask.set('0000');
42 | component.suffix.set('.com');
43 | equal('', '', fixture);
44 | equal('12', '12.com', fixture);
45 | equal('12344', '1234.com', fixture);
46 | equal('1234.co7m', '1234.com', fixture);
47 | equal('123.co4m', '1234.com', fixture);
48 | });
49 |
50 | it('separator should work correct with suffix', () => {
51 | component.mask.set('separator');
52 | component.suffix.set('$');
53 | equal('', '', fixture);
54 | equal('123', '123$', fixture);
55 | equal('1234', '1 234$', fixture);
56 | });
57 | it('should not delete suffix', () => {
58 | component.mask.set('0000');
59 | component.suffix.set('$');
60 | const debugElement: DebugElement = fixture.debugElement.query(By.css('input'));
61 | const inputTarget: HTMLInputElement = debugElement.nativeElement as HTMLInputElement;
62 | spyOnProperty(document, 'activeElement').and.returnValue(inputTarget);
63 | fixture.detectChanges();
64 |
65 | inputTarget.value = '5678$';
66 | inputTarget.selectionStart = 5;
67 | inputTarget.selectionEnd = 5;
68 | debugElement.triggerEventHandler('keydown', {
69 | code: 'Backspace',
70 | key: 'Backspace',
71 | keyCode: 8,
72 | target: inputTarget,
73 | });
74 | debugElement.triggerEventHandler('input', { target: inputTarget });
75 |
76 | expect(inputTarget.value).toBe('5678$');
77 | expect(inputTarget.selectionStart).toEqual(4);
78 | });
79 | it('should delete all if value and part of suffix are deleted', () => {
80 | component.mask.set('A*');
81 | component.suffix.set(' test');
82 | const debugElement: DebugElement = fixture.debugElement.query(By.css('input'));
83 | const inputTarget: HTMLInputElement = debugElement.nativeElement as HTMLInputElement;
84 | spyOnProperty(document, 'activeElement').and.returnValue(inputTarget);
85 | fixture.detectChanges();
86 |
87 | inputTarget.value = '10 test';
88 | debugElement.triggerEventHandler('input', { target: inputTarget });
89 |
90 | expect(inputTarget.value).toBe('10 test');
91 |
92 | inputTarget.value = 'st';
93 | debugElement.triggerEventHandler('input', { target: inputTarget });
94 |
95 | expect(inputTarget.value).toBe('');
96 | });
97 | it('should not delete suffix', () => {
98 | component.mask.set('A{5}');
99 | component.suffix.set('.com');
100 | const debugElement: DebugElement = fixture.debugElement.query(By.css('input'));
101 | const inputTarget: HTMLInputElement = debugElement.nativeElement as HTMLInputElement;
102 | spyOnProperty(document, 'activeElement').and.returnValue(inputTarget);
103 | fixture.detectChanges();
104 |
105 | inputTarget.value = 'qwert.com';
106 | inputTarget.selectionStart = 10;
107 | inputTarget.selectionEnd = 10;
108 | debugElement.triggerEventHandler('keydown', {
109 | code: 'Backspace',
110 | key: 'Backspace',
111 | keyCode: 8,
112 | target: inputTarget,
113 | });
114 | debugElement.triggerEventHandler('input', { target: inputTarget });
115 |
116 | expect(inputTarget.value).toBe('qwert.com');
117 | expect(inputTarget.selectionStart).toEqual(5);
118 | });
119 | });
120 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/trigger-on-mask-change.cy-spec.ts:
--------------------------------------------------------------------------------
1 | import { CypressTestTriggerOnMaskChangeComponent } from './utils/cypress-test-trigger-on-mask-change.component';
2 |
3 | describe('Directive: Mask (Trigger on mask change) [Cypress]', () => {
4 | it('should put back initial value if mask is toggled', async () => {
5 | cy.mount(CypressTestTriggerOnMaskChangeComponent);
6 |
7 | cy.get('#masked').type('7912345678').should('have.value', '7912345678');
8 | cy.get('.formvalue').should('have.text', '7912345678');
9 |
10 | cy.get('input[value="ch"]').click();
11 | cy.get('input#masked').should('have.value', '79 123 45 67');
12 | cy.get('.formvalue').should('have.text', '791234567');
13 |
14 | cy.get('input[value="de"]').click();
15 | cy.get('input#masked').should('have.value', '7912345678');
16 | cy.get('.formvalue').should('have.text', '7912345678');
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/trigger-on-mask-change.spec.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentFixture } from '@angular/core/testing';
2 | import { TestBed } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 | import { TestMaskComponent } from './utils/test-component.component';
5 | import { ReactiveFormsModule } from '@angular/forms';
6 | import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
7 | import type { DebugElement } from '@angular/core';
8 | import { equal } from './utils/test-functions.component';
9 |
10 | describe('Directive: Mask (Trigger on mask change)', () => {
11 | let fixture: ComponentFixture;
12 | let component: TestMaskComponent;
13 |
14 | beforeEach(() => {
15 | TestBed.configureTestingModule({
16 | imports: [ReactiveFormsModule, NgxMaskDirective, TestMaskComponent],
17 | providers: [provideNgxMask()],
18 | });
19 | fixture = TestBed.createComponent(TestMaskComponent);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | });
23 |
24 | afterEach(() => {
25 | fixture.destroy();
26 | });
27 |
28 | it('should trigger form value update if mask is changed when triggerOnMaskChange is true', async () => {
29 | component.mask.set('');
30 | component.triggerOnMaskChange.set(true);
31 | fixture.detectChanges();
32 |
33 | component.form.setValue('7912345678');
34 | fixture.detectChanges();
35 | await fixture.whenStable();
36 | let inputEl = fixture.debugElement.query(By.css('input'));
37 | expect(inputEl.nativeElement.value).toEqual('7912345678');
38 | expect(component.form.value).toEqual('7912345678');
39 |
40 | component.mask.set('00 000 00 00');
41 | fixture.detectChanges();
42 | await fixture.whenStable();
43 | inputEl = fixture.debugElement.query(By.css('input'));
44 | expect(inputEl.nativeElement.value).toEqual('79 123 45 67');
45 | expect(component.form.value).toEqual('791234567');
46 | });
47 |
48 | it('should not trigger form value update if mask is changed when triggerOnMaskChange is false', async () => {
49 | component.mask.set('');
50 | component.triggerOnMaskChange.set(false);
51 | fixture.detectChanges();
52 |
53 | component.form.setValue('7912345678');
54 | fixture.detectChanges();
55 | await fixture.whenStable();
56 | let inputEl = fixture.debugElement.query(By.css('input'));
57 | expect(inputEl.nativeElement.value).toEqual('7912345678');
58 | expect(component.form.value).toEqual('7912345678');
59 |
60 | component.mask.set('00 000 00 00');
61 | fixture.detectChanges();
62 | await fixture.whenStable();
63 | inputEl = fixture.debugElement.query(By.css('input'));
64 | expect(inputEl.nativeElement.value).toEqual('79 123 45 67');
65 | expect(component.form.value).toEqual('791234567');
66 | });
67 |
68 | it('should trigger form value update if mask is changed when triggerOnMaskChange is true', async () => {
69 | component.mask.set('00000||00000-0000');
70 | component.triggerOnMaskChange.set(true);
71 | const debugElement: DebugElement = fixture.debugElement.query(By.css('input'));
72 | const inputTarget: HTMLInputElement = debugElement.nativeElement as HTMLInputElement;
73 | spyOnProperty(document, 'activeElement').and.returnValue(inputTarget);
74 | fixture.detectChanges();
75 |
76 | equal('1234', '1234', fixture);
77 | expect(inputTarget.value).toEqual('1234');
78 | expect(component.form.value).toBe('1234');
79 |
80 | component.mask.set('S0S 0S0');
81 | equal(inputTarget.value, '', fixture, true);
82 | expect(component.form.value).toBe('');
83 | });
84 |
85 | it('should not trigger form value update if mask is changed when triggerOnMaskChange is false', async () => {
86 | component.mask.set('00000||00000-0000');
87 | component.triggerOnMaskChange.set(false);
88 | const debugElement: DebugElement = fixture.debugElement.query(By.css('input'));
89 | const inputTarget: HTMLInputElement = debugElement.nativeElement as HTMLInputElement;
90 | spyOnProperty(document, 'activeElement').and.returnValue(inputTarget);
91 | fixture.detectChanges();
92 |
93 | equal('1234', '1234', fixture);
94 | expect(inputTarget.value).toEqual('1234');
95 | expect(component.form.value).toBe('1234');
96 |
97 | component.mask.set('S0S 0S0');
98 | equal(inputTarget.value, '', fixture, true);
99 | expect(component.form.value).toBe('');
100 | });
101 | });
102 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/type-number.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component, signal } from '@angular/core';
2 | import { FormControl, ReactiveFormsModule } from '@angular/forms';
3 | import type { ComponentFixture } from '@angular/core/testing';
4 | import { TestBed } from '@angular/core/testing';
5 | import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
6 | import { equal } from './utils/test-functions.component';
7 |
8 | @Component({
9 | selector: 'jsdaddy-open-source-test',
10 | standalone: true,
11 | imports: [ReactiveFormsModule, NgxMaskDirective],
12 | template: ` `,
13 | })
14 | // eslint-disable-next-line @angular-eslint/component-class-suffix
15 | export class TestTypeNumber {
16 | public form: FormControl = new FormControl('');
17 | public mask = signal('');
18 | }
19 |
20 | describe('Directive: Mask (Trigger on mask change)', () => {
21 | let fixture: ComponentFixture;
22 | let component: TestTypeNumber;
23 |
24 | beforeEach(() => {
25 | TestBed.configureTestingModule({
26 | imports: [ReactiveFormsModule, NgxMaskDirective, TestTypeNumber],
27 | providers: [provideNgxMask()],
28 | });
29 | fixture = TestBed.createComponent(TestTypeNumber);
30 | component = fixture.componentInstance;
31 | fixture.detectChanges();
32 | });
33 |
34 | afterEach(() => {
35 | fixture.destroy();
36 | });
37 |
38 | it('mask 0* should work with mask 0*', () => {
39 | component.mask.set('0*');
40 |
41 | equal('1', '1', fixture);
42 | equal('12', '12', fixture);
43 | equal('123', '123', fixture);
44 | equal('1234', '1234', fixture);
45 | equal('12345', '12345', fixture);
46 | });
47 |
48 | it('mask 0000 should work with mask 0000', () => {
49 | component.mask.set('0000');
50 |
51 | equal('1', '1', fixture);
52 | equal('12', '12', fixture);
53 | equal('123', '123', fixture);
54 | equal('1234', '1234', fixture);
55 | equal('12345', '1234', fixture);
56 | });
57 |
58 | it('mask 0000 should work with mask 0000', () => {
59 | component.mask.set('percent');
60 |
61 | equal('100', '100', fixture);
62 | equal('99', '99', fixture);
63 | });
64 |
65 | it('should be editable with empty mask', () => {
66 | component.mask.set('');
67 |
68 | equal('100', '100', fixture);
69 | equal('99', '99', fixture);
70 | equal('1', '1', fixture);
71 | equal('1324', '1324', fixture);
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/utils/cypress-test-component.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, inject, input } from '@angular/core';
2 | import { FormControl, ReactiveFormsModule } from '@angular/forms';
3 | import { scan, startWith } from 'rxjs';
4 | import type { NgxMaskConfig } from 'ngx-mask';
5 | import { provideNgxMask } from 'ngx-mask';
6 | import { NgxMaskDirective } from 'ngx-mask';
7 | import { NGX_MASK_CONFIG } from 'ngx-mask';
8 | import { toSignal } from '@angular/core/rxjs-interop';
9 |
10 | @Component({
11 | selector: 'jsdaddy-open-source-test',
12 | standalone: true,
13 | imports: [NgxMaskDirective, ReactiveFormsModule],
14 | providers: [provideNgxMask()],
15 | template: `
16 |
37 |
38 | {{ counter$() }}
39 | {{ form.value }}
40 | {{ form.pristine }}
41 |
42 | {{ leadZeroDateTime() }}
43 |
44 | `,
45 | })
46 | export class CypressTestMaskComponent {
47 | protected _config = inject(NGX_MASK_CONFIG);
48 | public mask = input('');
49 |
50 | public hiddenInput = input(this._config.hiddenInput);
51 |
52 | public allowNegativeNumbers = input(
53 | this._config.allowNegativeNumbers
54 | );
55 |
56 | public prefix = input(this._config.prefix);
57 |
58 | public suffix = input(this._config.suffix);
59 |
60 | public leadZero = input(this._config.leadZero);
61 |
62 | public showMaskTyped = input(this._config.showMaskTyped);
63 |
64 | public decimalMarker = input('.');
65 |
66 | public thousandSeparator = input(',');
67 |
68 | public keepCharacterPositions = input(
69 | this._config.keepCharacterPositions
70 | );
71 |
72 | public shownMaskExpression = input(
73 | this._config.shownMaskExpression
74 | );
75 |
76 | public placeHolderCharacter = input(
77 | this._config.placeHolderCharacter
78 | );
79 |
80 | public dropSpecialCharacters = input(
81 | this._config.dropSpecialCharacters
82 | );
83 | public leadZeroDateTime = input(
84 | this._config.leadZeroDateTime
85 | );
86 |
87 | public separatorLimit = input(this._config.separatorLimit);
88 |
89 | public patterns = input(this._config.patterns);
90 |
91 | public specialCharacters = input(
92 | this._config.specialCharacters
93 | );
94 |
95 | public inputTransformFn = input(
96 | this._config.inputTransformFn
97 | );
98 |
99 | public outputTransformFn = input(
100 | this._config.outputTransformFn
101 | );
102 |
103 | public form: FormControl = new FormControl('');
104 |
105 | public readonly counter$ = toSignal(
106 | this.form.valueChanges.pipe(
107 | startWith(0),
108 |
109 | scan((acc) => acc + 1, 0)
110 | )
111 | );
112 | }
113 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/utils/cypress-test-trigger-on-mask-change.component.ts:
--------------------------------------------------------------------------------
1 | import type { OnDestroy, OnInit } from '@angular/core';
2 | import { Component } from '@angular/core';
3 | import { FormControl, ReactiveFormsModule } from '@angular/forms';
4 | import { Subject, takeUntil } from 'rxjs';
5 | import { NgxMaskDirective } from 'ngx-mask';
6 |
7 | @Component({
8 | selector: 'jsdaddy-open-source-test',
9 | standalone: true,
10 | imports: [ReactiveFormsModule, NgxMaskDirective],
11 | styles: [
12 | 'code { border: 1px solid #ddd; background-color: #eee; padding: 0 5px; border-radius: 3px; }',
13 | ],
14 | template: `
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
29 |
30 |
31 | Mask: {{ mask }}
32 |
33 | Form Value: {{ form.value }}
34 |
35 | `,
36 | })
37 | export class CypressTestTriggerOnMaskChangeComponent implements OnInit, OnDestroy {
38 | public mask = '';
39 |
40 | public form: FormControl = new FormControl('');
41 |
42 | public radio: FormControl = new FormControl('de');
43 |
44 | private destroyed = new Subject();
45 |
46 | public ngOnInit(): void {
47 | this.radio.valueChanges.pipe(takeUntil(this.destroyed)).subscribe((value) => {
48 | this.mask = value === 'de' ? '' : '00 000 00 00';
49 | });
50 | }
51 |
52 | public ngOnDestroy(): void {
53 | this.destroyed.next();
54 | this.destroyed.complete();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/utils/test-component.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, inject, signal } from '@angular/core';
2 | import { FormControl, ReactiveFormsModule } from '@angular/forms';
3 | import type { NgxMaskConfig } from 'ngx-mask';
4 | import { NGX_MASK_CONFIG } from 'ngx-mask';
5 | import { NgxMaskDirective } from 'ngx-mask';
6 |
7 | @Component({
8 | selector: 'jsdaddy-open-source-test',
9 | standalone: true,
10 | imports: [ReactiveFormsModule, NgxMaskDirective],
11 | template: `
12 |
38 | `,
39 | })
40 | export class TestMaskComponent {
41 | protected _config = inject(NGX_MASK_CONFIG);
42 |
43 | public form: FormControl = new FormControl();
44 |
45 | public mask = signal('');
46 |
47 | public dropSpecialCharacters = signal(
48 | this._config.dropSpecialCharacters
49 | );
50 | public hiddenInput = signal(this._config.hiddenInput);
51 | public clearIfNotMatch = signal(this._config.clearIfNotMatch);
52 | public specialCharacters = signal(
53 | this._config.specialCharacters
54 | );
55 | public patterns = signal(this._config.patterns);
56 | public prefix = signal(this._config.prefix);
57 | public suffix = signal(this._config.suffix);
58 | public thousandSeparator = signal(
59 | this._config.thousandSeparator
60 | );
61 | public decimalMarker = signal(this._config.decimalMarker);
62 | public showMaskTyped = signal(this._config.showMaskTyped);
63 | public placeHolderCharacter = signal(
64 | this._config.placeHolderCharacter
65 | );
66 | public validation = signal(this._config.validation);
67 | public separatorLimit = signal(this._config.separatorLimit);
68 | public allowNegativeNumbers = signal(
69 | this._config.allowNegativeNumbers
70 | );
71 | public leadZeroDateTime = signal(
72 | this._config.leadZeroDateTime
73 | );
74 | public leadZero = signal(this._config.leadZero);
75 | public apm = signal(this._config.apm);
76 | public inputTransformFn = signal(
77 | this._config.inputTransformFn
78 | );
79 | public outputTransformFn = signal(
80 | this._config.outputTransformFn
81 | );
82 | public keepCharacterPositions = signal(
83 | this._config.keepCharacterPositions
84 | );
85 | public triggerOnMaskChange = signal(
86 | this._config.triggerOnMaskChange
87 | );
88 | public instantPrefix = signal(this._config.instantPrefix);
89 | }
90 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/src/test/utils/test-functions.component.ts:
--------------------------------------------------------------------------------
1 | export const Paste = 'Paste';
2 | export const Type = 'Type';
3 |
4 | export function pasteTest(inputValue: string, fixture: any): string {
5 | fixture.detectChanges();
6 |
7 | fixture.nativeElement.querySelector('input').value = inputValue;
8 |
9 | fixture.nativeElement.querySelector('input').dispatchEvent(new Event('paste'));
10 | fixture.nativeElement.querySelector('input').dispatchEvent(new Event('input'));
11 | fixture.nativeElement.querySelector('input').dispatchEvent(new Event('ngModelChange'));
12 |
13 | return fixture.nativeElement.querySelector('input').value;
14 | }
15 |
16 | export function typeTest(inputValue: string, fixture: any): string {
17 | fixture.detectChanges();
18 | const inputArray = inputValue.split('');
19 | const inputElement = fixture.nativeElement.querySelector('input');
20 |
21 | inputElement.value = '';
22 | inputElement.dispatchEvent(new Event('input'));
23 | inputElement.dispatchEvent(new Event('ngModelChange'));
24 |
25 | {
26 | for (const element of inputArray) {
27 | inputElement.dispatchEvent(new KeyboardEvent('keydown'), { key: element });
28 | if (inputElement.type === 'text') {
29 | const selectionStart = inputElement.selectionStart || 0;
30 | const selectionEnd = inputElement.selectionEnd || 0;
31 | inputElement.value =
32 | inputElement.value.slice(0, selectionStart) +
33 | element +
34 | inputElement.value.slice(selectionEnd);
35 |
36 | inputElement.selectionStart = selectionStart + 1;
37 | } else {
38 | inputElement.value += element;
39 | }
40 | inputElement.dispatchEvent(new Event('input'));
41 | inputElement.dispatchEvent(new Event('ngModelChange'));
42 | }
43 | }
44 | return inputElement.value;
45 | }
46 |
47 | export function equal(
48 | value: string,
49 | expectedValue: string,
50 | fixture: any,
51 | async = false,
52 | testType: typeof Paste | typeof Type = Type
53 | ): void {
54 | if (testType === Paste) {
55 | pasteTest(value, fixture);
56 | } else {
57 | typeTest(value, fixture);
58 | }
59 |
60 | if (async) {
61 | Promise.resolve().then(() => {
62 | expect(fixture.nativeElement.querySelector('input').value).toBe(expectedValue);
63 | });
64 | return;
65 | }
66 | expect(fixture.nativeElement.querySelector('input').value).toBe(expectedValue);
67 | }
68 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/tsconfig.cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "allowSyntheticDefaultImports": true,
5 | "allowJs": true,
6 | "types": ["cypress", "node"]
7 | },
8 | "include": ["src/test/test/*.cy-spec.ts", "cypress/support/**/*.ts", "**/*.cy-spec.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/lib",
5 | "declarationMap": true,
6 | "inlineSources": true,
7 | "types": []
8 | },
9 | "exclude": ["src/test.ts", "**/*.spec.ts", "src/test/**/*.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/projects/ngx-mask-lib/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-mask-lib/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/spec",
5 | "types": ["jasmine", "node"]
6 | },
7 | "include": ["**/*.spec.ts", "**/*.d.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | @apply block h-full;
3 | }
4 |
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | describe('App component', () => {
2 | it('first test', () => {
3 | expect(1).toEqual(1);
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, signal } from '@angular/core';
2 | import { OptDocs, OptExamples } from 'src/assets/content/optional';
3 | import { lists } from 'src/assets/content/lists';
4 | import { SepDocs, SepExamples } from 'src/assets/content/separators';
5 | import { ComDocs, ComExamples } from 'src/assets/content/common-cases';
6 | import { OthDocs, OthExamples } from 'src/assets/content/other';
7 | import { OptionsComponent } from './options/options.component';
8 | import { HeaderComponent } from '@open-source/header/header.component';
9 | import type { ComDoc, ListItem, MaskOptions, TExample } from '@open-source/accordion/content.types';
10 | import { SubHeaderComponent } from '@open-source/sub-header/sub-header.component';
11 | import { AccordionComponent } from '@open-source/accordion/accordion.component';
12 | import { FooterComponent } from '@open-source/footer/footer.component';
13 | import { LinkPath } from '@libraries/link/link.path';
14 | import {
15 | FormatAndParserExamples,
16 | ParserAndFormatterDocs,
17 | } from '../assets/content/parser-and-formatter';
18 | import { VersionToken } from '@libraries/version/version.token';
19 |
20 | declare const VERSION: string;
21 |
22 | @Component({
23 | selector: 'jsdaddy-open-source-root',
24 | templateUrl: './app.component.html',
25 | styleUrls: ['./app.component.scss'],
26 | standalone: true,
27 | imports: [
28 | OptionsComponent,
29 | HeaderComponent,
30 | SubHeaderComponent,
31 | AccordionComponent,
32 | FooterComponent,
33 | ],
34 | providers: [{ provide: VersionToken, useValue: VERSION }],
35 | })
36 | export class AppComponent {
37 | public docs = signal(ComDocs);
38 | public examples = signal<(TExample | { _pipe: string })[]>(ComExamples);
39 |
40 | public readonly lists: ListItem[] = lists;
41 | public readonly githubMaskLink = LinkPath.NGX_MASK;
42 | public readonly title = 'Ngx-Mask';
43 | public readonly subtitle = 'Angular plugin to make masks on form fields and html elements';
44 | public readonly chips = ['Angular', 'TypeScript', 'Web', 'Input', 'Pipe', 'Show-Masks'];
45 |
46 | private readonly selectedCardId = signal(1);
47 |
48 | public switchCard(cardId: number): void {
49 | if (this.selectedCardId() === cardId) {
50 | return;
51 | }
52 | this.selectedCardId.set(cardId);
53 |
54 | switch (cardId) {
55 | case 2:
56 | this.docs.set(OptDocs);
57 | this.examples.set(OptExamples);
58 | break;
59 | case 3:
60 | this.docs.set(SepDocs);
61 | this.examples.set(SepExamples);
62 | break;
63 | case 4:
64 | this.docs.set(OthDocs);
65 | this.examples.set(OthExamples);
66 | break;
67 | case 5:
68 | this.docs.set(ParserAndFormatterDocs);
69 | this.examples.set(FormatAndParserExamples);
70 | break;
71 | default:
72 | this.docs.set(ComDocs);
73 | this.examples.set(ComExamples);
74 | break;
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/app/options/options.component.html:
--------------------------------------------------------------------------------
1 | @for (tile of cardDocs(); track tile.id; let i = $index) {
2 |
9 |
10 | {{ tile.header }}
11 |
12 |
13 |
39 |
40 | }
41 |
42 |
43 |
44 |
49 |
50 |
51 |
52 |
53 |
54 |
78 |
79 |
80 |
84 |
88 |
89 |
90 | @if (ex._validation) {
91 |
95 | }
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/src/app/options/options.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | @apply flex flex-col gap-4 p-4 bg-full-white box-border border-t border-t-black/10 py-5 pl-[22px] pr-4;
3 | }
4 |
--------------------------------------------------------------------------------
/src/app/options/options.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, effect, ElementRef, inject, input, viewChildren } from '@angular/core';
2 | import { JsonPipe, NgOptimizedImage, NgTemplateOutlet } from '@angular/common';
3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4 | import { initialConfig, NgxMaskDirective, NgxMaskPipe } from 'ngx-mask';
5 | import { HighlightModule } from 'ngx-highlightjs';
6 | import { AssetPipe } from '@libraries/asset/asset.pipe';
7 | import { IsEmptyPipe } from '@open-source/is-empty/is-empty.pipe';
8 | import { CardContentComponent } from '../shared/card-content/card-content.component';
9 | import { ScrollService } from '@open-source/scroll/scroll.service';
10 | import { AccordionService } from '@open-source/accordion/accordion.service';
11 | import { OpenSourcePath } from '@open-source/path/open-source.path';
12 | import { toSignal } from '@angular/core/rxjs-interop';
13 | import type { ComDoc, MaskOptions, TExample } from '@open-source/accordion/content.types';
14 |
15 | @Component({
16 | selector: 'jsdaddy-open-source-options',
17 | templateUrl: './options.component.html',
18 | styleUrls: ['./options.component.scss'],
19 | standalone: true,
20 | providers: [ScrollService, AccordionService],
21 | imports: [
22 | JsonPipe,
23 | NgTemplateOutlet,
24 | FormsModule,
25 | ReactiveFormsModule,
26 | HighlightModule,
27 | NgxMaskDirective,
28 | NgxMaskPipe,
29 | AssetPipe,
30 | IsEmptyPipe,
31 | CardContentComponent,
32 | NgOptimizedImage,
33 | ],
34 | })
35 | export class OptionsComponent {
36 | public cardDocs = input();
37 | public cardExamples = input<(TExample | { _pipe: string })[]>();
38 |
39 | public cards = viewChildren>('cards', {
40 | read: ElementRef,
41 | });
42 |
43 | public readonly phone = '123456789';
44 | public readonly openSourceOptionsPath = OpenSourcePath.OPTIONS;
45 | public readonly specialCharacters = initialConfig.specialCharacters;
46 | public readonly outputTransformFn = initialConfig.outputTransformFn;
47 | public readonly inputTransformFn = initialConfig.inputTransformFn;
48 |
49 | private readonly scrollService = inject(ScrollService);
50 | private readonly accordionService = inject(AccordionService);
51 |
52 | public readonly activeCardId = toSignal(this.scrollService.activeCard$);
53 |
54 | public constructor() {
55 | effect(() => {
56 | this.scrollService.onScroll(this.cards());
57 | this.accordionService.onChangeAccordion(this.cards());
58 | });
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/app/shared/card-content/card-content.component.html:
--------------------------------------------------------------------------------
1 | {{ title() }}
2 | {{ value() }}
8 |
--------------------------------------------------------------------------------
/src/app/shared/card-content/card-content.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | @apply flex flex-col mt-5 text-full-white;
3 |
4 | .green-view {
5 | @apply text-green bg-green/[.10];
6 | }
7 | .yellow-view {
8 | @apply text-yellow bg-yellow/[.10];
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/shared/card-content/card-content.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'jsdaddy-open-source-card-content[title][color][value]',
5 | templateUrl: './card-content.component.html',
6 | styleUrls: ['./card-content.component.scss'],
7 | standalone: true,
8 | })
9 | export class CardContentComponent {
10 | public color = input.required();
11 | public title = input.required();
12 | public value = input.required();
13 | }
14 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JsDaddy/ngx-mask/c8b845bb5aff6b6789c94b7e4f2f413921977539/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/assets/content/common-cases.ts:
--------------------------------------------------------------------------------
1 | import { UntypedFormControl } from '@angular/forms';
2 | import type { ComDoc, MaskOptions, TExample } from '@open-source/accordion/content.types';
3 |
4 | export const ComDocs: ComDoc[] = [
5 | {
6 | header: 'Date',
7 | text: '',
8 | code: ``,
9 | id: 1,
10 | anchor: 'date',
11 | },
12 | {
13 | header: 'Date and hour',
14 | text: '',
15 | code: ``,
16 | id: 2,
17 | anchor: 'date-and-hour',
18 | },
19 | {
20 | header: 'Valid 24 hour format',
21 | text: '',
22 | code: ``,
23 | id: 3,
24 | anchor: 'valid24',
25 | },
26 | {
27 | header: 'Mixed types',
28 | text: '',
29 | code: ``,
30 | id: 4,
31 | anchor: 'mixed',
32 | },
33 | {
34 | header: 'Valid date start with years',
35 | text: '',
36 | code: ``,
37 | id: 5,
38 | anchor: 'startWithYears',
39 | },
40 | {
41 | header: 'Mask with specialCharacters',
42 | text: '',
43 | code: ``,
49 | id: 6,
50 | anchor: 'mask-specialCharacters',
51 | },
52 | {
53 | header: 'Optional mask',
54 | text: '',
55 | code: ``,
56 | id: 7,
57 | anchor: 'optional-mask',
58 | },
59 | {
60 | header: 'Email mask with validation',
61 | text: '',
62 | code: ``,
63 | id: 8,
64 | anchor: 'email-mask',
65 | },
66 | {
67 | header: 'Email mask with validation',
68 | text: '',
69 | code: ``,
70 | id: 8,
71 | anchor: 'email-mask',
72 | },
73 | {
74 | header: 'Allow negative numbers to mask',
75 | text: 'You can allow negative numbers',
76 | code: ` `,
77 | id: 9,
78 | anchor: 'allowMask',
79 | },
80 | {
81 | header: 'Allow negative numbers to separator',
82 | text: 'You can allow negative numbers to',
83 | code: ` `,
84 | id: 9,
85 | anchor: 'allowSeparator',
86 | },
87 | {
88 | header: 'Allow negative numbers to percent',
89 | text: 'You can allow negative numbers',
90 | code: ` `,
91 | id: 9,
92 | anchor: 'allowPercent',
93 | },
94 | {
95 | header: 'Allow few mask in one expression',
96 | text: '',
97 | code: ``,
98 | id: 10,
99 | anchor: 'allow-few-mask',
100 | },
101 | {
102 | header: 'Allow few mask in one expression number or letter',
103 | text: '',
104 | code: ``,
105 | id: 10,
106 | anchor: 'allow-few-mask',
107 | },
108 | ];
109 |
110 | export const ComExamples: TExample[] = [
111 | {
112 | _placeholder: 'Date',
113 | _mask: 'd0/M0/0000',
114 | control: { form: new UntypedFormControl(''), model: '' },
115 | },
116 | {
117 | _placeholder: 'Date and Hour',
118 | _mask: 'd0/M0/0000 Hh:m0:s0',
119 | control: { form: new UntypedFormControl(''), model: '' },
120 | },
121 | {
122 | _placeholder: 'Valid 24 hour format',
123 | _mask: 'Hh:m0:s0',
124 | control: { form: new UntypedFormControl(''), model: '' },
125 | },
126 | {
127 | _placeholder: 'Mixed Type',
128 | _mask: 'AAA 000-S0S',
129 | control: { form: new UntypedFormControl(''), model: '' },
130 | },
131 | {
132 | _placeholder: 'Valid date start with years',
133 | _mask: '0000.M0.d0',
134 | control: { form: new UntypedFormControl(''), model: '' },
135 | },
136 | {
137 | _placeholder: 'Mask with specialCharacters',
138 | _mask: '(000) 000-0000 ext. 000000',
139 | _showMaskTyped: true,
140 | _shownMaskExpression: '(___) ___-____ ext. ______',
141 | _specialCharacters: ['e', 'x', 't', ' ', '(', ')', '-', '.'],
142 | control: { form: new UntypedFormControl(''), model: '' },
143 | },
144 | {
145 | _placeholder: 'Optional mask',
146 | _mask: '9999 999 999',
147 | control: { form: new UntypedFormControl(''), model: '' },
148 | },
149 | {
150 | _placeholder: 'Valid email',
151 | _mask: 'A*@A*.SSS',
152 | _validation: true,
153 | _dropSpecialCharacters: false,
154 | control: { form: new UntypedFormControl(''), model: '' },
155 | },
156 | {
157 | _placeholder: 'Valid email',
158 | _validation: true,
159 | _dropSpecialCharacters: false,
160 | _mask: 'A*@A*.A*',
161 | control: { form: new UntypedFormControl(''), model: '' },
162 | },
163 |
164 | {
165 | _placeholder: 'allowNegativeNumbers mask',
166 | _allowNegativeNumbers: true,
167 | _mask: '0000',
168 | control: { form: new UntypedFormControl(''), model: '' },
169 | },
170 | {
171 | _placeholder: 'allowNegativeNumbers separator',
172 | _allowNegativeNumbers: true,
173 | _decimalMarker: '.',
174 | _mask: 'separator',
175 | control: { form: new UntypedFormControl(''), model: '' },
176 | },
177 | {
178 | _placeholder: 'allowNegativeNumbers percent',
179 | _decimalMarker: '.',
180 | _allowNegativeNumbers: true,
181 | _mask: 'percent.2',
182 | control: { form: new UntypedFormControl(''), model: '' },
183 | },
184 | {
185 | _placeholder: 'Allow few mask in one expression',
186 | _mask: '(00) 00000000||+00 (00) 00000000',
187 | control: { form: new UntypedFormControl(''), model: '' },
188 | },
189 | {
190 | _placeholder: 'Allow few mask in one expression',
191 | _mask: '00||SS',
192 | control: { form: new UntypedFormControl(''), model: '' },
193 | },
194 | ];
195 |
--------------------------------------------------------------------------------
/src/assets/content/lists.ts:
--------------------------------------------------------------------------------
1 | import type { ListItem } from '@open-source/accordion/content.types';
2 |
3 | export const lists: ListItem[] = [
4 | {
5 | header: 'Common cases',
6 | id: 1,
7 | defaultSvg: 'common-cases',
8 | activeSvg: 'common-cases-active',
9 | whiteChevron: 'white-chevron-down',
10 | yellowChevron: 'yellow-chevron-down',
11 | text: [
12 | {
13 | content: 'Date',
14 | id: 1,
15 | scrollTo: 'date',
16 | },
17 | {
18 | content: 'Date and hour',
19 | id: 2,
20 | scrollTo: 'date-and-hour',
21 | },
22 | {
23 | content: 'Valid 24 hour format',
24 | id: 3,
25 | scrollTo: 'valid24',
26 | },
27 | {
28 | content: 'Mixed types',
29 | id: 4,
30 | scrollTo: 'mixed',
31 | },
32 | {
33 | content: 'Valid date start with years',
34 | id: 5,
35 | scrollTo: 'startWithYears',
36 | },
37 | {
38 | content: 'SpecialCharacters Mask',
39 | id: 6,
40 | scrollTo: 'mask-specialCharacters',
41 | },
42 | {
43 | content: 'Optional mask',
44 | id: 7,
45 | scrollTo: 'optional-mask',
46 | },
47 | {
48 | content: 'Validation email mask',
49 | id: 8,
50 | scrollTo: 'email-mask',
51 | },
52 | {
53 | content: 'allowNegativeNumber',
54 | id: 9,
55 | scrollTo: 'allowMask',
56 | },
57 | {
58 | content: 'Allow few mask in one expression',
59 | id: 10,
60 | scrollTo: 'allow-few-mask',
61 | },
62 | ],
63 | },
64 | {
65 | header: 'Options',
66 | id: 2,
67 | defaultSvg: 'options',
68 | activeSvg: 'options-active',
69 | whiteChevron: 'white-chevron-down',
70 | yellowChevron: 'yellow-chevron-down',
71 | text: [
72 | {
73 | content: 'Prefix',
74 | id: 1,
75 | scrollTo: 'prefix',
76 | },
77 | {
78 | content: 'Suffix',
79 | id: 2,
80 | scrollTo: 'suffix',
81 | },
82 | {
83 | content: 'dropSpecialCharacters',
84 | id: 3,
85 | scrollTo: 'special-ch',
86 | },
87 | {
88 | content: 'showMaskTyped',
89 | id: 4,
90 | scrollTo: 'show-mask',
91 | },
92 | {
93 | content: 'clearIfNotMatch',
94 | id: 5,
95 | scrollTo: 'clear',
96 | },
97 | {
98 | content: 'Validation',
99 | id: 6,
100 | scrollTo: 'valid',
101 | },
102 | {
103 | content: 'Keep Character Position',
104 | id: 7,
105 | scrollTo: 'keep000',
106 | },
107 | ],
108 | },
109 | {
110 | header: 'Parser and Formatter',
111 | id: 5,
112 | defaultSvg: 'parser-and-formatter',
113 | activeSvg: 'parser-and-formatter-active',
114 | whiteChevron: 'white-chevron-down',
115 | yellowChevron: 'yellow-chevron-down',
116 | text: [
117 | {
118 | content: 'To upper case',
119 | id: 1,
120 | scrollTo: 'toUpperCase',
121 | },
122 | {
123 | content: 'To local date',
124 | id: 2,
125 | scrollTo: 'to_date',
126 | },
127 | {
128 | content: 'Change decimalMarker',
129 | id: 3,
130 | scrollTo: 'replace_dot',
131 | },
132 | {
133 | content: 'Value toFixed(2)',
134 | id: 4,
135 | scrollTo: 'toFixed',
136 | },
137 | ],
138 | },
139 | {
140 | header: 'Separators',
141 | id: 3,
142 | defaultSvg: 'separator',
143 | activeSvg: 'separator-active',
144 | whiteChevron: 'white-chevron-down',
145 | yellowChevron: 'yellow-chevron-down',
146 | text: [
147 | {
148 | content: 'Separator',
149 | id: 1,
150 | scrollTo: 'sep',
151 | },
152 | {
153 | content: 'Separator leadZero',
154 | id: 2,
155 | scrollTo: 'lead-zero',
156 | },
157 | {
158 | content: 'Dot separator',
159 | id: 3,
160 | scrollTo: 'Dsep',
161 | },
162 | {
163 | content: 'Comma separator',
164 | id: 4,
165 | scrollTo: 'comma_sep',
166 | },
167 | {
168 | content: 'Zero separator',
169 | id: 5,
170 | scrollTo: 'sep0',
171 | },
172 | ],
173 | },
174 | {
175 | header: 'Other',
176 | id: 4,
177 | defaultSvg: 'other',
178 | activeSvg: 'other-active',
179 | whiteChevron: 'white-chevron-down',
180 | yellowChevron: 'yellow-chevron-down',
181 | text: [
182 | {
183 | content: 'Secure input',
184 | id: 1,
185 | scrollTo: 'secure',
186 | },
187 | {
188 | content: 'Pipe',
189 | id: 2,
190 | scrollTo: 'pipe',
191 | },
192 | {
193 | content: 'specialCharacters',
194 | id: 3,
195 | scrollTo: 'special',
196 | },
197 | {
198 | content: '12 hour format',
199 | id: 4,
200 | scrollTo: '12hour',
201 | },
202 | {
203 | content: 'Percent with comma',
204 | id: 5,
205 | scrollTo: 'percentDecimalMarker',
206 | },
207 | ],
208 | },
209 | ];
210 |
--------------------------------------------------------------------------------
/src/assets/content/optional.ts:
--------------------------------------------------------------------------------
1 | import { UntypedFormControl } from '@angular/forms';
2 | import type { ComDoc, MaskOptions, TExample } from '@open-source/accordion/content.types';
3 |
4 | export const OptDocs: ComDoc[] = [
5 | {
6 | header: 'Prefix (string)',
7 | text: 'You can add prefix to you masked value',
8 | code: ``,
9 | id: 1,
10 | anchor: 'prefix',
11 | },
12 | {
13 | header: 'Suffix (string)',
14 | text: 'You can add suffix to you masked value',
15 | code: ``,
16 | id: 2,
17 | anchor: 'suffix',
18 | },
19 | {
20 | header: 'dropSpecialCharacters (boolean)',
21 | text: 'You can choose if mask will drop special character in the model, or not, default value true',
22 | code: ``,
23 | id: 3,
24 | anchor: 'special-ch',
25 | },
26 | {
27 | header: 'showMaskTyped (boolean)',
28 | text: 'You can choose if mask is shown while typing, or not, default value false',
29 | code: ` `,
30 | id: 4,
31 | anchor: 'show-mask',
32 | },
33 | {
34 | header: 'clearIfNotMatch (boolean)',
35 | text: 'You can choose clear the input if the input value not match the mask, default value false',
36 | code: ``,
37 | id: 5,
38 | anchor: 'clear',
39 | },
40 | {
41 | header: 'FormControl validation',
42 | text: 'You can validate your formControl, default value is true',
43 | code: ` `,
44 | id: 6,
45 | anchor: 'valid',
46 | },
47 | {
48 | header: 'Keep Character Positions',
49 | text: 'You can validate your formControl, default value is true',
50 | code: ` `,
51 | id: 7,
52 | anchor: 'keep000',
53 | },
54 | {
55 | header: 'Keep Character Positions',
56 | text: 'You can validate your formControl, default value is true',
57 | code: ` `,
58 | id: 7,
59 | anchor: 'keep000',
60 | },
61 | {
62 | header: 'Keep Character Positions',
63 | text: 'You can validate your formControl, default value is true',
64 | code: ` `,
65 | id: 7,
66 | anchor: 'keep000',
67 | },
68 | {
69 | header: 'Keep Character Positions',
70 | text: 'You can validate your formControl, default value is true',
71 | code: ` `,
72 | id: 7,
73 | anchor: 'keep000',
74 | },
75 | {
76 | header: 'Keep Character Positions',
77 | text: 'You can validate your formControl, default value is true',
78 | code: ` `,
79 | id: 7,
80 | anchor: 'keep000',
81 | },
82 | ];
83 |
84 | export const OptExamples: TExample[] = [
85 | {
86 | _placeholder: 'prefix',
87 | _prefix: '+7 ',
88 | _mask: '(00) 000 000',
89 | control: { form: new UntypedFormControl(''), model: '' },
90 | },
91 | {
92 | _placeholder: 'suffix',
93 | _suffix: ' $',
94 | _mask: '0 000',
95 | control: { form: new UntypedFormControl(''), model: '' },
96 | },
97 | {
98 | _placeholder: 'dropSpecialCharacters',
99 | _dropSpecialCharacters: false,
100 | _mask: '000-000.00',
101 | control: { form: new UntypedFormControl(''), model: '' },
102 | },
103 | {
104 | _placeholder: 'showMaskTyped',
105 | _showMaskTyped: true,
106 | _prefix: '+7',
107 | _mask: '(000) 000-0000',
108 | control: { form: new UntypedFormControl(''), model: '' },
109 | },
110 | {
111 | _placeholder: 'clearIfNotMatch',
112 | _clearIfNotMatch: true,
113 | _mask: '000-000.00',
114 | control: { form: new UntypedFormControl(''), model: '' },
115 | },
116 | {
117 | _placeholder: 'validation',
118 | _validation: true,
119 | _mask: '00 00',
120 | control: { form: new UntypedFormControl(''), model: '' },
121 | },
122 | {
123 | _showMaskTyped: true,
124 | _keepCharacterPositions: true,
125 | _mask: '000-000-000',
126 | control: { form: new UntypedFormControl(''), model: '' },
127 | },
128 | {
129 | _showMaskTyped: true,
130 | _keepCharacterPositions: true,
131 | _mask: '00/00/0000',
132 | control: { form: new UntypedFormControl(''), model: '' },
133 | },
134 | {
135 | _showMaskTyped: true,
136 | _keepCharacterPositions: true,
137 | _mask: '0000 0000 0000 0000',
138 | control: { form: new UntypedFormControl(''), model: '' },
139 | },
140 | {
141 | _showMaskTyped: true,
142 | _keepCharacterPositions: true,
143 | _suffix: '$',
144 | _mask: '0 000',
145 | control: { form: new UntypedFormControl(''), model: '' },
146 | },
147 | {
148 | _showMaskTyped: true,
149 | _keepCharacterPositions: true,
150 | _prefix: '$',
151 | _mask: '0000.00',
152 | control: { form: new UntypedFormControl(''), model: '' },
153 | },
154 | ];
155 |
--------------------------------------------------------------------------------
/src/assets/content/other.ts:
--------------------------------------------------------------------------------
1 | import { UntypedFormControl } from '@angular/forms';
2 | import type { ComDoc, MaskOptions, TExample } from '@open-source/accordion/content.types';
3 |
4 | export const OthDocs: ComDoc[] = [
5 | {
6 | header: 'Secure input',
7 | text: 'You can hide symbols in input field and get the actual value in formcontrol',
8 | code: ` `,
9 | id: 1,
10 | anchor: 'secure',
11 | },
12 | {
13 | header: 'Pipe',
14 | text: 'Also you can use mask pipe',
15 | code: ` {{phone | mask: '(000) 000-0000'}}`,
16 | id: 2,
17 | anchor: 'pipe',
18 | },
19 | {
20 | header: 'specialCharacters',
21 | text: '',
22 | // eslint-disable-next-line no-useless-escape
23 | code: ` `,
24 | id: 3,
25 | anchor: 'special',
26 | },
27 | {
28 | header: '12 hour format',
29 | text: '',
30 |
31 | code: ` `,
32 | id: 4,
33 | anchor: '12hour',
34 | },
35 | {
36 | header: 'Percent with decimalMarker ,',
37 | text: '',
38 |
39 | code: ` `,
40 | id: 5,
41 | anchor: 'percentDecimalMarker',
42 | },
43 | ];
44 |
45 | export const OthExamples: (TExample | { _pipe: string })[] = [
46 | {
47 | _placeholder: 'Secure input',
48 | _hiddenInput: true,
49 | _mask: 'XXX/X0/0000',
50 | control: { form: new UntypedFormControl(''), model: '' },
51 | },
52 | {
53 | _pipe: '(000) 000-0000',
54 | },
55 | {
56 | _placeholder: 'specialCharacters',
57 | _specialCharacters: `[ '[' ,']' , '\\' ]`,
58 | _mask: '[00][000]',
59 | control: { form: new UntypedFormControl(''), model: '' },
60 | },
61 | {
62 | _placeholder: '12 hour format',
63 | _mask: 'Hh:m0:s0',
64 | _apm: true,
65 | control: { form: new UntypedFormControl(''), model: '' },
66 | },
67 | {
68 | _placeholder: '12 hour format',
69 | _mask: 'percent.2',
70 | _decimalMarker: ',',
71 | control: { form: new UntypedFormControl(''), model: '' },
72 | },
73 | ];
74 |
--------------------------------------------------------------------------------
/src/assets/content/separators.ts:
--------------------------------------------------------------------------------
1 | import { UntypedFormControl } from '@angular/forms';
2 | import type { ComDoc, MaskOptions, TExample } from '@open-source/accordion/content.types';
3 |
4 | export const SepDocs: ComDoc[] = [
5 | {
6 | header: 'Thousand separator',
7 | text: 'You can divide your input by thousands',
8 | code: ``,
9 | id: 1,
10 | anchor: 'sep',
11 | },
12 | {
13 | header: 'Lead zero at model',
14 | text: 'You can divide your input by thousands',
15 | code: ``,
16 | id: 2,
17 | anchor: 'lead-zero',
18 | },
19 | {
20 | header: 'Dot separator',
21 | text: 'For separate input with dots',
22 | code: ``,
23 | id: 3,
24 | anchor: 'Dsep',
25 | },
26 | {
27 | header: 'Comma separator',
28 | text: `For separate input with commas`,
29 | code: ``,
30 | id: 4,
31 | anchor: 'comma_sep',
32 | },
33 | {
34 | header: 'Zero separator',
35 | text: 'You can divide your input by thousands',
36 | code: ``,
37 | id: 5,
38 | anchor: 'sep0',
39 | },
40 | ];
41 |
42 | export const SepExamples: TExample[] = [
43 | {
44 | _placeholder: 'Separator',
45 | _mask: 'separator',
46 | control: { form: new UntypedFormControl(''), model: '' },
47 | },
48 | {
49 | _placeholder: 'Separator 2 leadZero',
50 | _leadZero: true,
51 | _mask: 'separator.2',
52 | control: { form: new UntypedFormControl(''), model: '' },
53 | },
54 | {
55 | _placeholder: 'separator.2',
56 | _mask: 'separator.2',
57 | _thousandSeparator: '.',
58 | control: { form: new UntypedFormControl(''), model: '' },
59 | },
60 | {
61 | _placeholder: 'separator.2',
62 | _mask: 'separator.2',
63 | _thousandSeparator: ',',
64 | _decimalMarker: '.',
65 | control: { form: new UntypedFormControl(''), model: '' },
66 | },
67 | {
68 | _placeholder: 'separator.0',
69 | _mask: 'separator.0',
70 | control: { form: new UntypedFormControl(''), model: '' },
71 | },
72 | ];
73 |
--------------------------------------------------------------------------------
/src/assets/images/content/scroll-shadow.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/accordion/common-cases-active.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/accordion/common-cases.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/accordion/options-active.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/accordion/options.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/accordion/other-active.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/accordion/other.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/accordion/parser-and-formatter-active.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/accordion/parser-and-formatter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/accordion/separator-active.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/accordion/separator.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/accordion/white-chevron-down.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/accordion/yellow-chevron-down.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/accordion/yellow-chevron-up.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/header/burger.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/header/close.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/header/logo-white.svg:
--------------------------------------------------------------------------------
1 |
26 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/header/logo.svg:
--------------------------------------------------------------------------------
1 |
26 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/options/hand-box.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/options/input-vector.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/open-source/visit-btn/button-chevron.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/shared/github.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JsDaddy/ngx-mask/c8b845bb5aff6b6789c94b7e4f2f413921977539/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ngx Mask | JSDaddy
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { AppComponent } from './app/app.component';
3 | import { provideAnimations } from '@angular/platform-browser/animations';
4 | import { provideNgxMask } from 'ngx-mask';
5 | import { provideRouter } from '@angular/router';
6 | import { HIGHLIGHT_OPTIONS } from 'ngx-highlightjs';
7 | import { provideHttpClient } from '@angular/common/http';
8 | import { BaseHttpService } from '@libraries/base-http/base-http.service';
9 | import { DOMAIN } from '@libraries/token/token';
10 | import { GithubStarsService } from '@libraries/github/github-stars.service';
11 | import { provideExperimentalZonelessChangeDetection } from '@angular/core';
12 |
13 | bootstrapApplication(AppComponent, {
14 | providers: [
15 | provideExperimentalZonelessChangeDetection(),
16 | GithubStarsService,
17 | provideHttpClient(),
18 | {
19 | provide: DOMAIN,
20 | useValue: [BaseHttpService],
21 | },
22 | BaseHttpService,
23 | provideAnimations(),
24 | provideRouter([]),
25 | provideNgxMask(),
26 | {
27 | provide: HIGHLIGHT_OPTIONS,
28 | useValue: {
29 | fullLibraryLoader: () => import('highlight.js'),
30 | },
31 | },
32 | ],
33 | // eslint-disable-next-line no-console
34 | }).catch((err) => console.error(err));
35 |
--------------------------------------------------------------------------------
/src/styles.scss:
--------------------------------------------------------------------------------
1 | @use './libraries/styles/scroll-bar';
2 |
3 | @tailwind base;
4 | @tailwind components;
5 | @tailwind utilities;
6 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import { type Config } from 'tailwindcss';
2 |
3 | const tailwindConfig: Config = {
4 | content: ['./src/**/*.{html,scss,ts}'],
5 | theme: {
6 | extend: {
7 | screens: {
8 | mob: { min: '1px', max: '700px' },
9 | tab: { min: '700px', max: '1279px' },
10 | desk: { min: '1279px' },
11 | },
12 | fontFamily: {
13 | sans: ['Varela', 'system-ui', 'sans-serif'],
14 | },
15 | colors: {
16 | dark: {
17 | DEFAULT: '#191919',
18 | },
19 | yellow: {
20 | DEFAULT: '#FFD64D',
21 | },
22 | green: {
23 | DEFAULT: '#1AB77E',
24 | },
25 | orange: {
26 | DEFAULT: '#FF710A',
27 | },
28 | 'full-white': '#FFFFFF',
29 | white: {
30 | DEFAULT: '#F8F8F8',
31 | },
32 | },
33 | spacing: {
34 | '5px': '5px',
35 | '15px': '15px',
36 | '30px': '30px',
37 | '35px': '35px',
38 | '50px': '50px',
39 | },
40 | fontSize: {
41 | title: [
42 | '10px',
43 | {
44 | lineHeight: '14px',
45 | fontWeight: '400',
46 | },
47 | ],
48 | 'span-12': [
49 | '12px',
50 | {
51 | lineHeight: '21px',
52 | fontWeight: '400',
53 | },
54 | ],
55 | span: [
56 | '14px',
57 | {
58 | lineHeight: '21px',
59 | fontWeight: '400',
60 | },
61 | ],
62 |
63 | h3: [
64 | '25px',
65 | {
66 | lineHeight: '27px',
67 | fontWeight: '500',
68 | },
69 | ],
70 | h5: [
71 | '16px',
72 | {
73 | lineHeight: '24px',
74 | fontWeight: '400',
75 | },
76 | ],
77 | },
78 | borderRadius: {
79 | '4px': '4px',
80 | '5px': '5px',
81 | '10px': '10px',
82 | '15px': '15px',
83 | '25px': '25px',
84 | },
85 | borderWidth: {
86 | '2px': '2px',
87 | },
88 | },
89 | },
90 | plugins: [],
91 | };
92 |
93 | export default tailwindConfig;
94 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app"
5 | },
6 | "files": ["src/main.ts"],
7 | "include": ["src/**/*.d.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.eslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": ["**/*.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "downlevelIteration": true,
6 | "importHelpers": true,
7 | "outDir": "./dist/out-tsc",
8 | "sourceMap": true,
9 | "esModuleInterop": true,
10 | "declaration": false,
11 | "module": "ES2022",
12 | "experimentalDecorators": true,
13 | "strict": true,
14 | "target": "ES2022",
15 | "lib": ["ES2022", "dom"],
16 | "paths": {
17 | "@libraries/*": ["src/libraries/*"],
18 | "@open-source/*": ["src/libraries/open-source/*"],
19 | "ngx-mask": ["projects/ngx-mask-lib/src"],
20 | "ngx-mask/*": ["projects/ngx-mask-lib/src"]
21 | },
22 | "moduleResolution": "node",
23 | "forceConsistentCasingInFileNames": true,
24 | "noUnusedLocals": true,
25 | "noUnusedParameters": true,
26 | "noImplicitReturns": true,
27 | "noFallthroughCasesInSwitch": true,
28 | "noUncheckedIndexedAccess": true,
29 | "useUnknownInCatchVariables": true,
30 | "exactOptionalPropertyTypes": true,
31 | "removeComments": true,
32 | "resolveJsonModule": true,
33 | "allowSyntheticDefaultImports": true,
34 | "noImplicitOverride": true,
35 | "noPropertyAccessFromIndexSignature": true,
36 | "skipLibCheck": true,
37 | "noImplicitAny": true,
38 | "useDefineForClassFields": true,
39 | "isolatedModules": true
40 | },
41 | "angularCompilerOptions": {
42 | "strictStandalone": true,
43 | "enableI18nLegacyMessageIdFormat": false,
44 | "strictInjectionParameters": true,
45 | "preserveWhitespaces": true,
46 | "fullTemplateTypeCheck": true,
47 | "strictInputAccessModifiers": true,
48 | "strictTemplates": true,
49 | "extendedDiagnostics": {
50 | "checks": {
51 | "invalidBananaInBox": "error",
52 | "nullishCoalescingNotNullable": "warning",
53 | "unusedStandaloneImports": "error"
54 | },
55 | "defaultCategory": "suppress"
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "types": ["jasmine", "node"]
6 | },
7 | "include": ["**/*.spec.ts", "**/*.d.ts"]
8 | }
9 |
--------------------------------------------------------------------------------