├── .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 |
14 |
15 |
16 | Hand with box 21 | Usage 22 |
23 | Source code 26 |
27 |
 28 |                         
 29 |                         Input vector
 30 |                     
31 |
32 |
33 | 38 |
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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/assets/images/open-source/accordion/common-cases-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/images/open-source/accordion/common-cases.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/images/open-source/accordion/options-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/images/open-source/accordion/options.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/images/open-source/accordion/other-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/images/open-source/accordion/other.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/images/open-source/accordion/parser-and-formatter-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/images/open-source/accordion/parser-and-formatter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/images/open-source/accordion/separator-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/images/open-source/accordion/separator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/images/open-source/accordion/white-chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/open-source/accordion/yellow-chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/open-source/accordion/yellow-chevron-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/open-source/header/burger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/open-source/header/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/images/open-source/header/logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/assets/images/open-source/header/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/assets/images/open-source/options/hand-box.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/open-source/options/input-vector.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/open-source/visit-btn/button-chevron.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/shared/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | --------------------------------------------------------------------------------