[
  {
    "path": ".cspell.json",
    "content": "{\n    \"$schema\": \"https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json\",\n    \"import\": [\"@taiga-ui/cspell-config\"],\n    \"files\": [\"*/*.*\"],\n    \"ignorePaths\": [\n        \".git\",\n        \".gitignore\",\n        \".npmrc\",\n        \".cspell.json\",\n        \"**/dist/**\",\n        \"**/assets/**\",\n        \"**/node_modules/**\",\n        \"**/demo/server.ts\",\n        \"**/demo-integrations/src/tests/**\",\n        \"*.{log,svg,snap,png,ogv,yml}\",\n        \"**/package.json\",\n        \"**/tsconfig*.json\"\n    ],\n    \"ignoreWords\": [\"Acpekt\", \"WHATWG\", \"prebundle\"]\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 4\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".firebaserc",
    "content": "{\n  \"projects\": {\n    \"default\": \"maskito\"\n  }\n}\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @taiga-family/core-team\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making\nparticipation in our project and our community a harassment-free experience for everyone, regardless of age, body size,\ndisability, ethnicity, sex characteristics, gender identity and expression, level of experience, education,\nsocio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take\nappropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits,\nissues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any\ncontributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the\nproject or its community. Examples of representing a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed representative at an online or offline\nevent. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project at\nopensource@acpekt.ru. All complaints will be reviewed and investigated and will result in a response that is deemed\nnecessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to\nthe reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent\nrepercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at\nhttps://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing\n\n> Thank you for considering contributing to our project. Your help if very welcome!\n\nWhen contributing, it's better to first discuss the change you wish to make via issue, email, or any other method with\nthe owners of this repository before making a change.\n\nAll members of our community are expected to follow our [Code of Conduct](.github/CODE_OF_CONDUCT.md). Please make sure\nyou are welcoming and friendly in all of our spaces.\n\n## Getting started\n\nIn order to make your contribution please make a fork of the repository. After you've pulled the code, follow these\nsteps to kick-start the development:\n\n1. Run `npm ci` to install dependencies\n2. Run `npm start` to launch demo project where you could test your changes\n3. Use following commands to ensure code quality\n\n```bash\nnpm run lint\nnpm run build\nnpm run test\nnpm run cy:run\n```\n\n## Pull Request Process\n\n1. We follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.4/) in our commit messages, i.e.\n   `feat(core): improve typing`\n2. Update [demo](projects/demo) application to reflect changes related to public API and everything relevant\n3. Make sure you cover all code changes with unit tests and/or [Cypress](https://www.cypress.io) tests\n4. When you are ready, create Pull Request of your fork into original repository\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-bug-report.yml",
    "content": "name: '🐞 - Bug Report'\ntitle: '🐞 - '\ndescription: Report a bug in the Maskito\nlabels: ['bug']\ntype: Bug\n\nbody:\n  - type: dropdown\n    id: affected-packages\n    attributes:\n      label: Which package(s) are the source of the bug?\n      options:\n        - '@maskito/core'\n        - '@maskito/kit'\n        - '@maskito/phone'\n        - '@maskito/angular'\n        - '@maskito/react'\n        - '@maskito/vue'\n        - Don't known / other\n      multiple: true\n    validations:\n      required: true\n\n  - type: input\n    id: playground-link\n    attributes:\n      label: Playground Link\n      description: |\n        Link to an isolated reproduction in our [StackBlitz playground](https://maskito.dev/stackblitz).\n\n        If either of the following holds true:\n        - You can't reproduce the issue in the playground\n        - Your issue requires some complex setup - such as multiple files or a specific folder structure.\n\n        So you can use any link that might help for reproduction bug: github repo, demo url, etc.\n\n        ***Help us to help you!***\n      placeholder: https://stackblitz.com/edit/...\n    validations:\n      required: true\n\n  - type: textarea\n    id: description\n    attributes:\n      label: Description\n      placeholder: |\n        Please provide the exception or error you saw.\n        How do you trigger this bug?\n        Please walk us through it step by step.\n        Please provide a screenshot if possible.\n    validations:\n      required: true\n\n  - type: input\n    id: maskito-version\n    attributes:\n      label: Maskito version\n      placeholder: x.y.z\n    validations:\n      required: true\n\n  - type: checkboxes\n    id: browser-specific\n    attributes:\n      label: Which browsers have you used?\n      description: You may select more than one.\n      options:\n        - label: Chrome\n        - label: Firefox\n        - label: Safari\n        - label: Edge\n\n  - type: checkboxes\n    id: operating-systems\n    attributes:\n      label: Which operating systems have you used?\n      description: You may select more than one.\n      options:\n        - label: macOS\n        - label: Windows\n        - label: Linux\n        - label: iOS\n        - label: Android\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2-feature-request.yml",
    "content": "name: '🚀 - Feature Request'\ntitle: '🚀 - '\ndescription: Suggest a feature for Maskito\nlabels: ['feature']\ntype: Feature\n\nbody:\n  - type: dropdown\n    id: affected-packages\n    attributes:\n      label: Which package(s) are relevant/related to the feature request?\n      options:\n        - '@maskito/core'\n        - '@maskito/kit'\n        - '@maskito/phone'\n        - '@maskito/angular'\n        - '@maskito/react'\n        - '@maskito/vue'\n        - Don't known / other\n      multiple: true\n\n  - type: textarea\n    id: description\n    attributes:\n      label: Description\n      placeholder: |\n        Proposed solution.\n        Alternatives considered.\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/3-documentation.yml",
    "content": "name: '📚 - Documentation'\ntitle: '📚 - '\ndescription: Report an issue in Maskito's documentation\nlabels: ['documentation']\ntype: Documentation\n\nbody:\n  - type: input\n    id: affected-url\n    attributes:\n      label: What is the affected URL?\n\n  - type: textarea\n    id: description\n    attributes:\n      label: Description\n      placeholder: |\n        How do you trigger this bug?\n        Please walk us through it step by step.\n        Please provide a screenshot if possible.\n    validations:\n      required: true\n\n  - type: checkboxes\n    id: browser-specific\n    attributes:\n      label: Which browsers have you used?\n      description: You may select more than one.\n      options:\n        - label: Chrome\n        - label: Firefox\n        - label: Safari\n        - label: Edge\n\n  - type: checkboxes\n    id: operating-systems\n    attributes:\n      label: Which operating systems have you used?\n      description: You may select more than one.\n      options:\n        - label: macOS\n        - label: Windows\n        - label: Linux\n        - label: iOS\n        - label: Android\n"
  },
  {
    "path": ".github/renovate.json",
    "content": "{\n    \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n    \"extends\": [\"github>taiga-family/renovate-config\"],\n    \"packageRules\": [\n        {\n            \"enabled\": false,\n            \"matchPackageNames\": [\n                \"/^@nx.*/\",\n                \"/^nx$/\",\n                \"cypress\"\n            ]\n        },\n        {\n            \"enabled\": false,\n            \"matchPackageNames\": [\"jest-preset-angular\"]\n        }\n    ]\n}\n"
  },
  {
    "path": ".github/workflows/assign-author.yml",
    "content": "name: 🤖 PR author as an assignee\non:\n  pull_request:\n    types: [opened]\n\njobs:\n  assign:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: toshimaru/auto-author-assign@v3.0.1\n        continue-on-error: true\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n"
  },
  {
    "path": ".github/workflows/auto-merge.yml",
    "content": "name: 🤖 PR auto merge\non:\n  pull_request:\n\nenv:\n  PR_JOBS_NAME: '[ \"Packages\", \"Demo\", \"Firebase\", \"Lint result\", \"tests\", \"E2E result\" ]'\n\njobs:\n  setup:\n    runs-on: ubuntu-latest\n    outputs:\n      matrix: ${{ steps.matrix.outputs.value }}\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - id: matrix\n        run: echo \"value=$PR_JOBS_NAME\" >> $GITHUB_OUTPUT\n\n  wait:\n    needs: [setup]\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: true\n      matrix:\n        value: ${{ fromJSON(needs.setup.outputs.matrix) }}\n    steps:\n      - uses: taiga-family/ci/actions/run/wait-job@v1.201.0\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          job: ${{ matrix.value }}\n\n  approve:\n    needs: [wait]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/auto/approve/double@v1.201.0\n        if: env.IS_TAIGA_FAMILY_BOT_PR_AUTHOR == 'true'\n        with:\n          token1: ${{ secrets.GITHUB_TOKEN }}\n          token2: ${{ secrets.TAIGA_FAMILY_APPROVE_BOT_PAT }}\n      - uses: taiga-family/ci/actions/run/merge@v1.201.0\n        if: env.IS_TAIGA_FAMILY_BOT_PR_AUTHOR == 'true'\n        with:\n          token: ${{ secrets.TAIGA_FAMILY_BOT_PAT }}\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\non:\n  pull_request:\n  push:\n    branches: [main]\n\njobs:\n  build-packages:\n    name: Packages\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - run: npx nx run-many --target build --all --exclude=demo\n\n  build-demo:\n    name: Demo\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - run: npx nx build-gh-pages\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n"
  },
  {
    "path": ".github/workflows/deploy-preview.yml",
    "content": "name: Deploy / preview\n\non: pull_request\n\njobs:\n  build_and_preview:\n    name: Firebase\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - run: npx nx run-many --target build --all --exclude=demo\n      - run: npx nx run demo:build:typecheck\n\n      - name: Debug output\n        run: tree dist/demo/browser -P '*.html'\n      - name: Deploy preview\n        uses: FirebaseExtended/action-hosting-deploy@v0.10.0\n        if: env.IS_OWNER_MODE == 'true'\n        with:\n          repoToken: ${{ secrets.GITHUB_TOKEN }}\n          firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_MASKITO }}\n          projectId: maskito\n          expires: 1d\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "content": "name: Deploy\non:\n  workflow_dispatch:\n  push:\n    branches: [main]\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, 'chore(release)') }}\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - run: npx nx build-gh-pages\n      - uses: JamesIves/github-pages-deploy-action@v4.8.0\n        with:\n          branch: gh-pages\n          folder: dist/demo/browser\n          silent: false\n          clean: true\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n"
  },
  {
    "path": ".github/workflows/e2e.yml",
    "content": "name: E2E\non: [pull_request]\n\nenv:\n  CACHE_DIST_KEY: dist-${{ github.ref }}-${{ github.sha }}\n  CYPRESS_CACHE_FOLDER: ./node_modules/cache-cypress\n  UNIVERSAL_SERVER: http://localhost:4000\n  STATIC_SERVER: http://localhost:8080\n\njobs:\n  build-demo:\n    name: Build demo\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n        with:\n          fetch-depth: 10\n\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - run: npx tsc --project projects/demo-integrations/tsconfig.json\n\n      - name: Mark demo-app directory for persist in cache\n        uses: actions/cache@v5\n        with:\n          path: dist/demo\n          key: ${{ env.CACHE_DIST_KEY }}\n\n      - name: Build demo\n        # --optimization false to keep `window.ng` inside Cypress tests\n        run: npm run build -- --optimization false\n\n  e2e-kit:\n    needs: [build-demo]\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        project: [date, date-range, date-time, number, time]\n    name: Kit / ${{ matrix.project }}\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - uses: taiga-family/ci/actions/setup/cypress@v1.201.0\n\n      - name: Download demo build from cache\n        uses: actions/cache@v5\n        with:\n          path: dist/demo\n          key: ${{ env.CACHE_DIST_KEY }}\n\n      - name: Serving SSR server\n        run: |\n          npm run serve:ssr -- --ci & sleep 5\n          curl -X GET -I -f \"${{ env.UNIVERSAL_SERVER }}\"\n\n      - name: Run Cypress tests\n        run:\n          npx nx e2e demo-integrations --spec=\"**/kit/${{ matrix.project }}/**/*.cy.ts\" --baseUrl=${{\n          env.UNIVERSAL_SERVER}}\n\n  e2e-recipes:\n    needs: [build-demo]\n    runs-on: ubuntu-latest\n    name: Recipes\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - uses: taiga-family/ci/actions/setup/cypress@v1.201.0\n\n      - name: Download demo build from cache\n        uses: actions/cache@v5\n        with:\n          path: dist/demo\n          key: ${{ env.CACHE_DIST_KEY }}\n\n      - name: Serving SSR server\n        run: |\n          npm run serve:ssr -- --ci & sleep 5\n          curl -X GET -I -f \"${{ env.UNIVERSAL_SERVER }}\"\n\n      - name: Run Cypress tests\n        run: npx nx e2e demo-integrations --spec=\"**/recipes/**/*.cy.ts\" --baseUrl=${{ env.UNIVERSAL_SERVER}}\n\n  e2e-others:\n    needs: [build-demo]\n    runs-on: ubuntu-latest\n    name: Others\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - uses: taiga-family/ci/actions/setup/cypress@v1.201.0\n\n      - name: Download demo build from cache\n        uses: actions/cache@v5\n        with:\n          path: dist/demo\n          key: ${{ env.CACHE_DIST_KEY }}\n\n      - name: Serving SSR server\n        run: |\n          npm run serve:ssr -- --ci & sleep 5\n          curl -X GET -I -f \"${{ env.UNIVERSAL_SERVER }}\"\n\n      - name: Run Cypress tests\n        # Replace with npm run cy:run -- --spec \"**/!(kit|recipes)/*.cy.ts\" --config baseUrl=\"${{ env.UNIVERSAL_SERVER }}\"\n        # After this issue fix: https://github.com/cypress-io/cypress/issues/22407\n        run:\n          npx nx e2e demo-integrations --spec=\"**/(angular|react|ssr|addons|others)/**/*.cy.ts\" --baseUrl=${{\n          env.UNIVERSAL_SERVER}}\n\n  component-testing:\n    runs-on: ubuntu-latest\n    name: Component Testing\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - uses: taiga-family/ci/actions/setup/cypress@v1.201.0\n\n      - name: Run Cypress tests\n        run:\n          npx nx component-test demo-integrations --browser=chrome && npx nx ct-react demo-integrations --browser=chrome\n\n  result:\n    needs: [build-demo, e2e-kit, e2e-recipes, e2e-others, component-testing]\n    runs-on: ubuntu-latest\n    name: E2E result\n    steps:\n      - run: echo \"Success\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\non: [pull_request]\n\njobs:\n  typecheck:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - run: npm run typecheck\n\n  cspell:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - run: npm run cspell -- --no-progress\n\n  prettier:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n        with:\n          persist-credentials: false\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - run: npm run prettier ${{ env.SUPPORT_AUTO_PUSH == 'true' && '-- --write' || '-- --check' }}\n      - uses: taiga-family/ci/actions/auto/push@v1.201.0\n        with:\n          token: ${{ secrets.TAIGA_FAMILY_BOT_PAT }}\n\n  stylelint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n        with:\n          persist-credentials: false\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - run: npm run stylelint ${{ env.SUPPORT_AUTO_PUSH == 'true' && '-- --fix' || '' }}\n      - uses: taiga-family/ci/actions/auto/push@v1.201.0\n        with:\n          token: ${{ secrets.TAIGA_FAMILY_BOT_PAT }}\n\n  eslint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n        with:\n          persist-credentials: false\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - run: npm run lint ${{ env.SUPPORT_AUTO_PUSH == 'true' && '-- --fix' || '' }}\n      - uses: taiga-family/ci/actions/auto/push@v1.201.0\n        with:\n          token: ${{ secrets.TAIGA_FAMILY_BOT_PAT }}\n\n  result:\n    needs: [typecheck, cspell, prettier, stylelint, eslint]\n    runs-on: ubuntu-latest\n    name: Lint result\n    steps:\n      - run: echo \"Success\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: ⚠️ Release\n\non:\n  workflow_dispatch:\n    inputs:\n      mode:\n        type: choice\n        description: Bump version as requested\n        required: true\n        options:\n          - patch\n          - minor\n          - alpha\n          - major\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n      - uses: taiga-family/ci/actions/run/release-it@v1.201.0\n        id: release-it\n        with:\n          ref: ${{ github.ref }}\n          mode: ${{ github.event.inputs.mode }}\n          npmToken: ${{ secrets.NPM_TOKEN }}\n          githubToken: ${{ secrets.TAIGA_FAMILY_BOT_PAT }}\n\n      - uses: taiga-family/ci/actions/run/read-package-json@v1.201.0\n        id: info\n\n      - name: Announce to Telegram\n        if: steps.release-it.outputs.released == 'true'\n        uses: taiga-family/ci/actions/messenger/telegram/announce@v1.201.0\n        with:\n          chatId: ${{ secrets.TAIGA_TELEGRAM_CHAT_ID }}\n          topicId: ${{ secrets.TAIGA_TELEGRAM_CHAT_THREAD_ID }}\n          token: ${{ secrets.TAIGA_TELEGRAM_BOT_TOKEN }}\n          version: v${{ steps.info.outputs.version }}\n          textLink: '@maskito/core'\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Tests\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n\njobs:\n  tests:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: taiga-family/ci/actions/setup/variables@v1.201.0\n      - uses: taiga-family/ci/actions/setup/node@v1.201.0\n\n      - name: Run tests\n        run: npx nx run-many --target test --all --coverage\n\n      - name: Archive coverage artifacts\n        uses: actions/upload-artifact@v7.0.1\n        with:\n          name: coverage-${{ github.workflow }}-${{ github.run_id }}\n          path: coverage\n\n  codecov:\n    name: Collect coverage\n    needs: [tests]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: actions/download-artifact@v8.0.1\n        with:\n          name: coverage-${{ github.workflow }}-${{ github.run_id }}\n          path: coverage\n\n      - name: Display structure of coverage files\n        run: tree -L 2 ./coverage -P 'lcov.info'\n\n      - uses: codecov/codecov-action@v6.0.0\n        with:\n          directory: ./coverage/core/\n          flags: summary,core\n          name: core\n      - uses: codecov/codecov-action@v6.0.0\n        with:\n          directory: ./coverage/kit/\n          flags: summary,kit\n          name: kit\n      - uses: codecov/codecov-action@v6.0.0\n        with:\n          directory: ./coverage/phone/\n          flags: summary,phone\n          name: phone\n      - uses: codecov/codecov-action@v6.0.0\n        with:\n          directory: ./coverage/angular/\n          flags: summary,angular\n          name: angular\n      - uses: codecov/codecov-action@v6.0.0\n        with:\n          directory: ./coverage/react/\n          flags: summary,react\n          name: react\n      - uses: codecov/codecov-action@v6.0.0\n        with:\n          directory: ./coverage/vue/\n          flags: summary,vue\n          name: vue\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n"
  },
  {
    "path": ".gitignore",
    "content": "# compiled output\n/dist\n/tmp\n/out-tsc\n# Only exists if Bazel was run\n/bazel-out\n\n# dependencies\n**/node_modules/\n\n# profiling files\nchrome-profiler-events.json\nspeed-measure-plugin.json\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n*.iml\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# misc\n/.angular/cache\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n\n# cypress\n**/cypress/**/screenshots/**\nprojects/demo-integrations/cypress\n\n.nx\n.ssl\nRELEASE_BODY.md\n.cursor/rules/nx-rules.mdc\n.github/instructions/nx.instructions.md\n.rollup.cache\n"
  },
  {
    "path": ".husky/commit-msg",
    "content": "npx --no -- commitlint --edit $1\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "npx lint-staged\nnpm run typecheck\n"
  },
  {
    "path": ".npmrc",
    "content": "engine-strict=true\nloglevel=error\n"
  },
  {
    "path": ".release-it.js",
    "content": "module.exports = require('@taiga-ui/release-it-config');\n"
  },
  {
    "path": ".ws-context",
    "content": "{\n    \"framework\": \"angular\",\n    \"projects/vue/**\": {\n        \"framework\": \"vue\"\n    },\n    \"projects/react/**\": {\n        \"framework\": \"react\"\n    }\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "### [5.2.2](https://github.com/taiga-family/maskito/compare/v5.2.1...v5.2.2) (2026-03-31)\n\n### 🐞 Bug Fixes\n\n- **kit**: `Number` with `negativePattern=minusFirst` + `prefix` has unexpected caret shift on minus insertion (#2616)\n  [(1d7bc77)](https://github.com/taiga-family/maskito/commit/1d7bc7761f1e4c7dad3b01b6c651295dd6d31bc4)\n- **kit**: `Number` should ignore digits in affixes for `min`/`max` validation (#2615)\n  [(227a860)](https://github.com/taiga-family/maskito/commit/227a860453b4d0cee42a96e8ef06d155a44cce06)\n\n### [5.2.1](https://github.com/taiga-family/maskito/compare/v5.2.0...v5.2.1) (2026-03-27)\n\n### 🐞 Bug Fixes\n\n- **react**: omitted `d.ts` files (#2607)\n  [(a10e118)](https://github.com/taiga-family/maskito/commit/a10e1187c81cfd4af8935edd7d459c3e6b5c6cc4)\n- **kit**: `maskitoParseNumber` incorrectly parses postfix containing digits (#2600)\n  [(4805166)](https://github.com/taiga-family/maskito/commit/48051666d3235fe72c5bc90d83405175905ec7fa)\n\n### [5.2.0](https://github.com/taiga-family/maskito/compare/v5.1.2...v5.2.0) (2026-03-25)\n\n### 🚀 Features\n\n- **kit**: `Number` supports `thousandSeparatorPattern` (#2584)\n  [(f4e1340)](https://github.com/taiga-family/maskito/commit/f4e1340ddedd8abf04c181c8db31edf420564845)\n\n### 🐞 Bug Fixes\n\n- **kit**: validate digit count in time segment for `maskitoParseDateTime` (#2581)\n  [(9c3e9bc)](https://github.com/taiga-family/maskito/commit/9c3e9bccc046be9e9a91ea05255bf55985144df1)\n\n### [5.1.2](https://github.com/taiga-family/maskito/compare/v5.1.1...v5.1.2) (2026-03-13)\n\n### 🐞 Bug Fixes\n\n- **kit**: `maskitoParseDate` & `maskitoParseDateTime` supports parsing of dates in formats `mm/dd`, `dd/mm` (#2577)\n  [(a0f073b)](https://github.com/taiga-family/maskito/commit/a0f073b26b3be0d6bf776d970adc65c745cb3d41)\n- **kit**: `maskitoParseDate` should return `null` for invalid `Date` string (#2561)\n  [(cf2b5f2)](https://github.com/taiga-family/maskito/commit/cf2b5f206a6679d2171bfd25fdc969ffee6dad7f)\n\n### [5.1.1](https://github.com/taiga-family/maskito/compare/v5.1.0...v5.1.1) (2026-02-16)\n\n### 🐞 Bug Fixes\n\n- **kit**: `maskitoStringifyDate` incorrectly formats year with leading zeroes (#2538)\n  [(9817f08)](https://github.com/taiga-family/maskito/commit/9817f084dd34628afd1bebe8070189edf8f3fd1f)\n- **kit**: `Number` deletes the previous non-selected character on the first deletion (#2537)\n  [(40ef2e0)](https://github.com/taiga-family/maskito/commit/40ef2e03b8ecaf564a1d4f055b8d629ba8932370)\n- **phone**: `Phone` preserves previously entered digits on new digits paste (#2481)\n  [(f836f4f)](https://github.com/taiga-family/maskito/commit/f836f4f5ec94d865a2bcb44960161cfe25fe3a64)\n\n## [5.1.0](https://github.com/taiga-family/maskito/compare/v5.0.1...v5.1.0) (2026-01-22)\n\n### 🚀 Features\n\n- **phone:** `Phone` supports national format ([#2461](https://github.com/taiga-family/maskito/issues/2461))\n  ([c90bca2](https://github.com/taiga-family/maskito/commit/c90bca2c2f0f3bc3746a2559fc84bd688075c1be))\n\n### 🐞Bug Fixes\n\n- **kit:** `Number` fails to dynamically change postfix ([#2501](https://github.com/taiga-family/maskito/issues/2501))\n  ([cd73d6a](https://github.com/taiga-family/maskito/commit/cd73d6a729068e4b301509b12def6e149a7b5d66))\n- **kit:** `Number` throws `Failed to parse String to BigInt` error\n  ([#2509](https://github.com/taiga-family/maskito/issues/2509))\n  ([7b80f79](https://github.com/taiga-family/maskito/commit/7b80f79140740462d3d924ba49993e1376d2b6e3))\n- **react:** `useMaskito` should destroy Maskito instance if element is detached from the DOM\n  ([#2507](https://github.com/taiga-family/maskito/issues/2507))\n  ([1cdb203](https://github.com/taiga-family/maskito/commit/1cdb20359aaf4fa479420638d76bc7b238a6cb4c))\n\n### [5.0.1](https://github.com/taiga-family/maskito/compare/v5.0.0...v5.0.1) (2025-12-26)\n\n### 🐞 Bug Fixes\n\n- **phone**: `Phone` removes last digit if pasted without '+' (#2480)\n  [(5d709ca)](https://github.com/taiga-family/maskito/commit/5d709ca9c2e4a8abbe99a38c47551a9e6b2d8d36)\n- **kit**: `maskitoStringifyNumber` supports extremal exponent values (#2463)\n  [(1340257)](https://github.com/taiga-family/maskito/commit/1340257486a86499f94c96dbe67949ada32ebae4)\n- **phone**: `Phone` with initial value has problems with the first time delete action (#2455)\n  [(fa596fa)](https://github.com/taiga-family/maskito/commit/fa596fa101333b519bc3d760d6f4969151178e1a)\n\n## [5.0.0](https://github.com/taiga-family/maskito/compare/v4.0.1...v5.0.0) (2025-12-03)\n\n### ⚠ BREAKING CHANGES\n\n- Bump Safari browser support (#2439)\n\n  |                | < 5.0.0 | ≥ 5.0.0 |\n  | -------------- | ------- | ------- |\n  | Safari Desktop | 13.1+   | 14.1+   |\n  | Safari Mobile  | 13.4+   | 14.5+   |\n\n- **kit**: `Number` supports `BigInt` (#2431)\n  [(2d2f86d)](https://github.com/taiga-family/maskito/commit/2d2f86dafb1524528305908af27b4df37d9e1330)\n\n  New default values for `maskitoNumberOptionsGenerator` / `maskitoStringifyNumber`:\n\n  | MaskitoNumberParams | < 5.0.0                   | ≥ 5.0.0     |\n  | ------------------- | ------------------------- | ----------- |\n  | `min`               | `Number.MIN_SAFE_INTEGER` | `-Infinity` |\n  | `max`               | `Number.MAX_SAFE_INTEGER` | `Infinity`  |\n\n### [4.0.1](https://github.com/taiga-family/maskito/compare/v4.0.0...v4.0.1) (2025-11-14)\n\n### 🐞 Bug Fixes\n\n- **kit**: `Time` with `[step]` ignores `timeSegmentMinValues` on arrow stepping (#2420)\n  [(398a5c1)](https://github.com/taiga-family/maskito/commit/398a5c163d03502d413ca15c8756f41891b75197)\n- **kit**: `DateRange` with `[minLength]` & `[maxLength]` incorrectly appends month in backward direction (#2369)\n  [(3c80959)](https://github.com/taiga-family/maskito/commit/3c80959773a4c44b29e70a420bc1fc6e074030b9)\n\n## [4.0.0](https://github.com/taiga-family/maskito/compare/v3.11.1...v4.0.0) (2025-10-13)\n\n### ⚠ BREAKING CHANGES\n\n- **kit**: delete deprecated `precision` & `decimalZeroPadding` parameters from `Number` mask (#2354)\n\n  **Previous behavior:**\n\n  ```ts\n  import {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\n  const options = maskitoNumberOptionsGenerator({\n    precision: 2, // ---> Use `maximumFractionDigits` instead\n    decimalZeroPadding: true, // ---> Use `minimumFractionDigits` instead\n  });\n  ```\n\n  <p align=\"center\">⬇️ </p>\n\n  **New behavior**:\n\n  ```ts\n  import {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\n  const options = maskitoNumberOptionsGenerator({\n    maximumFractionDigits: 2,\n    minimumFractionDigits: 2,\n  });\n  ```\n\n- **kit**: `maskitoParseNumber` accepts only `MaskitoNumberParams` as the 2nd argument (#2355)\n\n  **Previous behavior:**\n\n  ```ts\n  import {maskitoParseNumber} from '@maskito/kit';\n\n  maskitoParseNumber(\n    '0,42',\n    ',', // decimalSeparator\n  );\n  ```\n\n  <p align=\"center\">⬇️ </p>\n\n  **New behavior**:\n\n  ```ts\n  import {maskitoParseNumber} from '@maskito/kit';\n\n  maskitoParseNumber(\n    '0,42',\n    {decimalSeparator: ','}, // MaskitoNumberParams\n  );\n  ```\n\n- **kit**: remove invalid `MM.SS.MSS` type from `MaskitoTimeMode` (use `MM:SS.MSS` instead) (#2365)\n- **angular**: bump minimum required Angular version (16+ => 19+) (#2347) (#2348) (#2349)\n- **angular**: `MaskitoDirective` uses model inputs (#2363)\n\n### [3.11.1](https://github.com/taiga-family/maskito/compare/v3.11.0...v3.11.1) (2025-09-30)\n\n### 🐞 Bug Fixes\n\n- **kit**: resolve circular dependencies inside `Number` mask (#2344)\n  [(efb3039)](https://github.com/taiga-family/maskito/commit/efb303980905c33bfe58e0163b74c72da5f83fcd)\n- **kit**: `Number` fails to clear initial value (by selecting all + Backspace/Delete) (#2343)\n  [(63f6e72)](https://github.com/taiga-family/maskito/commit/63f6e725af215dc492ddca02d30123de0dd026de)\n- **kit**: `Number` has broken support for postfix with leading point (#2337)\n  [(e9a3598)](https://github.com/taiga-family/maskito/commit/e9a3598c9ce7f5c39f932d4e6b3c0ffabcde3741)\n\n### [3.11.0](https://github.com/taiga-family/maskito/compare/v3.10.3...v3.11.0) (2025-09-23)\n\n### 🚀 Features\n\n- **kit**: `Number` supports minus before prefix (#2281)\n  [(480c1fd)](https://github.com/taiga-family/maskito/commit/480c1fde7693b62df768364c0df00fc7328cb4e6)\n- **kit**: `Number` uses `toNumberParts` / `fromNumberParts` approach (#2270)\n  [(891780a)](https://github.com/taiga-family/maskito/commit/891780a8f179345a49dbe8b8036e639ae0a98cbd)\n\n### 🐞 Bug Fixes\n\n- **kit**: `PostfixPostprocessor` duplicates postfix on paste of value with incompleted postfix (#2267)\n  [(2707771)](https://github.com/taiga-family/maskito/commit/27077719ffc8628758664638e802e1ad3c9f8e27)\n- **kit**: `maskitoStringifyTime` and `maskitoParseTime` should support `AM` / `PM` formats (#2260)\n  [(a0aea6f)](https://github.com/taiga-family/maskito/commit/a0aea6f741fea3139f4e7d7c8f84ce46c1738c26)\n- **angular**: use `@Input` setters instead of `ngOnChanges` to handle programmatic changes (#2257)\n  [(cb8c129)](https://github.com/taiga-family/maskito/commit/cb8c129f1afd196a38f87dd4b36328ddea3b60a5)\n\n### [3.10.3](https://github.com/taiga-family/maskito/compare/v3.10.2...v3.10.3) (2025-08-06)\n\n### 🐞 Bug Fixes\n\n- **kit**: `DateRange` + `minLength` / `maxLength` has incorrect limits (#2210)\n  [(e8917e0)](https://github.com/taiga-family/maskito/commit/e8917e0a124b26ffc9806f74c5c70016084f5280)\n- **kit**: `maskitoStringifyNumber` fails to stringify number with exponential notation (#2224)\n  [(9fe0b08)](https://github.com/taiga-family/maskito/commit/9fe0b080b2703a72674a25b8ce352486cc274663)\n\n### [3.10.2](https://github.com/taiga-family/maskito/compare/v3.10.1...v3.10.2) (2025-07-28)\n\n### 🐞 Bug Fixes\n\n- **kit**: `Number` with `input[maxlength]` is incompatible with `document.execCommand('delete')` (#2217)\n  [(2604d2c)](https://github.com/taiga-family/maskito/commit/2604d2ce8dc60e16f464a3fc4328f907bef58d55)\n\n### [3.10.1](https://github.com/taiga-family/maskito/compare/v3.10.0...v3.10.1) (2025-07-18)\n\n### 🐞 Bug Fixes\n\n- **core**: dynamic mask switching to mask without fixed character fails on new character insertion (#2207)\n  [(50e68d4)](https://github.com/taiga-family/maskito/commit/50e68d4bb8f6e5330bda76c67806514a7fa53294)\n\n### [3.10.0](https://github.com/taiga-family/maskito/compare/v3.9.1...v3.10.0) (2025-07-04)\n\n### 🚀 Features\n\n- **kit**: `Time` supports `prefix` & `postfix` parameters (#2185)\n  [(2cc7462)](https://github.com/taiga-family/maskito/commit/2cc7462583a2fe372d0cad312fb5f0d90ca0fe8e)\n\n### 🐞 Bug Fixes\n\n- **core**: invalid behavior of dynamic mask expression with trailing fixed characters (#2184)\n  [(cecf9d6)](https://github.com/taiga-family/maskito/commit/cecf9d69468e56de8ff4f39af7ebc07d5a686fe8)\n- **core**: do not insert fixed character on attempt to enter invalid character at its position (#2181)\n  [(7a51702)](https://github.com/taiga-family/maskito/commit/7a51702361237a41cd9bbdcdbb8e46d0bfa2e4bc)\n- **kit**: date-related mask with month-first mode has incorrect zero-padding logic (#2166)\n  [(26294e8)](https://github.com/taiga-family/maskito/commit/26294e8250591c727f99ccec563e8492df7c1068)\n\n### [3.9.1](https://github.com/taiga-family/maskito/compare/v3.9.0...v3.9.1) (2025-06-23)\n\n### 🐞 Bug Fixes\n\n- **kit**: `Number` with custom `minusSign` has broken `min`/`max` behavior (#2164)\n  [(52ed25d)](https://github.com/taiga-family/maskito/commit/52ed25debaa2838a0b360983a508a3d627c78277)\n- **core**: Android with Microsoft SwiftKey Keyboard ignores `preventDefault()` for `beforeinput` event on backspace\n  (#2163) [(722d9af)](https://github.com/taiga-family/maskito/commit/722d9afc265df8392613c770759df3bb8955e08e)\n\n### [3.9.0](https://github.com/taiga-family/maskito/compare/v3.8.0...v3.9.0) (2025-06-05)\n\n### 🚀 Features\n\n- **react**: add support for React-specific `onChange` event handler (#2153)\n  [(e941847)](https://github.com/taiga-family/maskito/commit/e941847990662835343c4e25d3f2b2e64ab54345)\n\n### 🐞 Bug Fixes\n\n- **core**: do not unnecessarily trigger element's `value` setter on every keystroke (#2152)\n  [(fd3449b)](https://github.com/taiga-family/maskito/commit/fd3449b69f88dbcab5b06b03ff19273b511bcd64)\n\n### [3.8.0](https://github.com/taiga-family/maskito/compare/v3.7.2...v3.8.0) (2025-05-13)\n\n### 🚀 Features\n\n- **angular**: new `MaskitoPattern` directive (#2081)\n  [(c3f7142)](https://github.com/taiga-family/maskito/commit/c3f7142245b603af9136541de9d181189e01a7a3)\n\n### 🐞 Bug Fixes\n\n- **kit**: update the first digit zero-padding logic for date-related mask (#2117)\n  [(b5b2598)](https://github.com/taiga-family/maskito/commit/b5b2598f455f3ad3438c3bd89b81009aca82f17c)\n- **core**: incorrect handle of paste event for `&lt;input /&gt;` with `maxlength` attribute (#2090)\n  [(e20e50b)](https://github.com/taiga-family/maskito/commit/e20e50bb92aca9d70bc483f9fc66904264a64c35)\n- **kit**: `Number` should support non-erasable minus (as `prefix`) for `max &lt;= 0` (#2087)\n  [(3910914)](https://github.com/taiga-family/maskito/commit/39109144075d58734d1545be888cbd03c5b6286e)\n\n### [3.7.2](https://github.com/taiga-family/maskito/compare/v3.7.1...v3.7.2) (2025-04-22)\n\n### 🐞 Bug Fixes\n\n- **kit**: missing export of `maskitoParseDateTime` & `maskitoStringifyDateTime` utilities (#2074)\n  [(6aa34aa)](https://github.com/taiga-family/maskito/commit/6aa34aa610cf140248bc7a691beb5aaba1f0e0cd)\n\n### [3.7.1](https://github.com/taiga-family/maskito/compare/v3.7.0...v3.7.1) (2025-04-16)\n\n### 🐞 Bug Fixes\n\n- **core**: updated selection range (even if textfield value is untouched) should not be ignored (#2069)\n  [(9276117)](https://github.com/taiga-family/maskito/commit/927611775e8d23eb89663150cd84e7981b12d2e7)\n\n### [3.7.0](https://github.com/taiga-family/maskito/compare/v3.6.0...v3.7.0) (2025-04-15)\n\n### 🚀 Features\n\n- **kit**: new `maskitoParseDateTime` and `maskitoStringifyDateTime` helpers (#2055)\n  [(5028084)](https://github.com/taiga-family/maskito/commit/5028084f9be876cf8b1dc2607956cd4906285c43)\n\n### 🐞 Bug Fixes\n\n- **core**: add possibility to overwrites `selection` property in processors (#2053)\n  [(de354f4)](https://github.com/taiga-family/maskito/commit/de354f4fbeed7a632e23c0e1d00809effbb0229b)\n\n### [3.6.0](https://github.com/taiga-family/maskito/compare/v3.5.0...v3.6.0) (2025-04-08)\n\n### 🚀 Features\n\n- **kit**: `Number` supports new properties `minimumFractionDigits` & `maximumFractionDigits` (#2022)\n  [(8719b9e)](https://github.com/taiga-family/maskito/commit/8719b9e30d6463deff4aed213cba774189ddd305)\n\n### 🐞 Bug Fixes\n\n- **core**: double space bar removes characters (#2040)\n  [(ccbebd8)](https://github.com/taiga-family/maskito/commit/ccbebd878ae7ba92da0a8d25d5b9d0b5c3ed3bcf)\n\n### [3.5.0](https://github.com/taiga-family/maskito/compare/v3.4.0...v3.5.0) (2025-03-21)\n\n### 🚀 Features\n\n- **kit**: `Time` supports `MM:SS` mode (#2008)\n  [(b93ad1e)](https://github.com/taiga-family/maskito/commit/b93ad1ecc71f608dd68de01b43487153b8e89d95)\n\n### 🐞 Bug Fixes\n\n- **kit**: `maskitoParseDate` should return `null` for incompleted date string (#2009)\n  [(9eec35b)](https://github.com/taiga-family/maskito/commit/9eec35b878411a79fec84986cbea94fbdc9f24d8)\n\n### [3.4.0](https://github.com/taiga-family/maskito/compare/v3.3.0...v3.4.0) (2025-03-10)\n\n### 🚀 Features\n\n- **kit**: new `maskitoStringifyNumber` helper (#1987)\n  [(cbfd4bc)](https://github.com/taiga-family/maskito/commit/cbfd4bc4bb6ca56bf12667bb3626c55ae1b04c48)\n\n### 🐞 Bug Fixes\n\n- **phone**: `Phone` should accept incomplete phone number of selected country (even with `strict=true`) (#1982)\n  [(965d735)](https://github.com/taiga-family/maskito/commit/965d7358ad39888d3844c121dd6934ee66cdc541)\n\n### [3.3.0](https://github.com/taiga-family/maskito/compare/v3.2.1...v3.3.0) (2025-02-28)\n\n### 🚀 Features\n\n- **kit**: new `maskitoParseDate` and `maskitoStringifyDate` helpers (#1973)\n  [(208a4ab)](https://github.com/taiga-family/maskito/commit/208a4abc8018b368d3154ebc26a81504b6abec3d)\n- **kit**: `Date` supports `dd/mm` and `mm/dd` modes (#1939)\n  [(bc290af)](https://github.com/taiga-family/maskito/commit/bc290affdcdc1cd6e088a32a60dc5e74fd00a1d8)\n\n### 🐞 Bug Fixes\n\n- **kit**: `SelectionChangeHandler` does not work for Safari after programmatic update of textfield value (#1930)\n  [(34c11d0)](https://github.com/taiga-family/maskito/commit/34c11d0ee88b861ab21d54113aff21f3091a053f)\n\n### [3.2.1](https://github.com/taiga-family/maskito/compare/v3.2.0...v3.2.1) (2024-12-26)\n\n### 🚀 Features\n\n- **kit**: remove circular import (#1861)\n  [(15ff0b8)](https://github.com/taiga-family/maskito/commit/15ff0b8558bc954ac6eda07bdb13d087fc2f3491)\n\n### 🐞 Bug Fixes\n\n- **kit**: `Number` should ignore `[decimalSeparator]` value if `[precision]=0` (#1908)\n  [(19effe2)](https://github.com/taiga-family/maskito/commit/19effe2c7218646335b2f08c53a1ed3c3f0d89a1)\n- **kit**: `Number` + postfix (with leading space) adds unnecessary spaces on paste value with trailing spaces (#1865)\n  [(c37b1d6)](https://github.com/taiga-family/maskito/commit/c37b1d636fefee1cba17b4aa07ccdd30edc5ff66)\n- **kit**: `DateRange` should accept single character date segment paste even if date and range separators are equal\n  (#1796) [(be6a4c3)](https://github.com/taiga-family/maskito/commit/be6a4c3c57132cf320ec462372fd8536dca4781a)\n\n### [3.2.0](https://github.com/taiga-family/maskito/compare/v3.1.2...v3.2.0) (2024-10-29)\n\n### 🚀 Features\n\n- **kit**: new `maskitoSelectionChangeHandler` plugin (#1794)\n  [(c6e9a4d)](https://github.com/taiga-family/maskito/commit/c6e9a4d9b1a2e75bc44aaecbda840b84f786d065)\n\n### [3.1.2](https://github.com/taiga-family/maskito/compare/v3.1.1...v3.1.2) (2024-10-22)\n\n### 🐞 Bug Fixes\n\n- **kit**: `Time` & `DateTime` has conflicts between `step` & `AM/PM` features (#1791)\n  [(805f70b)](https://github.com/taiga-family/maskito/commit/805f70b74e04fb3b8613f89d84e771c734438dab)\n- **kit**: `Number` incorrectly shift caret for 1st time insertion into textfield with initial value (#1792)\n  [(0049d91)](https://github.com/taiga-family/maskito/commit/0049d91a0a498977bb5f4cba9fbf9f02cb74dae9)\n\n### [3.1.1](https://github.com/taiga-family/maskito/compare/v3.1.0...v3.1.1) (2024-10-17)\n\n### 🐞 Bug Fixes\n\n- **kit**: `Number` fails to prevent user insertion of extra spaces on invalid positions (#1789)\n  [(a40445c)](https://github.com/taiga-family/maskito/commit/a40445cf4d852328a9310a55cf38801e17525476)\n- **kit**: `DateTime` fails to process value without any separators (paste from clipboard) (#1779)\n  [(1733422)](https://github.com/taiga-family/maskito/commit/1733422b803fda3de9b40a9fa675ef6bb8b5195e)\n\n### [3.1.0](https://github.com/taiga-family/maskito/compare/v3.0.3...v3.1.0) (2024-10-09)\n\n### 🚀 Features\n\n- **kit**: `Time` & `DateTime` support `AM` / `PM` formats (#1708)\n  [(98ce35e)](https://github.com/taiga-family/maskito/commit/98ce35e8fd3318a750959d840f36caaf427fe8f0)\n- **kit**: simplify some code logic for `Time` mask (#1688)\n  [(8c608b8)](https://github.com/taiga-family/maskito/commit/8c608b8cb5eaeca1166b78c6691d38303eb67c6c)\n\n### 🐞 Bug Fixes\n\n- **core**: `overwriteMode: replace` has incorrect behavior on attempt to insert invalid characters (#1772)\n  [(5aeb074)](https://github.com/taiga-family/maskito/commit/5aeb0741fa82ad6e43e862059a17b2e78ee9831b)\n\n### [3.0.3](https://github.com/taiga-family/maskito/compare/v3.0.2...v3.0.3) (2024-09-25)\n\n### 🐞 Bug Fixes\n\n- **angular**: race condition when `[maskitoOptions]` are changed before long element predicate is resolved (#1696)\n  [(9f9bad3)](https://github.com/taiga-family/maskito/commit/9f9bad3036774fa51350c3c8402cf57f15e789d6)\n- **kit**: `Time` has invalid segment separator for `MM:SS.MSS` mode (#1687)\n  [(93972be)](https://github.com/taiga-family/maskito/commit/93972be370e1abf4278497b11f61d3c923ae5caa)\n- **core**: incorrect behavior of `overwriteMode = replace` if selection contains several characters (#1685)\n  [(67c3c10)](https://github.com/taiga-family/maskito/commit/67c3c10704f62efff4c47f1ad802859d54257752)\n- **react**: race condition when `options` are changed before long element predicate is resolved (#1651)\n  [(f2932ce)](https://github.com/taiga-family/maskito/commit/f2932ce10ec80a1080befaee9e5c235bc41a1b16)\n\n### [3.0.2](https://github.com/taiga-family/maskito/compare/v3.0.1...v3.0.2) (2024-09-20)\n\n### 🐞 Bug Fixes\n\n- **core:** `Time` with `[step]` has unexpected cursor jump to the next segment on `ArrowUp`/`ArrowDown`\n  ([#1478](https://github.com/taiga-family/maskito/issues/1478))\n  ([59a5927](https://github.com/taiga-family/maskito/commit/59a5927822e2c20691dc0948c438d67d497b6381))\n- **core:** fix scroll for masked narrow textfields ([#1645](https://github.com/taiga-family/maskito/issues/1645))\n  ([c6d2828](https://github.com/taiga-family/maskito/commit/c6d282873f10892ecb3536b878d919fc57f5c921))\n\n### [3.0.1](https://github.com/taiga-family/maskito/compare/v3.0.0...v3.0.1) (2024-08-19)\n\n### 🐞 Bug Fixes\n\n- **kit:** `maskitoStringifyTime` was adding `0` on the wrong side\n  ([#1401](https://github.com/taiga-family/maskito/issues/1401))\n  ([b28ee12](https://github.com/taiga-family/maskito/commit/b28ee12f923b86eb3a8c32d17cd401e9222cfc30))\n- **kit:** `Placeholder` should support partial programmatic removal of placeholder's characters\n  ([#1441](https://github.com/taiga-family/maskito/issues/1441))\n  ([146a557](https://github.com/taiga-family/maskito/commit/146a55723ec4a1ac8b9cfba254056b84173326c9))\n- **kit:** `Time` incorrectly validates value if `timeSegmentMaxValues` includes single digit\n  ([#1402](https://github.com/taiga-family/maskito/issues/1402))\n  ([26670f4](https://github.com/taiga-family/maskito/commit/26670f4dbdfb84495ea0faa127868185d7bb0765))\n\n## [3.0.0](https://github.com/taiga-family/maskito/compare/v2.5.0...v3.0.0) (2024-07-18)\n\n### ⚠ BREAKING CHANGES\n\n- **phone:** remove built-in `RemoveOnBlur` / `AddOnFocus` plugins from `@maskito/phone`\n  ([#1352](https://github.com/taiga-family/maskito/issues/1352))\n\n  Learn more: https://maskito.dev/addons/phone#focus-blur\n\n- **angular:** bump minimum required Angular version (15+ => 16+)\n  ([#1328](https://github.com/taiga-family/maskito/issues/1328))\n\n- **angular:** delete deprecated `MaskitoModule` & `MaskitoCVA`\n  ([#1391](https://github.com/taiga-family/maskito/issues/1391))\n\n### 🚀 Features\n\n- **core:** new built-in `maskitoChangeEventPlugin` ([#1338](https://github.com/taiga-family/maskito/issues/1338))\n\n  Learn more: https://maskito.dev/core-concepts/plugins#change-event\n\n## [2.5.0](https://github.com/taiga-family/maskito/compare/v2.4.0...v2.5.0) (2024-06-24)\n\n### 🚀 Features\n\n- **kit:** new `maskitoParseTime` and `maskitoStringifyTime` utils\n  ([#1302](https://github.com/taiga-family/maskito/issues/1302))\n  ([d0f9b13](https://github.com/taiga-family/maskito/commit/d0f9b1331f3bb18403691ac7c513c31f5123cf78))\n\n### 🐞 Bug Fixes\n\n- **core:** correct handling of browser autofill/suggestion in Firefox\n  ([#1326](https://github.com/taiga-family/maskito/issues/1326))\n  ([a049207](https://github.com/taiga-family/maskito/commit/a049207b355da72092948a8c556020062fb7c819))\n- **kit:** `Date`, `DateRange`, `DateTime` supports multi-character date segments separator\n  ([#1306](https://github.com/taiga-family/maskito/issues/1306))\n  ([cdf2fae](https://github.com/taiga-family/maskito/commit/cdf2faee4c16cd3963557a511d4ec053e2d41fc0))\n- **kit:** move caret after attempt to erase fixed character in a mask with `Placeholder`\n  ([#1307](https://github.com/taiga-family/maskito/issues/1307))\n  ([87ae431](https://github.com/taiga-family/maskito/commit/87ae431ded798e3c31d6247f965a00c27ddad3f1))\n\n## [2.4.0](https://github.com/taiga-family/maskito/compare/v2.3.2...v2.4.0) (2024-06-03)\n\n### 🚀 Features\n\n- **kit:** `Time` & `DateTime` support increment / decrement of time segment via `ArrowUp` / `ArrowDown`\n  ([#1223](https://github.com/taiga-family/maskito/issues/1223))\n  ([af961b8](https://github.com/taiga-family/maskito/commit/af961b84f8765e7d2147c80210e3a8ac6ed30597))\n- **kit:** `Time` supports `SS.MSS` & `MM.SS.MSS` modes ([#1224](https://github.com/taiga-family/maskito/issues/1224))\n  ([7bed4bc](https://github.com/taiga-family/maskito/commit/7bed4bcaac14908e7e445b277f5b4b6e5b0fd281))\n\n### 🐞 Bug Fixes\n\n- **core:** add `.select()`-method support for `MaskitoElement`\n  ([#1268](https://github.com/taiga-family/maskito/issues/1268))\n  ([51f5934](https://github.com/taiga-family/maskito/commit/51f5934f382b7862a6653412b687c46fd318b0bb))\n- **kit:** `Number` should support float `min`/`max`-parameters in range -1 < x < 1\n  ([#1280](https://github.com/taiga-family/maskito/issues/1280))\n  ([b44013e](https://github.com/taiga-family/maskito/commit/b44013e0a45ffcfa69564f13d634a79d45b4d926))\n\n### [2.3.2](https://github.com/taiga-family/maskito/compare/v2.3.1...v2.3.2) (2024-05-16)\n\n### 🐞 Bug Fixes\n\n- **kit:** `Number` pads integer part with zero if user selects all and then types decimal separator\n  ([#1220](https://github.com/taiga-family/maskito/issues/1220))\n  ([8371e45](https://github.com/taiga-family/maskito/commit/8371e45767150ebc4db03a2b74c68afd6fe1e593))\n- **phone:** revert mistakenly fixated `libphonenumber-js` peer-dependency to just `>=1.0.0`\n  ([#1234](https://github.com/taiga-family/maskito/issues/1234))\n  ([27ee4a1](https://github.com/taiga-family/maskito/commit/27ee4a1264c0a70a5a06427368b8d18ed0e25bd4))\n- **react:** revert mistakenly fixated `react` & `react-demo` peer-dependencies to just `>=16.8`\n  ([#1231](https://github.com/taiga-family/maskito/issues/1231))\n  ([ae89d6f](https://github.com/taiga-family/maskito/commit/ae89d6ff549dfb21d7db56b26e3c1f3a7044a817))\n- **vue:** revert mistakenly fixated `vue` peer-dependency to just `>=3.0.0`\n  ([#1232](https://github.com/taiga-family/maskito/issues/1232))\n  ([22d84e2](https://github.com/taiga-family/maskito/commit/22d84e2f731ae8798f457466be7c9538d2f40fd9))\n\n### [2.3.1](https://github.com/taiga-family/maskito/compare/v2.3.0...v2.3.1) (2024-04-23)\n\n### 🐞 Bug Fixes\n\n- **kit:** `Number` should drop decimal separator if all digits are erased\n  ([#1211](https://github.com/taiga-family/maskito/issues/1211))\n  ([5836c96](https://github.com/taiga-family/maskito/commit/5836c965d6ce5ad497aaa59118204adc3e8625d8))\n- **kit:** `Number` with `decimalZeroPadding=true` should erase everything on `.00`\n  ([#1207](https://github.com/taiga-family/maskito/issues/1207))\n  ([d72f225](https://github.com/taiga-family/maskito/commit/d72f2257cec1a023aa81bb7de62e9543404630bd))\n- **kit:** `Placeholder` can have now the same character as textfield's value\n  ([#1209](https://github.com/taiga-family/maskito/issues/1209))\n  ([ed06936](https://github.com/taiga-family/maskito/commit/ed06936c41297cbd2e8ed308558914e9ad6c2eda))\n\n## [2.3.0](https://github.com/taiga-family/maskito/compare/v2.2.0...v2.3.0) (2024-04-16)\n\n### 🚀 Features\n\n- **core:** add `contenteditable` support ([#1039](https://github.com/taiga-family/maskito/issues/1039))\n  ([0d5bb31](https://github.com/taiga-family/maskito/commit/0d5bb319225fb61f3ac7643c21208122b4a2a2ae))\n- **kit:** `DateTime` supports configurable parameter `dateTimeSeparator`\n  ([#1143](https://github.com/taiga-family/maskito/issues/1143))\n  ([ec86284](https://github.com/taiga-family/maskito/commit/ec8628467814cff7dfae22668370236f402d8146))\n\n### 🐞 Bug Fixes\n\n- **kit:** `Date` formatting errors for `mm/yyyy`, `yyyy/mm`, `mm/yy` modes\n  ([#1177](https://github.com/taiga-family/maskito/issues/1177))\n  ([948a350](https://github.com/taiga-family/maskito/commit/948a35098da2233bc78793eb7e83b7c5136becbd))\n\n## [2.2.0](https://github.com/taiga-family/maskito/compare/v2.1.0...v2.2.0) (2024-03-07)\n\n### 🚀 Features\n\n- **kit:** `Number` supports new configurable parameter `minusSign`\n  ([#1118](https://github.com/taiga-family/maskito/issues/1118))\n  ([a7bec35](https://github.com/taiga-family/maskito/commit/a7bec35f19d7dfa4023ad83fa36a935b2d636fc7))\n\n### 🐞 Bug Fixes\n\n- totally disable `Maskito` if nullable options are passed inside `@maskito/{angular,react,vue}`\n  ([#1117](https://github.com/taiga-family/maskito/issues/1117))\n  ([8cbadcf](https://github.com/taiga-family/maskito/commit/8cbadcfdf9af283dc687b131361f7bb19a7f9b02))\n\n## [2.1.0](https://github.com/taiga-family/maskito/compare/v2.0.2...v2.1.0) (2024-03-04)\n\n### 🚀 Features\n\n- **kit:** `Date` & `DateRange` & `DateTime` has improved zero-padding support for browser autofill & IME composition\n  ([#1027](https://github.com/taiga-family/maskito/issues/1027))\n  ([77ac01c](https://github.com/taiga-family/maskito/commit/77ac01ca0b5e61d36dc3240a35c3dc93ce5fe93c))\n- **kit:** add full-width numbers support for `Time`, `Date`, `DateTime`, `DateRange`\n  ([#1043](https://github.com/taiga-family/maskito/issues/1043))\n  ([434c9c5](https://github.com/taiga-family/maskito/commit/434c9c5f349ab3c19e11722e95313c5763203b08))\n\n### 🐞 Bug Fixes\n\n- **kit:** `maskitoParseNumber` should interpret japanese prolonged sound mark as pseudo minus\n  ([#1115](https://github.com/taiga-family/maskito/issues/1115))\n  ([b152698](https://github.com/taiga-family/maskito/commit/b152698fda8ac671286eb5f4a29de62562934fa2))\n\n### [2.0.2](https://github.com/taiga-family/maskito/compare/v2.0.1...v2.0.2) (2024-02-01)\n\n### 🐞 Bug Fixes\n\n- **kit:** `Number` with initial value has problems with the first time input\n  ([#986](https://github.com/taiga-family/maskito/issues/986))\n  ([e40d3ff](https://github.com/taiga-family/maskito/commit/e40d3ff93c668c8afa60cd347faa7ebec76d0e6a))\n- **react:** `@maskito/react` includes again missing `cjs` module format\n  ([#991](https://github.com/taiga-family/maskito/issues/991))\n  ([18e3e0c](https://github.com/taiga-family/maskito/commit/18e3e0cf8911fa764a73e2e937081186f1dcde79))\n\n### [2.0.1](https://github.com/taiga-family/maskito/compare/v2.0.0...v2.0.1) (2024-01-31)\n\n### 🐞 Bug Fixes\n\n- **core:** `maskitoUpdateElement` should not dispatch `InputEvent` if value is not changed\n  ([#977](https://github.com/taiga-family/maskito/issues/977))\n  ([2410b64](https://github.com/taiga-family/maskito/commit/2410b6478c88f4d530b4469d7d50b1e4663d1572))\n- **core:** don't execute `setSelectionRange` if element is not focused\n  ([#937](https://github.com/taiga-family/maskito/issues/937))\n  ([92f288b](https://github.com/taiga-family/maskito/commit/92f288b677dbe77f7978308dd7b1612d6bfd68fb))\n- **kit:** `Number` rejects the first time input of full width digits\n  ([#955](https://github.com/taiga-family/maskito/issues/955))\n  ([c416884](https://github.com/taiga-family/maskito/commit/c41688488630e83d69eba795580916145e5fe17c))\n- **react:** `@maskito/react` library should not include `core-js` imports\n  ([#962](https://github.com/taiga-family/maskito/issues/962))\n  ([3b7e401](https://github.com/taiga-family/maskito/commit/3b7e4014029fae206020723c18762f08e92b8c41))\n\n## [2.0.0](https://github.com/taiga-family/maskito/compare/v1.9.0...v2.0.0) (2024-01-22)\n\n### ⚠ BREAKING CHANGES\n\n- **core:** merge `MaskitoElementPredicate` & `MaskitoElementPredicateAsync` into single type\n  ([#757](https://github.com/taiga-family/maskito/issues/757))\n- **core:** remove value's calibration on initialization + new `maskitoInitialCalibrationPlugin`\n  ([#778](https://github.com/taiga-family/maskito/issues/778))\n- **core:** bump Firefox browser support (55+ => 87+) ([#876](https://github.com/taiga-family/maskito/issues/876)) and\n  drop legacy fallbacks for `Firefox` ([#756](https://github.com/taiga-family/maskito/issues/756))\n- **kit:** delete deprecated `separator` for `DateRange` (use `dateSeparator` instead)\n  ([#790](https://github.com/taiga-family/maskito/issues/790))\n- **angular:** bump minimum required Angular version (12+ => 15+)\n  ([#710](https://github.com/taiga-family/maskito/issues/710))\n  ([#720](https://github.com/taiga-family/maskito/issues/720))\n  ([#725](https://github.com/taiga-family/maskito/issues/725))\n- **angular:** deprecate `MaskitoModule` (use standalone `MaskitoDirective`, `MaskitoCVA`, `MaskitoPipe`)\n  ([#754](https://github.com/taiga-family/maskito/issues/754))\n\n### 🚀 More features\n\n- **core:** new built-in `maskitoStrictCompositionPlugin` ([#881](https://github.com/taiga-family/maskito/issues/881))\n- **kit:** `Number` allows to enter full width numbers ([#864](https://github.com/taiga-family/maskito/issues/864))\n\n### 🐞 Bug Fixes\n\n- **core:** drop some excess dispatches of `Input`-event ([#882](https://github.com/taiga-family/maskito/issues/882))\n- **kit:** add `{bubbles:true}` for `input` events inside all built-in plugins to support `ReactSyntheticEvent`\n  ([#806](https://github.com/taiga-family/maskito/issues/806))\n- **kit:** `Number` has problems when prefix/postfix includes `decimalSeparator` symbol\n  ([#874](https://github.com/taiga-family/maskito/issues/874))\n  ([#816](https://github.com/taiga-family/maskito/issues/816))\n  ([#921](https://github.com/taiga-family/maskito/issues/921))\n- **kit:** `Placeholder` is not compatible with `maskitoEventHandler` + `focus`/`blur` events\n  ([#928](https://github.com/taiga-family/maskito/pull/928))\n\n## [1.9.0](https://github.com/taiga-family/maskito/compare/v1.8.2...v1.9.0) (2023-11-23)\n\n### 🚀 Features\n\n- **phone:** add ability to configure the separator ([#685](https://github.com/taiga-family/maskito/issues/685))\n  ([ab6bb11](https://github.com/taiga-family/maskito/commit/ab6bb11b1b40e069d31598b676c04456329aaf64))\n\n### [1.8.2](https://github.com/taiga-family/maskito/compare/v1.8.1...v1.8.2) (2023-11-16)\n\n### 🐞 Bug Fixes\n\n- **kit:** `PrefixPostprocessor` has problems with multi-character prefix\n  ([#669](https://github.com/taiga-family/maskito/issues/669))\n  ([be459e5](https://github.com/taiga-family/maskito/commit/be459e51f3cbf028fa36b1b6a57e47d7fe8482a3))\n\n### [1.8.1](https://github.com/taiga-family/maskito/compare/v1.8.0...v1.8.1) (2023-10-19)\n\n### 🐞 Bug Fixes\n\n- **kit:** `Date` accept single character date segment during paste\n  ([#610](https://github.com/taiga-family/maskito/issues/610))\n  ([e493198](https://github.com/taiga-family/maskito/commit/e4931987c2fad37894ea07f658f08e35152040df))\n\n## [1.8.0](https://github.com/taiga-family/maskito/compare/v1.7.0...v1.8.0) (2023-10-18)\n\n### 🚀 Features\n\n- **angular:** allow nullable options ([#605](https://github.com/taiga-family/maskito/issues/605))\n  ([21eaa7c](https://github.com/taiga-family/maskito/commit/21eaa7c0c0e7d5173c6f070f5222ba6492e196a6))\n\n### 🐞 Bug Fixes\n\n- **kit:** `Number` has broken zero padding when `decimalSeparator` equals to non-default value\n  ([#586](https://github.com/taiga-family/maskito/issues/586))\n  ([7241761](https://github.com/taiga-family/maskito/commit/72417614dd4974c22854dfacc2ee35044c080074))\n\n## [1.7.0](https://github.com/taiga-family/maskito/compare/v1.6.0...v1.7.0) (2023-09-15)\n\n### 🚀 Features\n\nNew `@maskito/phone` library ([#425](https://github.com/taiga-family/maskito/pull/425))\n([#482](https://github.com/taiga-family/maskito/issues/482))\n\nLearn more: https://maskito.dev/addons/phone\n\n## [1.6.0](https://github.com/taiga-family/maskito/compare/v1.5.1...v1.6.0) (2023-09-15)\n\n### 🚀 Features\n\n- **react:** `elementPredicate` can accept asynchronous predicate\n  ([#502](https://github.com/taiga-family/maskito/issues/502))\n  ([4bbf758](https://github.com/taiga-family/maskito/commit/4bbf758107ed4b2fdbde5a241f22c0f363c22104))\n\n### 🐞 Bug Fixes\n\n- **kit:** `Number` should accept all types of spaces as interchangeable characters for `thousandSeparator`\n  ([#505](https://github.com/taiga-family/maskito/issues/505))\n  ([73975bb](https://github.com/taiga-family/maskito/commit/73975bbc676487330359056c367f73e32ea6eaf4))\n\n### [1.5.1](https://github.com/taiga-family/maskito/compare/v1.5.0...v1.5.1) (2023-09-08)\n\n### 🐞 Bug Fixes\n\n- **vue:** `elementPredicate` should accept `MaskitoElementPredicateAsync` type\n  ([#487](https://github.com/taiga-family/maskito/issues/487))\n  ([fe7e9dc](https://github.com/taiga-family/maskito/commit/fe7e9dcb468bf3ab30978c947d8fa21cc0e51a75))\n\n## [1.5.0](https://github.com/taiga-family/maskito/compare/v1.4.0...v1.5.0) (2023-09-04)\n\n### 🚀 Features\n\n- **core:** add IME composition support ([#467](https://github.com/taiga-family/maskito/issues/467))\n  ([e7d664b](https://github.com/taiga-family/maskito/commit/e7d664b66a008a742c0a532e341b0e0bb0a0f759))\n- **demo:** documentation is now available at https://maskito.dev\n  ([#392](https://github.com/taiga-family/maskito/issues/392))\n  ([355f87f](https://github.com/taiga-family/maskito/commit/355f87fd536758bc2db59f760ed114d28264122a))\n\n### 🐞 Bug Fixes\n\n- **kit:** `maskitoCaretGuard` doesn't work after focus on `<select />`\n  ([#462](https://github.com/taiga-family/maskito/issues/462))\n  ([9f456da](https://github.com/taiga-family/maskito/commit/9f456dad7f7f9d02db4eb5993ecb5fb5aabfe613))\n- **kit:** `Number` should drop items from `decimalPseudoSeparators` if any is equal to `thousandSeparator`\n  ([#390](https://github.com/taiga-family/maskito/issues/390))\n  ([2107adc](https://github.com/taiga-family/maskito/commit/2107adc445ed26ce1507c5e0c534b668d7ae5b12))\n\n## [1.4.0](https://github.com/taiga-family/maskito/compare/v1.3.0...v1.4.0) (2023-07-27)\n\n### 🚀 Features\n\n- **kit:** `Date` & `DateRange` support new modes `yyyy`, `mm/yyyy`, `yyyy/mm`\n  ([#384](https://github.com/taiga-family/maskito/issues/384))\n  ([7886d50](https://github.com/taiga-family/maskito/commit/7886d50012a76fec872816b6d5e2b7e67c931dd7))\n- **kit:** `Time` supports new mode `HH` ([#385](https://github.com/taiga-family/maskito/issues/385))\n  ([3c7a3f6](https://github.com/taiga-family/maskito/commit/3c7a3f65a0013152473ba57af8da28012cb58f32))\n\n## [1.3.0](https://github.com/taiga-family/maskito/compare/v1.2.2...v1.3.0) (2023-07-24)\n\n### 🚀 Features\n\n- **kit:** `DateRange` add configurable parameter `rangeSeparator`\n  ([#376](https://github.com/taiga-family/maskito/issues/376))\n  ([d904842](https://github.com/taiga-family/maskito/commit/d90484214da76f4c73ad925eef5fe391a154c499))\n\n### 🐞 Bug Fixes\n\n- **kit:** `Number` has problems with run-time updates of postfix\n  ([#380](https://github.com/taiga-family/maskito/issues/380))\n  ([8210896](https://github.com/taiga-family/maskito/commit/8210896d2095a44e79a27a38e4c8745e2beccdb7))\n\n### [1.2.2](https://github.com/taiga-family/maskito/compare/v1.2.1...v1.2.2) (2023-07-19)\n\n### 🐞 Bug Fixes\n\n- **kit:** `maskitoCaretGuard` should wait for `mouseup` before execution\n  ([#372](https://github.com/taiga-family/maskito/issues/372))\n  ([8554fea](https://github.com/taiga-family/maskito/commit/8554fead2a2474104f0674fb597cf86467274943))\n- **kit:** `Number` should remove repeated leading zeroes for integer part only on `blur`-event\n  ([#373](https://github.com/taiga-family/maskito/issues/373))\n  ([7cf4938](https://github.com/taiga-family/maskito/commit/7cf4938853ccbd049b89482f8eb22ab4e71fe01f))\n\n### [1.2.1](https://github.com/taiga-family/maskito/compare/v1.2.0...v1.2.1) (2023-07-11)\n\n### 🐞 Bug Fixes\n\n- **kit:** `Number` with `postfix` should be compatible with `decimalZeroPadding`\n  ([#364](https://github.com/taiga-family/maskito/issues/364))\n  ([501cf9c](https://github.com/taiga-family/maskito/commit/501cf9c747229d1776fb62cc04fbc8879990c617))\n- **kit:** `Prefix`/`Postfix` is incompatible if they end/start with the same character\n  ([#366](https://github.com/taiga-family/maskito/issues/366))\n  ([06afbcb](https://github.com/taiga-family/maskito/commit/06afbcb4a2c5c15e2ef9dc81db4309adf01aa8ef))\n\n## [1.2.0](https://github.com/taiga-family/maskito/compare/v1.1.1...v1.2.0) (2023-07-03)\n\n### 🚀 Features\n\n- **kit:** `maskitoCaretGuard`'s function has the 2nd argument with current selection range\n  ([#358](https://github.com/taiga-family/maskito/issues/358))\n  ([eedc4d6](https://github.com/taiga-family/maskito/commit/eedc4d610efaf36b98a4049f5c5334561b5b21c5))\n\n### 🐞 Bug Fixes\n\n- **kit:** `maskitoCaretGuard` incorrectly validates the left side of constraints\n  ([#356](https://github.com/taiga-family/maskito/issues/356))\n  ([17ee90f](https://github.com/taiga-family/maskito/commit/17ee90fe055f6a8370d6ea75ff2b236fd498441e))\n- **kit:** `Number` should skip min/max validation if value does not contain any digits\n  ([#359](https://github.com/taiga-family/maskito/issues/359))\n  ([ed8221e](https://github.com/taiga-family/maskito/commit/ed8221e14eca62334af41b4c8e571eb86ed68247))\n\n### [1.1.1](https://github.com/taiga-family/maskito/compare/v1.1.0...v1.1.1) (2023-06-29)\n\n### 🐞 Bug Fixes\n\n- **core:** don't ignore native attribute `maxlength` ([#350](https://github.com/taiga-family/maskito/issues/350))\n  ([8504f49](https://github.com/taiga-family/maskito/commit/8504f497152931da06dd745763be2505587f97b4))\n- **kit:** `Number` should ignore new typed decimal separator if it already exists in text field\n  ([#351](https://github.com/taiga-family/maskito/issues/351))\n  ([4ccfdc8](https://github.com/taiga-family/maskito/commit/4ccfdc86ff08bcebfd18c04403aa9c9c83cbbd02))\n\n## [1.1.0](https://github.com/taiga-family/maskito/compare/v1.0.0...v1.1.0) (2023-06-23)\n\n### 🚀 Features\n\n- **kit:** `maskitoEventHandler` accepts `AddEventListenerOptions` as the 3d optional argument\n  ([#346](https://github.com/taiga-family/maskito/issues/346))\n  ([1d5866e](https://github.com/taiga-family/maskito/commit/1d5866efa5e0e4736dd735ae006e027e9bd01e31))\n- **kit:** use capturing phase for `focus`/`blur` events in plugins\n  ([#347](https://github.com/taiga-family/maskito/issues/347))\n  ([ef539e1](https://github.com/taiga-family/maskito/commit/ef539e160f601023e513036d704f7daff9689286))\n\n### 🐞 Bug Fixes\n\n- **kit:** `Date` allows to replace the whole selection range with zero\n  ([#345](https://github.com/taiga-family/maskito/issues/345))\n  ([98fd21d](https://github.com/taiga-family/maskito/commit/98fd21d50899db365b864faf597fad9a21a3db06))\n\n## [1.0.0](https://github.com/taiga-family/maskito/compare/v0.16.0...v1.0.0) (2023-06-21)\n\n### ⚠ BREAKING CHANGES\n\n- **core:** delete deprecated `preprocessor` & `postprocessor` from `MaskitoOptions`\n  ([#337](https://github.com/taiga-family/maskito/issues/337))\n  ([0b6aad2](https://github.com/taiga-family/maskito/commit/0b6aad2622ed152d12c91f8ca64b767709ecdbc2))\n- **kit:** delete deprecated `isNegativeAllowed` parameter from `Number` mask\n  ([#338](https://github.com/taiga-family/maskito/issues/338))\n  ([9fd3005](https://github.com/taiga-family/maskito/commit/9fd30055b3157072076f7a8567045fac05b6af9e))\n\n## [0.16.0](https://github.com/taiga-family/maskito/compare/v0.15.0...v0.16.0) (2023-06-20)\n\n### 🚀 Features\n\n- **vue:** support async predicate ([#336](https://github.com/taiga-family/maskito/issues/336))\n  ([d1452b5](https://github.com/taiga-family/maskito/commit/d1452b5f1b2f8a252dfd05a5c1eb04ba971a1970))\n\n### 🐞 Bug Fixes\n\n- **kit:** `Number` fails to parse small number on blur (exponential notation problem)\n  ([#339](https://github.com/taiga-family/maskito/issues/339))\n  ([7f83a7f](https://github.com/taiga-family/maskito/commit/7f83a7f170906c1911eb4444da2d636c0338ed4a))\n\n## [0.15.0](https://github.com/taiga-family/maskito/compare/v0.14.0...v0.15.0) (2023-06-14)\n\n### 🚀 Features\n\n- **core:** add new parameters `preprocessors` & `postprocessors` and deprecate `preprocessor` & `postprocessor`\n  ([#333](https://github.com/taiga-family/maskito/issues/333))\n  ([0137775](https://github.com/taiga-family/maskito/commit/01377751a9875143257930934b1e2a9143b6da03))\n\n### 🐞 Bug Fixes\n\n- **kit:** `maskitoParseNumber` should return `NaN` for all strings with no digits\n  ([#331](https://github.com/taiga-family/maskito/issues/331))\n  ([d1ebcec](https://github.com/taiga-family/maskito/commit/d1ebceceedf418b21a68082f7350002d09159ebf))\n- **kit:** `Number` incorrectly implements `min`/`max` behaviour\n  ([#334](https://github.com/taiga-family/maskito/issues/334))\n  ([9876d88](https://github.com/taiga-family/maskito/commit/9876d885f98f86d18db04d723460b468bca3837d))\n\n## [0.14.0](https://github.com/taiga-family/maskito/compare/v0.13.0...v0.14.0) (2023-06-09)\n\n### 🚀 Features\n\n- **angular:** `[maskitoElement]` can accept asynchronous predicate\n  ([#316](https://github.com/taiga-family/maskito/issues/316))\n  ([3d8949e](https://github.com/taiga-family/maskito/commit/3d8949e878e644079b7f5404cb9ebf6c5eadab86))\n- **kit:** `Number` pads empty integer part with zero on blur (if decimal part exists)\n  ([#328](https://github.com/taiga-family/maskito/issues/328))\n  ([bd01967](https://github.com/taiga-family/maskito/commit/bd01967fba38be26a3c8f0d2f23c0ced12d3b1c2))\n\n## [0.13.0](https://github.com/taiga-family/maskito/compare/v0.12.1...v0.13.0) (2023-06-02)\n\n### 🚀 Features\n\n- **core:** better layout-independent way to detect `Undo` and `Redo`\n  ([#320](https://github.com/taiga-family/maskito/issues/320))\n  ([4c5a7f6](https://github.com/taiga-family/maskito/commit/4c5a7f64b9a8ac209584c75e17ec022674b87c1b))\n- **vue:** add dedicated Vue package ([#321](https://github.com/taiga-family/maskito/issues/321))\n  ([f6ffb24](https://github.com/taiga-family/maskito/commit/f6ffb24eca5f1a1a57a93103b9e74cdf410e4132))\n\n### [0.12.1](https://github.com/taiga-family/maskito/compare/v0.12.0...v0.12.1) (2023-05-25)\n\n### 🐞 Bug Fixes\n\n- **kit:** `DateTime` validate min / max if date is complete\n  ([#314](https://github.com/taiga-family/maskito/issues/314))\n  ([5783e76](https://github.com/taiga-family/maskito/commit/5783e766a657abcf0fc7f8a8d12ac1bf412dc18a))\n- **kit:** `Time` & `DateTime` should accept time segment separator typed by user\n  ([#317](https://github.com/taiga-family/maskito/issues/317))\n  ([3bcac7f](https://github.com/taiga-family/maskito/commit/3bcac7f6566043991a9211f04db744a5ec6f019f))\n\n## [0.12.0](https://github.com/taiga-family/maskito/compare/v0.11.1...v0.12.0) (2023-05-19)\n\n### 🚀 Features\n\n- **core:** add `plugins` to `MaskitoOptions` ([#305](https://github.com/taiga-family/maskito/issues/305))\n  ([b512ae2](https://github.com/taiga-family/maskito/commit/b512ae2c64b2a2c6560e2e5c68d8c72952474c71))\n- **core:** expose `MaskitoMask`, `MaskitoPreprocessor`, `MaskitoPostprocessor` and `MaskitoPlugin`\n  ([#307](https://github.com/taiga-family/maskito/issues/307))\n  ([9315a9f](https://github.com/taiga-family/maskito/commit/9315a9f4620b3be86cf3b7af993861664f281a19))\n- **kit:** new `maskitoWithPlaceholder` utility ([#299](https://github.com/taiga-family/maskito/issues/299))\n  ([21eb69c](https://github.com/taiga-family/maskito/commit/21eb69cfeb73bbe645d5a5879659ab8b6aadbf0c))\n\n### [0.11.1](https://github.com/taiga-family/maskito/compare/v0.11.0...v0.11.1) (2023-05-11)\n\n### 🐞 Bug Fixes\n\n- **core:** `insertFromDrop` action behaves now in the same way as `insertFromPaste`\n  ([#291](https://github.com/taiga-family/maskito/issues/291))\n  ([58e0fcc](https://github.com/taiga-family/maskito/commit/58e0fccb7ddd3c741ffa3c8b99efbcf4571aab37))\n- **kit:** `Time` doesn't validate time segments on `drop` event\n  ([#289](https://github.com/taiga-family/maskito/issues/289))\n  ([0c6d1b9](https://github.com/taiga-family/maskito/commit/0c6d1b9917d0c86a98c0d215c38a0e2076ff5680))\n\n## [0.11.0](https://github.com/taiga-family/maskito/compare/v0.10.0...v0.11.0) (2023-05-02)\n\n### 🚀 Features\n\n- **react:** new library `@maskito/react` ([#273](https://github.com/taiga-family/maskito/issues/273))\n  ([4c2f755](https://github.com/taiga-family/maskito/commit/4c2f755bac9513689964af7fdb7f4deec56bfb52))\n\n## [0.10.0](https://github.com/taiga-family/maskito/compare/v0.9.0...v0.10.0) (2023-04-25)\n\n### 🚀 Features\n\n- **kit:** `Number` keeps untouched decimal part if `precision: Infinity`\n  ([#253](https://github.com/taiga-family/maskito/issues/253))\n  ([261779e](https://github.com/taiga-family/maskito/commit/261779ead327397a61b27e634bc827ee70b718f4))\n- **kit:** `Number` supports new `prefix` & `postfix` parameters\n  ([#264](https://github.com/taiga-family/maskito/issues/264))\n  ([6e78581](https://github.com/taiga-family/maskito/commit/6e785818dabcde623d8c1c40a584166a0a66f5b6))\n- **kit:** new `maskitoPostfixPostprocessorGenerator` ([#257](https://github.com/taiga-family/maskito/issues/257))\n  ([fdc86db](https://github.com/taiga-family/maskito/commit/fdc86dbad368bfc17efd1047b7d68d9622968bb0))\n\n### 🐞 Bug Fixes\n\n- **kit:** `Number` fails to trim leading zeroes after deleting of leading digit\n  ([#268](https://github.com/taiga-family/maskito/issues/268))\n  ([4ae0010](https://github.com/taiga-family/maskito/commit/4ae0010ef2149694d22d7ae9eb8c9880120c8c75))\n- **kit:** `Number` should trim redundant thousand separators\n  ([#267](https://github.com/taiga-family/maskito/issues/267))\n  ([100b793](https://github.com/taiga-family/maskito/commit/100b79317a420103ca98a3b43fe646a6f77d19d5))\n- **kit:** fix first zero in short-format date-mask ([#251](https://github.com/taiga-family/maskito/issues/251))\n  ([08bdfd2](https://github.com/taiga-family/maskito/commit/08bdfd26727777c3a6fc870e433003be2b64cc0e))\n\n## [0.9.0](https://github.com/taiga-family/maskito/compare/v0.8.1...v0.9.0) (2023-03-31)\n\n### 🚀 Features\n\n- **kit:** new `maskitoPrefixPostprocessorGenerator` ([#235](https://github.com/taiga-family/maskito/issues/235))\n  ([50f0d58](https://github.com/taiga-family/maskito/commit/50f0d58ccbfa22d15174d76479a9d642687db099))\n\n### 🐞 Bug Fixes\n\n- **angular:** Jest throws `Class constructor DefaultValueAccessor cannot be invoked without 'new'`\n  ([#232](https://github.com/taiga-family/maskito/issues/232))\n  ([5089612](https://github.com/taiga-family/maskito/commit/508961288898a5fdd21cc0e26b23ecc8845f9068))\n\n### [0.8.1](https://github.com/taiga-family/maskito/compare/v0.8.0...v0.8.1) (2023-03-27)\n\n### 🐞 Bug Fixes\n\n- `@maskito/core` & `@maskito/kit` now include both `UMD` and `ESM` module formats\n  ([#227](https://github.com/taiga-family/maskito/issues/227))\n  ([fa1c514](https://github.com/taiga-family/maskito/commit/fa1c514a5753e3bca20e8b0994e4bf9f1c0ab6a4))\n\n## [0.8.0](https://github.com/taiga-family/maskito/compare/v0.7.2...v0.8.0) (2023-03-23)\n\n### 🚀 Features\n\n- **kit:** `DateRange` swaps dates if the 2nd date is less than the 1st one\n  ([#212](https://github.com/taiga-family/maskito/issues/212))\n  ([3efbb42](https://github.com/taiga-family/maskito/commit/3efbb42f2dd5c4e43ff514da7a82abfc7c4b3a38))\n\n### 🐞 Bug Fixes\n\n- **core:** incorrect order of actions during update of native element\n  ([#225](https://github.com/taiga-family/maskito/issues/225))\n  ([394d5d9](https://github.com/taiga-family/maskito/commit/394d5d996bdb9d21229ea0301eb3f776bee05d30))\n\n### [0.7.2](https://github.com/taiga-family/maskito/compare/v0.7.1...v0.7.2) (2023-03-23)\n\n### 🐞 Bug Fixes\n\n- **angular:** `@maskito/angular` should not depend on `@maskito/kit`\n  ([#221](https://github.com/taiga-family/maskito/issues/221))\n  ([0ae7b20](https://github.com/taiga-family/maskito/commit/0ae7b2089ec0436caa8dbb14d5c696ae93e9e7ed))\n- **angular:** `npm i @maskito/angular` throws `unable to resolve dependency tree`\n  ([#220](https://github.com/taiga-family/maskito/issues/220))\n  ([8b4d6e6](https://github.com/taiga-family/maskito/commit/8b4d6e6186db47f97d328186b7afd9af75a3889b))\n\n### [0.7.1](https://github.com/taiga-family/maskito/compare/v0.7.0...v0.7.1) (2023-03-22)\n\n### 🐞 Bug Fixes\n\n- **angular:** use `@nrwl/angular:package` executor instead of `@nrwl/angular:ng-packagr-lite`\n  ([#216](https://github.com/taiga-family/maskito/issues/216))\n  ([164d015](https://github.com/taiga-family/maskito/commit/164d015c2f18a279e195b45329e84c0d023c9483))\n- **kit:** `Number` broken `Delete`-button navigation if `decimalZeroPadding=true`\n  ([#211](https://github.com/taiga-family/maskito/issues/211))\n  ([1b750d1](https://github.com/taiga-family/maskito/commit/1b750d135ebd53bfeda2ca734425de08a808b1af))\n\n## [0.7.0](https://github.com/taiga-family/maskito/compare/v0.6.0...v0.7.0) (2023-03-20)\n\n### 🚀 Features\n\n- **core:** add `deleteSoftLineBackward` & `deleteSoftLineForward` support\n  ([#207](https://github.com/taiga-family/maskito/issues/207))\n  ([cbd5479](https://github.com/taiga-family/maskito/commit/cbd5479c04c07113804eee6ea6c9838ee8681597))\n- **kit:** use 1 as min segment value in `Date`-related masks\n  ([#197](https://github.com/taiga-family/maskito/issues/197))\n  ([c85ca23](https://github.com/taiga-family/maskito/commit/c85ca2355cb0b6fcef73f3e7497f7c31fa82c87c))\n\n### 🐞 Bug Fixes\n\n- **core:** `Maskito` losses valid characters on invalid insertion (`overwriteMode: replace`)\n  ([#208](https://github.com/taiga-family/maskito/issues/208))\n  ([ef183b4](https://github.com/taiga-family/maskito/commit/ef183b454e4a7db5b2cb48cbe26129bf303f676a))\n- **kit:** `Number` should drop leading zeroes for negative numbers\n  ([#204](https://github.com/taiga-family/maskito/issues/204))\n  ([6e9adf7](https://github.com/taiga-family/maskito/commit/6e9adf758aa585944ee08f2e2aff81a5664adefd))\n\n## [0.6.0](https://github.com/taiga-family/maskito/compare/v0.5.0...v0.6.0) (2023-03-15)\n\n### 🚀 Features\n\n- **angular:** add CVA and pipe ([#187](https://github.com/taiga-family/maskito/issues/187))\n  ([a099257](https://github.com/taiga-family/maskito/commit/a099257a16b569444cdae9276ce66e9a806f531e))\n- **core:** add `deleteWordBackward` & `deleteWordForward` support\n  ([#193](https://github.com/taiga-family/maskito/issues/193))\n  ([24b761c](https://github.com/taiga-family/maskito/commit/24b761c84d0947df5e4c78a2114f1de8f6ca20f4))\n\n### 🐞 Bug Fixes\n\n- **core:** show trailing fixed characters + duplicated fixed character on `Drop`\n  ([#185](https://github.com/taiga-family/maskito/issues/185))\n  ([c7f6a1b](https://github.com/taiga-family/maskito/commit/c7f6a1bb8098b5641ed8c6921c2ebc86c6135b58))\n- **kit:** `maskitoParseNumber` incorrectly parses negative numbers\n  ([#190](https://github.com/taiga-family/maskito/issues/190))\n  ([d713bd1](https://github.com/taiga-family/maskito/commit/d713bd143e5090870a406ea14498cb99843bb9d0))\n- **kit:** `Number` should drop decimal part on paste from clipboard if `precision=0`\n  ([#195](https://github.com/taiga-family/maskito/issues/195))\n  ([ba85c38](https://github.com/taiga-family/maskito/commit/ba85c38ec0c81a22ff758f4ba386d045ac49ffd5))\n\n## [0.5.0](https://github.com/taiga-family/maskito/compare/v0.4.0...v0.5.0) (2023-03-09)\n\n### 🚀 Features\n\n- **core:** new utility `maskitoTransform(value, maskitoOptions)`\n  ([#177](https://github.com/taiga-family/maskito/issues/177))\n  ([20316f1](https://github.com/taiga-family/maskito/commit/20316f15e153bfeeb45eda6406b8792e00f3238f))\n- **kit:** new utility `maskitoParseNumber` ([#178](https://github.com/taiga-family/maskito/issues/178))\n  ([fc58141](https://github.com/taiga-family/maskito/commit/fc58141625ecbdc7d804aa382a69b38bf7146fc4))\n\n### 🐞 Bug Fixes\n\n- **kit:** `Number` mask throws an error on empty string in `thousandSeparator`\n  ([#176](https://github.com/taiga-family/maskito/issues/176))\n  ([cd52fad](https://github.com/taiga-family/maskito/commit/cd52fad80bc278f171dafa1709c54cba3f8fbc81))\n\n## [0.4.0](https://github.com/taiga-family/maskito/compare/v0.3.0...v0.4.0) (2023-03-02)\n\n### 🚀 Features\n\n- **angular:** `maskitoElement` add new input ([#164](https://github.com/taiga-family/maskito/issues/164))\n  ([407c131](https://github.com/taiga-family/maskito/commit/407c131d2d8f8514173ad7a5e248759e2d4f8abc))\n\n### 🐞 Bug Fixes\n\n- **kit:** `Number` pads empty integer part when paste from clipboard\n  ([#168](https://github.com/taiga-family/maskito/issues/168))\n  ([d043a82](https://github.com/taiga-family/maskito/commit/d043a82561cbed94b19d59d174fa6da7f08d49d4))\n\n## [0.3.0](https://github.com/taiga-family/maskito/compare/v0.2.0...v0.3.0) (2023-03-01)\n\n### 🚀 Features\n\n- **angular:** add other maskito packages to `ng-update.packageGroup` of `package.json`\n  ([#161](https://github.com/taiga-family/maskito/issues/161))\n  ([bdecdaa](https://github.com/taiga-family/maskito/commit/bdecdaa9cac2681e35191cabd2d5d853eb97a09d))\n\n### 🐞 Bug Fixes\n\n- **kit:** `Date`-mask fix wrong year that appears using the min property\n  ([#157](https://github.com/taiga-family/maskito/issues/157))\n  ([544e891](https://github.com/taiga-family/maskito/commit/544e8912d2752e0fc8f77757e935070b94823f65))\n\n## [0.2.0](https://github.com/taiga-family/maskito/compare/v0.1.1...v0.2.0) (2023-02-28)\n\n### 🚀 Features\n\n- **kit:** new `DateTime` mask ([#146](https://github.com/taiga-family/maskito/issues/146))\n  ([6d6b2c1](https://github.com/taiga-family/maskito/commit/6d6b2c17b5c0f62bc804451524cd4b2ce3e50660))\n\n### 🐞 Bug Fixes\n\n- **kit:** `Number` is now replacing hyphen, en-dash and em-dash with minus sign\n  ([#153](https://github.com/taiga-family/maskito/issues/153))\n  ([1f21f11](https://github.com/taiga-family/maskito/commit/1f21f1159baadcef65e49bacaec77eba3b6f36d8))\n\n### [0.1.1](https://github.com/taiga-family/maskito/compare/v0.1.0...v0.1.1) (2023-02-15)\n\n### 🐞 Bug Fixes\n\n- **core:** `Module parse failed: 'import' and 'export' may appear only with 'sourceType: module'`\n  ([#131](https://github.com/taiga-family/maskito/issues/131))\n  ([41e05c0](https://github.com/taiga-family/maskito/commit/41e05c09e41ed611e0c2b9aa07a953dfbe049da7))\n\n## 0.1.0 (2023-02-14)\n\nThis release introduces the first publishing of the following packages:\n\n- `@maskito/core` <br /> It is the main zero-dependency and framework-agnostic package. It can be used alone in Vanilla\n  JavaScript project. It listens `beforeinput` and `input` events to validate and calibrate textfield's value. <br />\n  Read more: https://maskito.dev/core-concepts/overview\n- `@maskito/kit` <br /> The optional framework-agnostic package. It contains ready-to-use masks with configurable\n  parameters. This release introduces the following masks:\n  - [Number](https://maskito.dev/kit/number)\n  - [Time](https://maskito.dev/kit/time)\n  - [Date](https://maskito.dev/kit/date)\n  - [DateRange](https://maskito.dev/kit/date-range)\n- `@maskito/angular`<br /> The Angular-specific library. It provides two convenient ways of using Maskito:\n  - Basic directive approach (when developer has direct access to native input element).\n  - Dependency Injection approach (when native input element is hidden somewhere deep inside another component).\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   Copyright 2024 Acpekt\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "README.md",
    "content": "# Maskito\n\n[![npm version](https://img.shields.io/npm/v/@maskito/core.svg)](https://npmjs.com/package/@maskito/core)\n[![All packages CI](https://github.com/taiga-family/maskito/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/taiga-family/maskito/actions/workflows/build.yml)\n\n<p align=\"center\">\n    <img src=\"projects/demo/src/assets/icons/maskito.svg\" alt=\"Maskito logo\" width=\"120px\">\n</p>\n\n<p align=\"center\">\n    <a href=\"https://maskito.dev\">Documentation</a> •\n    <a href=\"https://github.com/taiga-family/maskito/issues/new/choose\">Submit an Issue</a> •\n    <a href=\"https://t.me/taiga_ui/10600\">Contact Us</a>\n</p>\n\n**Maskito** is a collection of libraries, built with TypeScript. It helps you to create an input mask which ensures that\nusers type values according to predefined format.\n\n## Why Maskito\n\n- **Maskito** supports all user’s interactions with text fields: basic typing and deleting via keyboard, pasting,\n  dropping text inside with a pointer, browser autofill, predictive text from mobile native keyboard.\n\n- **Maskito** is robust. The whole project is developed with strict TypeScript mode. Our code is covered by hundreds of\n  [Cypress](https://www.cypress.io) tests.\n\n- Server Side Rendering and Shadow DOM support.\n\n- You can use it with `HTMLInputElement` or `HTMLTextAreaElement` or even with `[contenteditable]` element.\n\n- **Maskito** core is zero-dependency package. You can mask input in your vanilla JavaScript project. However, we have\n  separate packages for Angular, React and Vue as well.\n\n- **Maskito** includes optional framework-agnostic package with configurable ready-to-use masks.\n\nNo text field with invalid value! Use Maskito. **Mask it!** Learn more about the library in our\n[documentation](https://maskito.dev).\n\n## Contributing\n\nIf you have suggestions for how **Maskito** could be improved, or want to report a bug, open an issue! We'd love all and\nany contributions.\n\nFor more, check out the [Contributing Guide](CONTRIBUTING.md).\n\n## Maintained\n\nMaskito is a part of [Taiga UI](https://github.com/taiga-family/taiga-ui) libraries family which is backed and used by a\nlarge enterprise. This means you can rely on timely support and continuous development.\n\n| **Package**                                                    | **Downloads**                                                                                     |\n| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| [@maskito/core](https://npmjs.com/package/@maskito/core)       | [![](https://img.shields.io/npm/dw/@maskito/core)](https://npmjs.com/package/@maskito/core)       |\n| [@maskito/kit](https://npmjs.com/package/@maskito/kit)         | [![](https://img.shields.io/npm/dw/@maskito/kit)](https://npmjs.com/package/@maskito/kit)         |\n| [@maskito/react](https://npmjs.com/package/@maskito/react)     | [![](https://img.shields.io/npm/dw/@maskito/react)](https://npmjs.com/package/@maskito/react)     |\n| [@maskito/angular](https://npmjs.com/package/@maskito/angular) | [![](https://img.shields.io/npm/dw/@maskito/angular)](https://npmjs.com/package/@maskito/angular) |\n| [@maskito/vue](https://npmjs.com/package/@maskito/vue)         | [![](https://img.shields.io/npm/dw/@maskito/vue)](https://npmjs.com/package/@maskito/vue)         |\n| [@maskito/phone](https://npmjs.com/package/@maskito/phone)     | [![](https://img.shields.io/npm/dw/@maskito/phone)](https://npmjs.com/package/@maskito/phone)     |\n\n## License\n\n🆓 Feel free to use our library in your commercial and private applications\n\nAll **Maskito** packages are covered by [Apache 2.0](/LICENSE)\n\nRead more about this license [here](https://choosealicense.com/licenses/apache-2.0/)\n"
  },
  {
    "path": "codecov.yml",
    "content": "codecov:\n  branch: main\n  notify:\n    require_ci_to_pass: no\n\ncoverage:\n  # This value is used to customize the visible color range in Codecov.\n  # The first number represents the red, and the second represents green.\n  # You can change the range of colors by adjusting this configuration.\n  range: 50..100 # by default 70..100\n  round: down\n  precision: 2\n\n  # Disable codecov/patch check\n  status:\n    project:\n      default:\n        enabled: false\n    patch:\n      default:\n        enabled: false\n"
  },
  {
    "path": "eslint.config.ts",
    "content": "import taiga from '@taiga-ui/eslint-plugin-experience-next';\n\nexport default [\n    ...taiga.configs.recommended,\n    {\n        files: ['*.tsx'],\n        rules: {\n            'react/display-name': 'off',\n            'react/react-in-jsx-scope': 'off',\n            'no-irregular-whitespace': 'off',\n        },\n    },\n    {\n        files: ['*.spec.tsx'],\n        rules: {\n            'jest/prefer-ending-with-an-expect': [\n                'error',\n                {assertFunctionNames: ['expect', 'check']},\n            ],\n        },\n    },\n    // TODO: fix later\n    {\n        files: ['**/*'],\n        rules: {\n            '@typescript-eslint/no-unused-private-class-members': 'off',\n            '@typescript-eslint/prefer-function-type': 'off',\n            '@typescript-eslint/no-restricted-types': 'off',\n            '@typescript-eslint/non-nullable-type-assertion-style': 'off',\n            '@angular-eslint/template/alt-text': 'off',\n            '@angular-eslint/prefer-signals': 'off',\n            '@typescript-eslint/no-redundant-type-constituents': 'off',\n            'no-irregular-whitespace': 'off',\n            'de-morgan/no-negated-disjunction': 'off',\n            '@angular-eslint/template/no-interpolation-in-attributes': 'off',\n            '@typescript-eslint/no-invalid-this': 'off',\n            '@angular-eslint/consistent-component-styles': 'off',\n            '@typescript-eslint/no-useless-default-assignment': 'off',\n            'import/consistent-type-specifier-style': 'off',\n            '@typescript-eslint/no-unnecessary-type-conversion': 'off',\n            '@typescript-eslint/consistent-type-exports': 'off',\n            '@typescript-eslint/method-signature-style': 'off',\n            '@typescript-eslint/strict-void-return': 'off',\n        },\n    },\n];\n"
  },
  {
    "path": "firebase.json",
    "content": "{\n    \"hosting\": {\n        \"public\": \"dist/demo/browser\",\n        \"ignore\": [\"firebase.json\", \"**/.*\", \"**/node_modules/**\"],\n        \"rewrites\": [\n            {\n                \"source\": \"**\",\n                \"destination\": \"/index.html\"\n            }\n        ]\n    }\n}\n"
  },
  {
    "path": "jest.config.ts",
    "content": "import {getJestProjectsAsync} from '@nx/jest';\nimport type {Config} from 'jest';\n\nexport default async (): Promise<Config> => ({projects: await getJestProjectsAsync()});\n"
  },
  {
    "path": "jest.preset.js",
    "content": "const nxPreset = require('@nx/jest/preset').default;\nconst {resolve} = require('node:path');\n\nmodule.exports = {\n    ...nxPreset,\n    transform: {\n        '^.+\\\\.(ts|tsx|js|jsx|mjs|html|svg)$': [\n            'jest-preset-angular',\n            {\n                diagnostics: true,\n                stringifyContentPathRegex: String.raw`\\.html$`,\n                tsconfig: resolve(__dirname, 'tsconfig.spec.json'),\n            },\n        ],\n    },\n};\n"
  },
  {
    "path": "nx.json",
    "content": "{\n    \"tui\": {\n        \"enabled\": false\n    },\n    \"workspaceLayout\": {\n        \"libsDir\": \"projects\",\n        \"appsDir\": \"projects\"\n    },\n    \"defaultProject\": \"demo\",\n    \"generators\": {\n        \"@nx/js:library\": {\n            \"buildable\": true,\n            \"publishable\": true,\n            \"strict\": true,\n            \"linter\": \"none\",\n            \"unitTestRunner\": \"jest\",\n            \"config\": \"project\"\n        },\n        \"@nx/angular:library\": {\n            \"linter\": \"none\",\n            \"unitTestRunner\": \"jest\",\n            \"buildable\": true,\n            \"publishable\": true,\n            \"compilationMode\": \"partial\",\n            \"strict\": true,\n            \"skipModule\": true,\n            \"standaloneConfig\": true\n        },\n        \"@nx/angular:application\": {\n            \"style\": \"less\",\n            \"linter\": \"none\",\n            \"unitTestRunner\": \"jest\"\n        },\n        \"@nx/angular:component\": {\n            \"style\": \"less\"\n        },\n        \"@nx/react\": {\n            \"application\": {\n                \"babel\": true\n            },\n            \"library\": {\n                \"linter\": \"eslint\",\n                \"publishable\": true,\n                \"bundler\": \"rollup\",\n                \"style\": \"none\",\n                \"strict\": true,\n                \"unitTestRunner\": \"jest\"\n            }\n        }\n    },\n    \"$schema\": \"./node_modules/nx/schemas/nx-schema.json\",\n    \"namedInputs\": {\n        \"default\": [\"{projectRoot}/**/*\", \"sharedGlobals\"],\n        \"sharedGlobals\": [\n            \"{workspaceRoot}/angular.json\",\n            \"{workspaceRoot}/nx.json\",\n            \"{workspaceRoot}/karma.*.js\",\n            \"{workspaceRoot}/tsconfig.*.json\",\n            \"{workspaceRoot}/tsconfig.json\",\n            \"{workspaceRoot}/babel.config.json\"\n        ],\n        \"production\": [\n            \"default\",\n            \"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)\",\n            \"!{projectRoot}/tsconfig.spec.json\",\n            \"!{projectRoot}/jest.config.[jt]s\",\n            \"!{projectRoot}/src/test-setup.[jt]s\",\n            \"!{projectRoot}/**/*.cy.[jt]s?(x)\",\n            \"!{projectRoot}/cypress.config.[jt]s\"\n        ]\n    },\n    \"targetDefaults\": {\n        \"build\": {\n            \"inputs\": [\"production\", \"^production\"],\n            \"cache\": true\n        },\n        \"lint\": {\n            \"cache\": true\n        },\n        \"@nx/jest:jest\": {\n            \"inputs\": [\"default\", \"^production\", \"{workspaceRoot}/jest.preset.js\"],\n            \"cache\": true,\n            \"options\": {\n                \"passWithNoTests\": true\n            },\n            \"configurations\": {\n                \"ci\": {\n                    \"ci\": true,\n                    \"codeCoverage\": true\n                }\n            }\n        },\n        \"component-test\": {\n            \"cache\": true,\n            \"inputs\": [\"default\", \"^production\"]\n        },\n        \"ct-react\": {\n            \"cache\": true,\n            \"inputs\": [\"default\", \"^production\"]\n        }\n    },\n    \"parallel\": 3,\n    \"useInferencePlugins\": false,\n    \"defaultBase\": \"origin/main\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"maskito\",\n    \"version\": \"5.2.2\",\n    \"description\": \"Collection of libraries to create an input mask which ensures that user types value according to predefined format\",\n    \"homepage\": \"https://maskito.dev\",\n    \"bugs\": \"https://github.com/taiga-family/maskito/issues\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/taiga-family/maskito.git\"\n    },\n    \"license\": \"Apache-2.0\",\n    \"author\": {\n        \"email\": \"nikita.s.barsukov@gmail.com\",\n        \"name\": \"Nikita Barsukov\",\n        \"url\": \"https://github.com/nsbarsukov\"\n    },\n    \"contributors\": [\n        {\n            \"email\": \"alexander@inkin.ru\",\n            \"name\": \"Alex Inkin\"\n        },\n        {\n            \"email\": \"vladimir.potekh@gmail.com\",\n            \"name\": \"Vladimir Potekhin\"\n        },\n        {\n            \"email\": \"nikita.s.barsukov@gmail.com\",\n            \"name\": \"Nikita Barsukov\"\n        },\n        {\n            \"email\": \"nextzeddicus@gmail.com\",\n            \"name\": \"Georgiy Lunin\"\n        }\n    ],\n    \"workspaces\": [\n        \"projects/*\"\n    ],\n    \"scripts\": {\n        \"build\": \"nx run demo:build:production\",\n        \"cspell\": \"cspell --relative --dot --gitignore .\",\n        \"cy:open\": \"cypress open --project ./projects/demo-integrations/\",\n        \"cy:run\": \"nx e2e demo-integrations && nx component-test demo-integrations && nx ct-react demo-integrations\",\n        \"lint\": \"eslint .\",\n        \"prepare\": \"husky\",\n        \"prettier\": \"prettier !package-lock.json . --ignore-path .gitignore\",\n        \"release\": \"npx nx run-many --target publish --all\",\n        \"release:local\": \"npx release-it --no-git.push --'hooks.before:release=\\\"echo Skip publish\\\"'\",\n        \"serve:ssr\": \"node dist/demo/server/server.mjs\",\n        \"start\": \"nx run demo:serve\",\n        \"stylelint\": \"stylelint '**/*.{less,css}'\",\n        \"test\": \"nx run-many --target test --all\",\n        \"typecheck\": \"tsc --noEmit --skipLibCheck --incremental false --tsBuildInfoFile null --project tsconfig.spec.json && tsc -p projects/demo-integrations/tsconfig.json\"\n    },\n    \"commitlint\": {\n        \"extends\": [\n            \"@taiga-ui/commitlint-config\"\n        ]\n    },\n    \"lint-staged\": {\n        \"*.less\": [\n            \"stylelint --fix\"\n        ],\n        \"*.{js,ts,html,md,less,json,svg,yml}\": [\n            \"npm run lint -- --fix\",\n            \"prettier --write\"\n        ]\n    },\n    \"browserslist\": [\n        \"extends @taiga-ui/browserslist-config\"\n    ],\n    \"prettier\": \"@taiga-ui/prettier-config\",\n    \"stylelint\": {\n        \"extends\": [\n            \"@taiga-ui/stylelint-config\"\n        ],\n        \"ignoreFiles\": [\n            \"**/dist/**\",\n            \"**/coverage/**\",\n            \"**/node_modules/**\"\n        ]\n    },\n    \"overrides\": {\n        \"@taiga-ui/addon-commerce\": {\n            \"@maskito/angular\": \">=4.0.0\",\n            \"@maskito/core\": \">=4.0.0\",\n            \"@maskito/kit\": \">=4.0.0\",\n            \"@maskito/phone\": \">=4.0.0\"\n        },\n        \"@taiga-ui/kit\": {\n            \"@maskito/angular\": \">=4.0.0\",\n            \"@maskito/core\": \">=4.0.0\",\n            \"@maskito/kit\": \">=4.0.0\",\n            \"@maskito/phone\": \">=4.0.0\"\n        }\n    },\n    \"devDependencies\": {\n        \"@angular-devkit/build-angular\": \"19.2.22\",\n        \"@angular-devkit/core\": \"19.2.22\",\n        \"@angular-devkit/schematics\": \"19.2.22\",\n        \"@angular/build\": \"19.2.22\",\n        \"@angular/cli\": \"19.2.22\",\n        \"@angular/compiler-cli\": \"19.2.20\",\n        \"@angular/core\": \"19.2.20\",\n        \"@angular/platform-browser-dynamic\": \"19.2.20\",\n        \"@nx/angular\": \"21.6.3\",\n        \"@nx/eslint\": \"21.6.3\",\n        \"@nx/jest\": \"21.6.3\",\n        \"@nx/js\": \"21.6.3\",\n        \"@nx/module-federation\": \"21.6.3\",\n        \"@nx/react\": \"21.6.3\",\n        \"@nx/rollup\": \"21.6.3\",\n        \"@nx/workspace\": \"21.6.3\",\n        \"@taiga-ui/configs\": \"0.476.0\",\n        \"@tinkoff/eslint-config\": \"5.2.0\",\n        \"@tinkoff/eslint-config-react\": \"5.2.0\",\n        \"@types/highlight.js\": \"10.1.0\",\n        \"@types/node\": \"24.10.11\",\n        \"http-server\": \"14.1.1\",\n        \"husky\": \"9.1.7\",\n        \"ng-packagr\": \"19.2.2\",\n        \"nx\": \"21.6.3\",\n        \"postcss-preset-env\": \"10.6.1\",\n        \"ts-node\": \"10.9.2\",\n        \"tsutils\": \"3.21.0\",\n        \"typescript\": \"5.8.3\"\n    },\n    \"engines\": {\n        \"node\": \">= 24\",\n        \"npm\": \">= 11\",\n        \"yarn\": \"Please use npm instead of yarn to install dependencies\"\n    },\n    \"auto-changelog\": {\n        \"prepend\": true,\n        \"template\": \"templates/note.hbs\"\n    },\n    \"syncer\": {\n        \"includePaths\": [\n            \"./projects\",\n            \"./package-lock.json\"\n        ],\n        \"matchPackageNames\": [\n            \"@maskito/*\",\n            \"maskito\"\n        ]\n    }\n}\n"
  },
  {
    "path": "projects/angular/README.md",
    "content": "# @maskito/angular\n\n[![npm version](https://img.shields.io/npm/v/@maskito/angular.svg)](https://npmjs.com/package/@maskito/angular)\n[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@maskito/angular)](https://bundlephobia.com/result?p=@maskito/angular)\n\n<p align=\"center\">\n    <img src=\"https://raw.githubusercontent.com/taiga-family/maskito/main/projects/demo/src/assets/icons/maskito.svg\" alt=\"Maskito logo\" width=\"120px\">\n</p>\n\n<p align=\"center\">\n    <a href=\"https://maskito.dev/frameworks/angular\">Documentation</a> •\n    <a href=\"https://github.com/taiga-family/maskito/issues/new/choose\">Submit an Issue</a> •\n    <a href=\"https://t.me/taiga_ui/10600\">Contact Us</a>\n</p>\n\n> The Angular-specific library.<br /> It provides a convenient way to use Maskito as a directive.\n\n## How to install\n\n```bash\nnpm i @maskito/{core,angular}\n```\n"
  },
  {
    "path": "projects/angular/jest.config.ts",
    "content": "export default {\n    displayName: 'angular',\n    preset: '../../jest.preset.js',\n    setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],\n    coverageDirectory: '../../coverage/angular',\n    transformIgnorePatterns: ['node_modules/(?!.*.mjs$)'],\n    snapshotSerializers: [\n        'jest-preset-angular/build/serializers/no-ng-attributes',\n        'jest-preset-angular/build/serializers/ng-snapshot',\n        'jest-preset-angular/build/serializers/html-comment',\n    ],\n};\n"
  },
  {
    "path": "projects/angular/ng-package.json",
    "content": "{\n    \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n    \"dest\": \"../../dist/angular\",\n    \"lib\": {\n        \"entryFile\": \"src/index.ts\"\n    }\n}\n"
  },
  {
    "path": "projects/angular/package.json",
    "content": "{\n    \"name\": \"@maskito/angular\",\n    \"version\": \"5.2.2\",\n    \"description\": \"The Angular-specific Maskito's library\",\n    \"keywords\": [\n        \"input\",\n        \"mask\",\n        \"inputmask\",\n        \"input-mask\",\n        \"text-mask\",\n        \"format\",\n        \"input-format\",\n        \"input-formatting\",\n        \"angular\"\n    ],\n    \"homepage\": \"https://maskito.dev\",\n    \"bugs\": \"https://github.com/taiga-family/maskito/issues\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/taiga-family/maskito.git\"\n    },\n    \"license\": \"Apache-2.0\",\n    \"author\": {\n        \"email\": \"nikita.s.barsukov@gmail.com\",\n        \"name\": \"Nikita Barsukov\",\n        \"url\": \"https://github.com/nsbarsukov\"\n    },\n    \"contributors\": [\n        {\n            \"email\": \"alexander@inkin.ru\",\n            \"name\": \"Alex Inkin\"\n        },\n        {\n            \"email\": \"vladimir.potekh@gmail.com\",\n            \"name\": \"Vladimir Potekhin\"\n        },\n        {\n            \"email\": \"nikita.s.barsukov@gmail.com\",\n            \"name\": \"Nikita Barsukov\"\n        },\n        {\n            \"email\": \"nextzeddicus@gmail.com\",\n            \"name\": \"Georgiy Lunin\"\n        }\n    ],\n    \"dependencies\": {\n        \"tslib\": \"2.8.1\"\n    },\n    \"devDependencies\": {\n        \"@angular/core\": \"19.2.20\",\n        \"@angular/forms\": \"19.2.20\"\n    },\n    \"peerDependencies\": {\n        \"@angular/core\": \">=19.0.0\",\n        \"@angular/forms\": \">=19.0.0\",\n        \"@maskito/core\": \"^5.2.2\"\n    },\n    \"ng-update\": {\n        \"packageGroup\": [\n            \"@maskito/core\",\n            \"@maskito/kit\"\n        ]\n    }\n}\n"
  },
  {
    "path": "projects/angular/project.json",
    "content": "{\n    \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n    \"name\": \"angular\",\n    \"prefix\": \"maskito\",\n    \"projectType\": \"library\",\n    \"sourceRoot\": \"projects/angular/src\",\n    \"tags\": [],\n    \"targets\": {\n        \"build\": {\n            \"configurations\": {\n                \"development\": {},\n                \"production\": {\n                    \"tsConfig\": \"{projectRoot}/tsconfig.lib.prod.json\"\n                }\n            },\n            \"defaultConfiguration\": \"production\",\n            \"dependsOn\": [\n                {\n                    \"dependencies\": true,\n                    \"params\": \"forward\",\n                    \"target\": \"build\"\n                }\n            ],\n            \"executor\": \"@nx/angular:package\",\n            \"options\": {\n                \"project\": \"{projectRoot}/ng-package.json\",\n                \"tsConfig\": \"tsconfig.build.json\"\n            },\n            \"outputs\": [\"{workspaceRoot}/dist/{projectName}\"]\n        },\n        \"publish\": {\n            \"dependsOn\": [\n                {\n                    \"params\": \"ignore\",\n                    \"target\": \"build\"\n                }\n            ],\n            \"executor\": \"nx:run-commands\",\n            \"options\": {\n                \"command\": \"npm publish ./dist/{projectName} --ignore-scripts\"\n            }\n        },\n        \"test\": {\n            \"executor\": \"@nx/jest:jest\",\n            \"options\": {\n                \"jestConfig\": \"{projectRoot}/jest.config.ts\"\n            },\n            \"outputs\": [\"{workspaceRoot}/coverage/{projectName}\"]\n        }\n    }\n}\n"
  },
  {
    "path": "projects/angular/src/index.ts",
    "content": "export * from './lib/maskito.directive';\nexport * from './lib/maskito.pipe';\nexport * from './lib/pattern.directive';\n"
  },
  {
    "path": "projects/angular/src/lib/maskito.directive.ts",
    "content": "import {\n    Directive,\n    effect,\n    ElementRef,\n    inject,\n    model,\n    NgZone,\n    type OnDestroy,\n    untracked,\n} from '@angular/core';\nimport {DefaultValueAccessor} from '@angular/forms';\nimport {\n    Maskito,\n    MASKITO_DEFAULT_ELEMENT_PREDICATE,\n    type MaskitoOptions,\n    maskitoTransform,\n} from '@maskito/core';\n\n@Directive({selector: '[maskito]'})\nexport class MaskitoDirective implements OnDestroy {\n    private readonly elementRef: HTMLElement = inject(ElementRef).nativeElement;\n    private readonly ngZone = inject(NgZone);\n    private maskedElement: Maskito | null = null;\n\n    protected readonly initEffect = effect(async () => {\n        const options = this.options();\n        const elementPredicate = this.elementPredicate();\n        const {elementRef, ngZone} = this;\n\n        this.destroy();\n\n        if (!options) {\n            return;\n        }\n\n        const predicateResult = await elementPredicate(elementRef);\n\n        if (\n            untracked(this.elementPredicate) !== elementPredicate ||\n            untracked(this.options) !== options\n        ) {\n            // Ignore the result of the predicate if the\n            // maskito element (or its options) has changed before the predicate was resolved.\n            return;\n        }\n\n        ngZone.runOutsideAngular(() => {\n            this.maskedElement = new Maskito(predicateResult, options);\n        });\n    });\n\n    public readonly options = model<MaskitoOptions | null>(null, {alias: 'maskito'});\n    public readonly elementPredicate = model(MASKITO_DEFAULT_ELEMENT_PREDICATE, {\n        alias: 'maskitoElement',\n    });\n\n    constructor() {\n        const accessor = inject(DefaultValueAccessor, {self: true, optional: true});\n\n        if (accessor) {\n            const original = accessor.writeValue.bind(accessor);\n\n            accessor.writeValue = (value: unknown) => {\n                const options = untracked(this.options);\n\n                original(\n                    options ? maskitoTransform(String(value ?? ''), options) : value,\n                );\n            };\n        }\n    }\n\n    public ngOnDestroy(): void {\n        this.destroy();\n    }\n\n    private destroy(): void {\n        this.maskedElement?.destroy();\n        this.maskedElement = null;\n    }\n}\n"
  },
  {
    "path": "projects/angular/src/lib/maskito.pipe.ts",
    "content": "import {Pipe, type PipeTransform} from '@angular/core';\nimport {\n    MASKITO_DEFAULT_OPTIONS,\n    type MaskitoOptions,\n    maskitoTransform,\n} from '@maskito/core';\n\n@Pipe({name: 'maskito'})\nexport class MaskitoPipe implements PipeTransform {\n    public transform(value: unknown, maskitoOptions: MaskitoOptions | null): string {\n        return maskitoTransform(\n            String(value ?? ''),\n            maskitoOptions ?? MASKITO_DEFAULT_OPTIONS,\n        );\n    }\n}\n"
  },
  {
    "path": "projects/angular/src/lib/pattern.directive.ts",
    "content": "import {Directive, inject} from '@angular/core';\n\nimport {MaskitoDirective} from './maskito.directive';\n\n@Directive({\n    selector: '[maskitoPattern]',\n    inputs: ['maskitoPattern'],\n    hostDirectives: [MaskitoDirective],\n})\nexport class MaskitoPattern {\n    private readonly maskitoDirective = inject(MaskitoDirective, {self: true});\n\n    public set maskitoPattern(pattern: RegExp | string) {\n        this.maskitoDirective.options.set({\n            mask: typeof pattern === 'string' ? new RegExp(`^${pattern}$`) : pattern,\n        });\n    }\n}\n"
  },
  {
    "path": "projects/angular/src/lib/tests/maskito.directive.spec.ts",
    "content": "import {ChangeDetectionStrategy, Component, signal} from '@angular/core';\nimport {type ComponentFixture, TestBed} from '@angular/core/testing';\nimport {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {\n    Maskito,\n    MASKITO_DEFAULT_ELEMENT_PREDICATE,\n    type MaskitoElementPredicate,\n    type MaskitoOptions,\n} from '@maskito/core';\n\nconst DIGIT_ONLY: MaskitoOptions = {mask: /^\\d*$/};\n\ndescribe('MaskitoDirective — initEffect', () => {\n    @Component({\n        imports: [MaskitoDirective],\n        template: `\n            <input\n                [maskito]=\"options()\"\n                [maskitoElement]=\"predicate()\"\n            />\n        `,\n        changeDetection: ChangeDetectionStrategy.OnPush,\n    })\n    class TestHostComponent {\n        public readonly options = signal<MaskitoOptions | null>(null);\n        public readonly predicate = signal(MASKITO_DEFAULT_ELEMENT_PREDICATE);\n    }\n\n    let fixture: ComponentFixture<TestHostComponent>;\n    let destroySpy: ReturnType<typeof jest.spyOn>;\n\n    function getDirective(): MaskitoDirective {\n        return fixture.debugElement.children[0]!.injector.get(MaskitoDirective);\n    }\n\n    function getMaskedElement(): Maskito | null {\n        return (getDirective() as unknown as {maskedElement: Maskito | null})\n            .maskedElement;\n    }\n\n    beforeEach(() => {\n        TestBed.configureTestingModule({imports: [TestHostComponent]});\n        destroySpy = jest.spyOn(Maskito.prototype, 'destroy');\n        fixture = TestBed.createComponent(TestHostComponent);\n        fixture.detectChanges();\n    });\n\n    afterEach(() => {\n        jest.restoreAllMocks();\n    });\n\n    describe('when options is null', () => {\n        it('does not create a Maskito instance', async () => {\n            await fixture.whenStable();\n\n            expect(getMaskedElement()).toBeNull();\n        });\n    });\n\n    describe('when options are provided', () => {\n        beforeEach(async () => {\n            fixture.componentInstance.options.set(DIGIT_ONLY);\n            fixture.detectChanges();\n            await fixture.whenStable();\n        });\n\n        it('creates a Maskito instance', () => {\n            expect(getMaskedElement()).toBeInstanceOf(Maskito);\n        });\n\n        it('destroys the old instance and creates a new one when options change', async () => {\n            fixture.componentInstance.options.set({mask: /^[a-z]*$/});\n            fixture.detectChanges();\n            await fixture.whenStable();\n\n            expect(destroySpy).toHaveBeenCalledTimes(1);\n            expect(getMaskedElement()).toBeInstanceOf(Maskito);\n        });\n\n        it('destroys the instance when options become null', async () => {\n            fixture.componentInstance.options.set(null);\n            fixture.detectChanges();\n            await fixture.whenStable();\n\n            expect(destroySpy).toHaveBeenCalledTimes(1);\n            expect(getMaskedElement()).toBeNull();\n        });\n\n        it('destroys the instance when the directive is destroyed', () => {\n            const directive = getDirective() as unknown as {\n                maskedElement: Maskito | null;\n            };\n\n            fixture.destroy();\n\n            expect(destroySpy).toHaveBeenCalledTimes(1);\n            expect(directive.maskedElement).toBeNull();\n        });\n    });\n\n    describe('stale async predicate', () => {\n        function makeControlledPredicate(): {\n            predicate: MaskitoElementPredicate;\n            resolveCall: (index: number) => void;\n        } {\n            const resolvers: Array<() => void> = [];\n            const predicate: MaskitoElementPredicate = async (el) =>\n                new Promise<HTMLInputElement>((resolve) => {\n                    resolvers.push(() => resolve(el as HTMLInputElement));\n                });\n\n            return {predicate, resolveCall: (i) => resolvers[i]!()};\n        }\n\n        it('ignores the result when elementPredicate changes before it resolves', async () => {\n            const {predicate: slowPredicate, resolveCall} = makeControlledPredicate();\n\n            fixture.componentInstance.options.set(DIGIT_ONLY);\n            fixture.componentInstance.predicate.set(slowPredicate);\n            fixture.detectChanges();\n            await fixture.whenStable();\n\n            expect(getMaskedElement()).toBeNull();\n\n            fixture.componentInstance.predicate.set(MASKITO_DEFAULT_ELEMENT_PREDICATE);\n            fixture.detectChanges();\n            await fixture.whenStable();\n\n            expect(getMaskedElement()).toBeInstanceOf(Maskito);\n            expect(destroySpy).not.toHaveBeenCalled();\n\n            resolveCall(0);\n            await fixture.whenStable();\n\n            expect(destroySpy).not.toHaveBeenCalled();\n            expect(getMaskedElement()).toBeInstanceOf(Maskito);\n        });\n\n        it('ignores the result when options change before the predicate resolves', async () => {\n            const {predicate: slowPredicate, resolveCall} = makeControlledPredicate();\n\n            fixture.componentInstance.options.set(DIGIT_ONLY);\n            fixture.componentInstance.predicate.set(slowPredicate);\n            fixture.detectChanges();\n            await fixture.whenStable();\n\n            expect(getMaskedElement()).toBeNull();\n\n            fixture.componentInstance.options.set({mask: /^[a-z]*$/});\n            fixture.detectChanges();\n            await fixture.whenStable();\n\n            resolveCall(0);\n            await fixture.whenStable();\n\n            expect(destroySpy).not.toHaveBeenCalled();\n            expect(getMaskedElement()).toBeNull();\n        });\n    });\n});\n"
  },
  {
    "path": "projects/angular/src/lib/tests/maskito.spec.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {type ComponentFixture, TestBed} from '@angular/core/testing';\nimport {FormControl, ReactiveFormsModule} from '@angular/forms';\nimport {beforeEach, describe, expect, it} from '@jest/globals';\nimport {MaskitoDirective, MaskitoPipe} from '@maskito/angular';\nimport type {MaskitoOptions} from '@maskito/core';\n\ndescribe('Maskito Angular package', () => {\n    @Component({\n        imports: [MaskitoDirective, MaskitoPipe, ReactiveFormsModule],\n        template: `\n            <div id=\"pipe\">{{ control.value | maskito: options }}</div>\n            <input\n                id=\"input\"\n                [formControl]=\"control\"\n                [maskito]=\"options\"\n            />\n        `,\n        // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection\n        changeDetection: ChangeDetectionStrategy.Default,\n    })\n    class TestComponent {\n        public readonly control = new FormControl();\n        public options: MaskitoOptions | null = {\n            mask: /^\\d+(,\\d{0,2})?$/,\n            preprocessors: [\n                ({elementState, data}) => {\n                    const {value, selection} = elementState;\n\n                    return {\n                        elementState: {\n                            selection,\n                            value: value.replace('.', ','),\n                        },\n                        data: data.replace('.', ','),\n                    };\n                },\n            ],\n        };\n    }\n\n    let fixture: ComponentFixture<TestComponent>;\n\n    beforeEach(() => {\n        TestBed.configureTestingModule({imports: [TestComponent]});\n\n        fixture = TestBed.createComponent(TestComponent);\n        fixture.detectChanges();\n    });\n\n    it('null is treated as empty string', () => {\n        expect(getText()).toBe('');\n        expect(getValue()).toBe('');\n    });\n\n    it('formats new control value', () => {\n        fixture.componentInstance.control.setValue(12345.6789);\n        fixture.detectChanges();\n\n        expect(getText()).toBe('12345,67');\n        expect(getValue()).toBe('12345,67');\n    });\n\n    it('disable mask formatting if options is null', () => {\n        fixture.componentInstance.options = null;\n        fixture.detectChanges();\n\n        fixture.componentInstance.control.setValue(123456.9999);\n        fixture.detectChanges();\n\n        expect(getText()).toBe('123456.9999');\n        expect(getValue()).toBe('123456.9999');\n    });\n\n    function getText(): string {\n        return fixture.debugElement.nativeElement\n            .querySelector('#pipe')\n            .textContent.trim();\n    }\n\n    function getValue(): string {\n        return fixture.debugElement.nativeElement.querySelector('#input').value;\n    }\n});\n"
  },
  {
    "path": "projects/angular/src/test-setup.ts",
    "content": "import {setupZoneTestEnv} from 'jest-preset-angular/setup-env/zone';\n\nsetupZoneTestEnv();\n"
  },
  {
    "path": "projects/angular/tsconfig.lib.prod.json",
    "content": "{\n    \"extends\": \"../../tsconfig.build.json\",\n    \"compilerOptions\": {\n        \"declarationMap\": false\n    }\n}\n"
  },
  {
    "path": "projects/core/README.md",
    "content": "# @maskito/core\n\n[![npm version](https://img.shields.io/npm/v/@maskito/core.svg)](https://npmjs.com/package/@maskito/core)\n[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@maskito/core)](https://bundlephobia.com/result?p=@maskito/core)\n\n<p align=\"center\">\n    <img src=\"https://raw.githubusercontent.com/taiga-family/maskito/main/projects/demo/src/assets/icons/maskito.svg\" alt=\"Maskito logo\" width=\"120px\">\n</p>\n\n<p align=\"center\">\n    <a href=\"https://maskito.dev\">Documentation</a> •\n    <a href=\"https://github.com/taiga-family/maskito/issues/new/choose\">Submit an Issue</a> •\n    <a href=\"https://t.me/taiga_ui/10600\">Contact Us</a>\n</p>\n\n> It is the main zero-dependency and framework-agnostic Maskito's package.<br />It can be used alone in\n> vanilla JavaScript project.\n\n## How to install\n\n```bash\nnpm i @maskito/core\n```\n"
  },
  {
    "path": "projects/core/jest.config.ts",
    "content": "export default {\n    displayName: 'core',\n    preset: '../../jest.preset.js',\n    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],\n    coverageDirectory: '../../coverage/core',\n};\n"
  },
  {
    "path": "projects/core/package.json",
    "content": "{\n    \"name\": \"@maskito/core\",\n    \"version\": \"5.2.2\",\n    \"description\": \"The main zero-dependency and framework-agnostic Maskito's package to create an input mask\",\n    \"keywords\": [\n        \"input\",\n        \"mask\",\n        \"inputmask\",\n        \"input-mask\",\n        \"text-mask\",\n        \"format\",\n        \"input-format\",\n        \"input-formatting\",\n        \"javascript\",\n        \"typescript\"\n    ],\n    \"homepage\": \"https://maskito.dev\",\n    \"bugs\": \"https://github.com/taiga-family/maskito/issues\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/taiga-family/maskito.git\"\n    },\n    \"license\": \"Apache-2.0\",\n    \"author\": {\n        \"email\": \"nikita.s.barsukov@gmail.com\",\n        \"name\": \"Nikita Barsukov\",\n        \"url\": \"https://github.com/nsbarsukov\"\n    },\n    \"contributors\": [\n        {\n            \"email\": \"alexander@inkin.ru\",\n            \"name\": \"Alex Inkin\"\n        },\n        {\n            \"email\": \"vladimir.potekh@gmail.com\",\n            \"name\": \"Vladimir Potekhin\"\n        },\n        {\n            \"email\": \"nikita.s.barsukov@gmail.com\",\n            \"name\": \"Nikita Barsukov\"\n        },\n        {\n            \"email\": \"nextzeddicus@gmail.com\",\n            \"name\": \"Georgiy Lunin\"\n        }\n    ]\n}\n"
  },
  {
    "path": "projects/core/project.json",
    "content": "{\n    \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n    \"name\": \"core\",\n    \"projectType\": \"library\",\n    \"sourceRoot\": \"projects/core/src\",\n    \"tags\": [],\n    \"targets\": {\n        \"build\": {\n            \"executor\": \"@nx/rollup:rollup\",\n            \"options\": {\n                \"assets\": [\n                    {\n                        \"glob\": \"README.md\",\n                        \"input\": \"{projectRoot}\",\n                        \"output\": \".\"\n                    }\n                ],\n                \"compiler\": \"tsc\",\n                \"external\": \"all\",\n                \"format\": [\"esm\", \"cjs\"],\n                \"main\": \"{projectRoot}/src/index.ts\",\n                \"outputPath\": \"dist/{projectName}\",\n                \"project\": \"{projectRoot}/package.json\",\n                \"tsConfig\": \"tsconfig.build.json\",\n                \"useLegacyTypescriptPlugin\": false\n            },\n            \"outputs\": [\"{options.outputPath}\"]\n        },\n        \"publish\": {\n            \"dependsOn\": [\n                {\n                    \"params\": \"ignore\",\n                    \"target\": \"build\"\n                }\n            ],\n            \"executor\": \"nx:run-commands\",\n            \"options\": {\n                \"command\": \"npm publish ./dist/{projectName} --ignore-scripts\"\n            }\n        },\n        \"test\": {\n            \"executor\": \"@nx/jest:jest\",\n            \"options\": {\n                \"jestConfig\": \"{projectRoot}/jest.config.ts\"\n            },\n            \"outputs\": [\"{workspaceRoot}/coverage/{projectName}\"]\n        }\n    }\n}\n"
  },
  {
    "path": "projects/core/src/index.ts",
    "content": "export {\n    MASKITO_DEFAULT_ELEMENT_PREDICATE,\n    MASKITO_DEFAULT_OPTIONS,\n} from './lib/constants';\nexport {Maskito} from './lib/mask';\nexport {\n    maskitoChangeEventPlugin,\n    maskitoInitialCalibrationPlugin,\n    maskitoStrictCompositionPlugin,\n} from './lib/plugins';\nexport type {\n    MaskitoElement,\n    MaskitoElementPredicate,\n    MaskitoMask,\n    MaskitoMaskExpression,\n    MaskitoOptions,\n    MaskitoPlugin,\n    MaskitoPostprocessor,\n    MaskitoPreprocessor,\n} from './lib/types';\nexport {\n    maskitoAdaptContentEditable,\n    maskitoPipe,\n    maskitoTransform,\n    maskitoUpdateElement,\n} from './lib/utils';\n"
  },
  {
    "path": "projects/core/src/lib/classes/index.ts",
    "content": "export {MaskHistory} from './mask-history';\nexport {MaskModel} from './mask-model/mask-model';\n"
  },
  {
    "path": "projects/core/src/lib/classes/mask-history.ts",
    "content": "import type {ElementState, TypedInputEvent} from '../types';\n\nexport abstract class MaskHistory {\n    private now: ElementState | null = null;\n    private readonly past: ElementState[] = [];\n    private future: ElementState[] = [];\n\n    protected abstract updateElementState(\n        state: ElementState,\n        eventInit: Pick<TypedInputEvent, 'data' | 'inputType'>,\n    ): void;\n\n    protected undo(): void {\n        const state = this.past.pop();\n\n        if (state && this.now) {\n            this.future.push(this.now);\n            this.updateElement(state, 'historyUndo');\n        }\n    }\n\n    protected redo(): void {\n        const state = this.future.pop();\n\n        if (state && this.now) {\n            this.past.push(this.now);\n            this.updateElement(state, 'historyRedo');\n        }\n    }\n\n    protected updateHistory(state: ElementState): void {\n        if (!this.now) {\n            this.now = state;\n\n            return;\n        }\n\n        const isValueChanged = this.now.value !== state.value;\n        const isSelectionChanged = this.now.selection.some(\n            (item, index) => item !== state.selection[index],\n        );\n\n        if (!isValueChanged && !isSelectionChanged) {\n            return;\n        }\n\n        if (isValueChanged) {\n            this.past.push(this.now);\n            this.future = [];\n        }\n\n        this.now = state;\n    }\n\n    private updateElement(\n        state: ElementState,\n        inputType: TypedInputEvent['inputType'],\n    ): void {\n        this.now = state;\n        this.updateElementState(state, {inputType, data: null});\n    }\n}\n"
  },
  {
    "path": "projects/core/src/lib/classes/mask-model/mask-model.ts",
    "content": "import type {\n    ElementState,\n    MaskitoMaskExpression,\n    MaskitoOptions,\n    SelectionRange,\n} from '../../types';\nimport {areElementStatesEqual} from '../../utils/element-states-equality';\nimport {applyOverwriteMode} from './utils/apply-overwrite-mode';\nimport {calibrateValueByMask} from './utils/calibrate-value-by-mask';\nimport {removeFixedMaskCharacters} from './utils/remove-fixed-mask-characters';\n\nexport class MaskModel implements ElementState {\n    private readonly unmaskInitialState: ElementState = {value: '', selection: [0, 0]};\n\n    public value = '';\n    public selection: SelectionRange = [0, 0];\n\n    constructor(\n        initialElementState: ElementState,\n        private readonly maskOptions: Required<MaskitoOptions>,\n    ) {\n        const expression = this.getMaskExpression(initialElementState);\n        const {value, selection} = calibrateValueByMask(initialElementState, expression);\n\n        this.unmaskInitialState = removeFixedMaskCharacters(\n            {value, selection},\n            expression,\n        );\n        this.value = value;\n        this.selection = selection;\n    }\n\n    public addCharacters(newCharacters: string): void {\n        const {value, selection, maskOptions} = this;\n        const initialElementState = {value, selection} as const;\n        const {\n            selection: [from, to],\n        } = applyOverwriteMode(\n            initialElementState,\n            newCharacters,\n            maskOptions.overwriteMode,\n        );\n        const maskExpression = this.getMaskExpression({\n            value: `${value.slice(0, from)}${newCharacters}${value.slice(to)}`,\n            selection: [from + newCharacters.length, from + newCharacters.length],\n        });\n        const [unmaskedFrom, unmaskedTo] = applyOverwriteMode(\n            this.unmaskInitialState,\n            newCharacters,\n            maskOptions.overwriteMode,\n        ).selection;\n        const newUnmaskedLeadingValuePart = `${this.unmaskInitialState.value.slice(0, unmaskedFrom)}${newCharacters}`;\n        const newCaretIndex = newUnmaskedLeadingValuePart.length;\n        const maskedElementState = calibrateValueByMask(\n            {\n                value: `${newUnmaskedLeadingValuePart}${this.unmaskInitialState.value.slice(unmaskedTo)}`,\n                selection: [newCaretIndex, newCaretIndex],\n            },\n            maskExpression,\n            initialElementState,\n        );\n        const prevLeadingPart = value.slice(0, from);\n        const newLeadingPartState = calibrateValueByMask(\n            {\n                value: newUnmaskedLeadingValuePart,\n                selection: [newCaretIndex, newCaretIndex],\n            },\n            maskExpression,\n            initialElementState,\n        );\n\n        const isInvalidCharsInsertion =\n            newLeadingPartState.value === prevLeadingPart ||\n            (newLeadingPartState.value.length < prevLeadingPart.length &&\n                removeFixedMaskCharacters(newLeadingPartState, maskExpression).value ===\n                    this.unmaskInitialState.value.slice(0, unmaskedFrom));\n\n        if (\n            isInvalidCharsInsertion ||\n            areElementStatesEqual(this, maskedElementState) // If typing new characters does not change value\n        ) {\n            throw new Error('Invalid mask value');\n        }\n\n        this.value = maskedElementState.value;\n        this.selection = maskedElementState.selection;\n    }\n\n    public deleteCharacters(): void {\n        const [from, to] = this.selection;\n\n        if (from === to || !to) {\n            return;\n        }\n\n        const {value} = this;\n        const maskExpression = this.getMaskExpression({\n            value: `${value.slice(0, from)}${value.slice(to)}`,\n            selection: [from, from],\n        });\n        const initialElementState = {value, selection: [from, to]} as const;\n        const [unmaskedFrom, unmaskedTo] = this.unmaskInitialState.selection;\n        const newUnmaskedValue = `${this.unmaskInitialState.value.slice(0, unmaskedFrom)}${this.unmaskInitialState.value.slice(unmaskedTo)}`;\n\n        const maskedElementState = calibrateValueByMask(\n            {value: newUnmaskedValue, selection: [unmaskedFrom, unmaskedFrom]},\n            maskExpression,\n            initialElementState,\n        );\n\n        this.value = maskedElementState.value;\n        this.selection = maskedElementState.selection;\n    }\n\n    private getMaskExpression(elementState: ElementState): MaskitoMaskExpression {\n        const {mask} = this.maskOptions;\n\n        return typeof mask === 'function' ? mask(elementState) : mask;\n    }\n}\n"
  },
  {
    "path": "projects/core/src/lib/classes/mask-model/tests/dynamic-mask.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\nimport {MASKITO_DEFAULT_OPTIONS} from '@maskito/core';\n\nimport type {ElementState, MaskitoMask, MaskitoOptions} from '../../../types';\nimport {MaskModel} from '../mask-model';\n\nconst EMPTY_STATE: ElementState = {\n    selection: [0, 0],\n    value: '',\n};\n\ndescribe('MaskModel | Dynamic mask', () => {\n    describe('switching on the fly works', () => {\n        const SHORT: MaskitoMask = Array.from<RegExp>({length: 10}).fill(/\\d/);\n        const MEDIUM: MaskitoMask = [\n            /\\d/,\n            /\\d/,\n            /\\d/,\n            /\\d/,\n            ' ',\n            /\\d/,\n            /\\d/,\n            /\\d/,\n            /\\d/,\n            ' ',\n            /\\d/,\n            /\\d/,\n            /\\d/,\n            /\\d/,\n            ' ',\n            /\\d/,\n            /\\d/,\n            /\\d/,\n            /\\d/,\n        ];\n        const LONG: MaskitoMask = Array.from<RegExp>({length: 20}).fill(/\\d/);\n        const maskitoOptions: Required<MaskitoOptions> = {\n            ...MASKITO_DEFAULT_OPTIONS,\n            mask: ({value}) => {\n                const digitsCount = value.replaceAll(/\\D/g, '').length;\n\n                if (digitsCount <= 10) {\n                    return SHORT;\n                }\n\n                if (digitsCount <= 16) {\n                    return MEDIUM;\n                }\n\n                return LONG;\n            },\n        };\n\n        it('enable short mask if number of digits is <=10', () => {\n            const maskModel = new MaskModel(EMPTY_STATE, maskitoOptions);\n\n            maskModel.addCharacters('01234abc56789');\n\n            expect(maskModel.value).toBe('0123456789');\n            expect(maskModel.selection).toEqual([\n                '0123456789'.length,\n                '0123456789'.length,\n            ]);\n        });\n\n        it('enable medium mask if number of digits is 10 < x <= 16', () => {\n            const maskModel = new MaskModel(EMPTY_STATE, maskitoOptions);\n\n            maskModel.addCharacters('01234abc56789123456');\n\n            expect(maskModel.value).toBe('0123 4567 8912 3456');\n            expect(maskModel.selection).toEqual([\n                '0123 4567 8912 3456'.length,\n                '0123 4567 8912 3456'.length,\n            ]);\n        });\n\n        it('enable long mask if number of digits is >16 (by paste)', () => {\n            const maskModel = new MaskModel(EMPTY_STATE, maskitoOptions);\n\n            maskModel.addCharacters('01234abc567891234567');\n\n            expect(maskModel.value).toBe('01234567891234567');\n            expect(maskModel.selection).toEqual([\n                '01234567891234567'.length,\n                '01234567891234567'.length,\n            ]);\n        });\n\n        it('enable long mask if number of digits is >16 (by adding new character to the previous mask)', () => {\n            const initialValue = '0123 4567 8912 3456';\n            const maskModel = new MaskModel(\n                {\n                    value: initialValue,\n                    selection: [initialValue.length, initialValue.length],\n                },\n                maskitoOptions,\n            );\n\n            maskModel.addCharacters('7');\n\n            expect(maskModel.value).toBe('01234567891234567');\n            expect(maskModel.selection).toEqual([\n                '01234567891234567'.length,\n                '01234567891234567'.length,\n            ]);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/core/src/lib/classes/mask-model/tests/mask-model-fixed-characters.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {MASKITO_DEFAULT_OPTIONS} from '../../../constants';\nimport type {MaskitoMask, MaskitoOptions} from '../../../types';\nimport {MaskModel} from '../mask-model';\n\ndescribe('MaskModel | Fixed characters', () => {\n    describe('New typed character is equal to the previous (already existing) fixed character', () => {\n        const phoneMaskitoOptions: Required<MaskitoOptions> = {\n            ...MASKITO_DEFAULT_OPTIONS,\n            mask: [\n                '+',\n                '7',\n                ' ',\n                '(',\n                /\\d/,\n                /\\d/,\n                /\\d/,\n                ')',\n                ' ',\n                /\\d/,\n                '0',\n                /\\d/,\n                '-',\n                /\\d/,\n                /\\d/,\n                '-',\n                /\\d/,\n                /\\d/,\n            ],\n        };\n\n        const check = ({\n            initialValue,\n            addedCharacters,\n            expectedNewValue,\n        }: {\n            initialValue: string;\n            addedCharacters: string;\n            expectedNewValue: string;\n        }): void => {\n            const selection: [number, number] = [\n                initialValue.length,\n                initialValue.length,\n            ];\n            const maskModel = new MaskModel(\n                {\n                    selection,\n                    value: initialValue,\n                },\n                phoneMaskitoOptions,\n            );\n\n            try {\n                maskModel.addCharacters(addedCharacters);\n            } finally {\n                expect(maskModel.value).toBe(expectedNewValue);\n                expect(maskModel.selection).toEqual([\n                    expectedNewValue.length,\n                    expectedNewValue.length,\n                ]);\n            }\n        };\n\n        it('+7| => Type 7 => +7 (7', () => {\n            check({\n                initialValue: '+7',\n                addedCharacters: '7',\n                expectedNewValue: '+7 (7',\n            });\n        });\n\n        it('+7| => Type 9 => +7 (9', () => {\n            check({\n                initialValue: '+7',\n                addedCharacters: '9',\n                expectedNewValue: '+7 (9',\n            });\n        });\n\n        it('+7 | (space after seven) => Type 7 => +7 (7', () => {\n            check({\n                initialValue: '+7 ',\n                addedCharacters: '7',\n                expectedNewValue: '+7 (7',\n            });\n        });\n\n        it('+7 (7| => Type 7 => +7 (77', () => {\n            check({\n                initialValue: '+7 (7',\n                addedCharacters: '7',\n                expectedNewValue: '+7 (77',\n            });\n        });\n\n        it('+7 (900) 2| (next character is fixed character \"0\") => Type 1 => +7 (900) 201|', () => {\n            check({\n                initialValue: '+7 (900) 2',\n                addedCharacters: '1',\n                expectedNewValue: '+7 (900) 201',\n            });\n        });\n\n        it('+7 (900) 20| => Type 0 => +7 (900) 200|', () => {\n            check({\n                initialValue: '+7 (900) 20',\n                addedCharacters: '0',\n                expectedNewValue: '+7 (900) 200',\n            });\n        });\n    });\n\n    describe('Attempt to insert invalid characters for `overwriteMode: replace`', () => {\n        const testCases: Record<string, MaskitoMask> = {\n            '[\"$\", /d/, /d/]': ['$', /\\d/, /\\d/],\n            'dynamic mask': ({value}) => {\n                const digitsCount = value.replaceAll(/\\D/g, '').length;\n\n                return [\n                    '$',\n                    ...Array.from<RegExp>({length: digitsCount || 1}).fill(/\\d/),\n                ];\n            },\n        };\n\n        Object.entries(testCases).forEach(([title, mask]) => {\n            describe(`mask expression contains leading characters – ${title}`, () => {\n                const options: Required<MaskitoOptions> = {\n                    ...MASKITO_DEFAULT_OPTIONS,\n                    mask,\n                    overwriteMode: 'replace',\n                };\n\n                it('$1|2 => Type A => $1|2', () => {\n                    const value = '$12';\n                    const selection = [2, 2] as const;\n                    const maskModel = new MaskModel({value, selection}, options);\n\n                    expect(() => maskModel.addCharacters('q')).toThrow();\n                    expect(maskModel.value).toBe(value);\n                    expect(maskModel.selection).toEqual(selection);\n                });\n\n                it('$|12 => Type $ => $|12', () => {\n                    const value = '$12';\n                    const selection = [1, 1] as const;\n                    const maskModel = new MaskModel({value, selection}, options);\n\n                    expect(() => maskModel.addCharacters('$')).toThrow();\n                    expect(maskModel.value).toBe(value);\n                    expect(maskModel.selection).toEqual(selection);\n                });\n\n                it('$|12 => Type X => $|12', () => {\n                    const value = '$12';\n                    const selection = [1, 1] as const;\n                    const maskModel = new MaskModel({value, selection}, options);\n\n                    expect(() => maskModel.addCharacters('X')).toThrow();\n                    expect(maskModel.value).toBe(value);\n                    expect(maskModel.selection).toEqual(selection);\n                });\n            });\n        });\n    });\n\n    describe('Dynamic mask expression + trailing fixed character', () => {\n        const postfix = 'left';\n        const timeMask: ReadonlyArray<RegExp | string> = [/\\d/, /\\d/, ':', /\\d/, /\\d/];\n        const timeWithPostfixMask: Required<MaskitoOptions> = {\n            ...MASKITO_DEFAULT_OPTIONS,\n            mask: ({value}) => {\n                let digitsCount = Math.min(value.replaceAll(/\\D/g, '').length, 4);\n                const afterLastDigit =\n                    timeMask.findIndex((x) => typeof x !== 'string' && !--digitsCount) +\n                    1;\n\n                return afterLastDigit\n                    ? timeMask.slice(0, afterLastDigit).concat(...postfix)\n                    : [];\n            },\n            overwriteMode: 'replace',\n        };\n\n        it('adds trailing postfix on enter of digit inside empty textfield', () => {\n            const maskModel = new MaskModel(\n                {\n                    selection: [0, 0],\n                    value: '',\n                },\n                timeWithPostfixMask,\n            );\n\n            maskModel.addCharacters('1');\n\n            expect(maskModel.value).toBe('1left');\n            expect(maskModel.selection).toEqual([1, 1]);\n        });\n\n        it('adds trailing postfix on paste of many digits inside empty textfield', () => {\n            const maskModel = new MaskModel(\n                {\n                    selection: [0, 0],\n                    value: '',\n                },\n                timeWithPostfixMask,\n            );\n\n            maskModel.addCharacters('123');\n\n            expect(maskModel.value).toBe('12:3left');\n            expect(maskModel.selection).toEqual(['12:3'.length, '12:3'.length]);\n        });\n\n        it('edits digit in the middle', () => {\n            const maskModel = new MaskModel(\n                {\n                    selection: [1, 1],\n                    value: '12:3left',\n                },\n                timeWithPostfixMask,\n            );\n\n            maskModel.addCharacters('4');\n\n            expect(maskModel.value).toBe('14:3left');\n            expect(maskModel.selection).toEqual([3, 3]);\n        });\n\n        it('erases the last digit without losing trailing fixed characters', () => {\n            const maskModel = new MaskModel(\n                {\n                    selection: [3, 4],\n                    value: '12:3left',\n                },\n                timeWithPostfixMask,\n            );\n\n            maskModel.deleteCharacters();\n\n            expect(maskModel.value).toBe('12left');\n            expect(maskModel.selection).toEqual([2, 2]);\n        });\n\n        it('erases the digit in the middle without losing trailing fixed character', () => {\n            const maskModel = new MaskModel(\n                {\n                    selection: [3, 4],\n                    value: '12:34 left',\n                },\n                timeWithPostfixMask,\n            );\n\n            maskModel.deleteCharacters();\n\n            expect(maskModel.value).toBe('12:4left');\n            expect(maskModel.selection).toEqual([3, 3]);\n        });\n    });\n\n    it('attempt to enter invalid character at the position of fixed character', () => {\n        const dateMask: Required<MaskitoOptions> = {\n            ...MASKITO_DEFAULT_OPTIONS,\n            mask: [/\\d/, /\\d/, '.', /\\d/, /\\d/],\n        };\n\n        const selection = [2, 2] as const;\n        const maskModel = new MaskModel(\n            {\n                selection,\n                value: '12',\n            },\n            dateMask,\n        );\n\n        expect(() => maskModel.addCharacters('#')).toThrow();\n        expect(maskModel.value).toBe('12');\n        expect(maskModel.selection).toEqual(selection);\n    });\n\n    it('accepts valid character at the position of fixed character', () => {\n        const dateMask: Required<MaskitoOptions> = {\n            ...MASKITO_DEFAULT_OPTIONS,\n            mask: [/\\d/, /\\d/, ':', /\\d/, /\\d/],\n        };\n\n        const selection = [2, 2] as const;\n        const maskModel = new MaskModel(\n            {\n                selection,\n                value: '12',\n            },\n            dateMask,\n        );\n\n        maskModel.addCharacters(':');\n\n        expect(maskModel.value).toBe('12:');\n        expect(maskModel.selection).toEqual(['12:'.length, '12:'.length]);\n    });\n});\n"
  },
  {
    "path": "projects/core/src/lib/classes/mask-model/utils/apply-overwrite-mode.ts",
    "content": "import type {ElementState, MaskitoOptions} from '../../../types';\n\nexport function applyOverwriteMode(\n    {value, selection}: ElementState,\n    newCharacters: string,\n    mode: MaskitoOptions['overwriteMode'],\n): ElementState {\n    const [from, to] = selection;\n    const computedMode = typeof mode === 'function' ? mode({value, selection}) : mode;\n\n    return {\n        value,\n        selection:\n            computedMode === 'replace'\n                ? [from, Math.max(from + newCharacters.length, to)]\n                : [from, to],\n    };\n}\n"
  },
  {
    "path": "projects/core/src/lib/classes/mask-model/utils/calibrate-value-by-mask.ts",
    "content": "import type {ElementState, MaskitoMaskExpression} from '../../../types';\nimport {guessValidValueByPattern} from './guess-valid-value-by-pattern';\nimport {guessValidValueByRegExp} from './guess-valid-value-by-reg-exp';\nimport {validateValueWithMask} from './validate-value-with-mask';\n\nexport function calibrateValueByMask(\n    elementState: ElementState,\n    mask: MaskitoMaskExpression,\n    initialElementState: ElementState | null = null,\n): ElementState {\n    if (validateValueWithMask(elementState.value, mask)) {\n        return elementState;\n    }\n\n    const {value, selection} = Array.isArray(mask)\n        ? guessValidValueByPattern(elementState, mask, initialElementState)\n        : guessValidValueByRegExp(elementState, mask);\n\n    return {\n        selection,\n        value: Array.isArray(mask) ? value.slice(0, mask.length) : value,\n    };\n}\n"
  },
  {
    "path": "projects/core/src/lib/classes/mask-model/utils/get-leading-fixed-characters.ts",
    "content": "import type {ElementState} from '../../../types';\nimport {isFixedCharacter} from './is-fixed-character';\n\nexport function getLeadingFixedCharacters(\n    mask: Array<RegExp | string>,\n    validatedValuePart: string,\n    newCharacter: string,\n    initialElementState: ElementState | null,\n): string {\n    let leadingFixedCharacters = '';\n\n    for (let i = validatedValuePart.length; i < mask.length; i++) {\n        const charConstraint = mask[i] || '';\n        const isInitiallyExisted = initialElementState?.value[i] === charConstraint;\n\n        if (\n            !isFixedCharacter(charConstraint) ||\n            (charConstraint === newCharacter && !isInitiallyExisted)\n        ) {\n            return leadingFixedCharacters;\n        }\n\n        leadingFixedCharacters += charConstraint;\n    }\n\n    return leadingFixedCharacters;\n}\n"
  },
  {
    "path": "projects/core/src/lib/classes/mask-model/utils/guess-valid-value-by-pattern.ts",
    "content": "import type {ElementState} from '../../../types';\nimport {getLeadingFixedCharacters} from './get-leading-fixed-characters';\nimport {isFixedCharacter} from './is-fixed-character';\nimport {validateValueWithMask} from './validate-value-with-mask';\n\nexport function guessValidValueByPattern(\n    elementState: ElementState,\n    mask: Array<RegExp | string>,\n    initialElementState: ElementState | null,\n): ElementState {\n    let maskedFrom: number | null = null;\n    let maskedTo: number | null = null;\n\n    const maskedValue = Array.from(elementState.value).reduce(\n        (validatedCharacters, char, charIndex) => {\n            const leadingCharacters = getLeadingFixedCharacters(\n                mask,\n                validatedCharacters,\n                char,\n                initialElementState,\n            );\n            const newValidatedChars = `${validatedCharacters}${leadingCharacters}`;\n            const charConstraint = mask[newValidatedChars.length] || '';\n\n            if (maskedFrom === null && charIndex >= elementState.selection[0]) {\n                maskedFrom = newValidatedChars.length;\n            }\n\n            if (maskedTo === null && charIndex >= elementState.selection[1]) {\n                maskedTo = newValidatedChars.length;\n            }\n\n            if (isFixedCharacter(charConstraint)) {\n                return `${newValidatedChars}${charConstraint}`;\n            }\n\n            if (char.match(charConstraint)) {\n                return `${newValidatedChars}${char}`;\n            }\n\n            return leadingCharacters.startsWith(char)\n                ? newValidatedChars\n                : validatedCharacters;\n        },\n        '',\n    );\n\n    const trailingFixedCharacters = getLeadingFixedCharacters(\n        mask,\n        maskedValue,\n        '',\n        initialElementState,\n    );\n\n    return {\n        value: validateValueWithMask(`${maskedValue}${trailingFixedCharacters}`, mask)\n            ? `${maskedValue}${trailingFixedCharacters}`\n            : maskedValue,\n        // issues: https://github.com/typescript-eslint/typescript-eslint/issues/12069\n        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n        selection: [maskedFrom ?? maskedValue.length, maskedTo ?? maskedValue.length],\n    };\n}\n"
  },
  {
    "path": "projects/core/src/lib/classes/mask-model/utils/guess-valid-value-by-reg-exp.ts",
    "content": "import type {ElementState} from '../../../types';\n\nexport function guessValidValueByRegExp(\n    {value, selection}: ElementState,\n    maskRegExp: RegExp,\n): ElementState {\n    const [from, to] = selection;\n    let newFrom = from;\n    let newTo = to;\n\n    const validatedValue = Array.from(value).reduce((validatedValuePart, char, i) => {\n        const newPossibleValue = `${validatedValuePart}${char}`;\n\n        if (from === i) {\n            newFrom = validatedValuePart.length;\n        }\n\n        if (to === i) {\n            newTo = validatedValuePart.length;\n        }\n\n        return newPossibleValue.match(maskRegExp) ? newPossibleValue : validatedValuePart;\n    }, '');\n\n    return {\n        value: validatedValue,\n        selection: [\n            Math.min(newFrom, validatedValue.length),\n            Math.min(newTo, validatedValue.length),\n        ],\n    };\n}\n"
  },
  {
    "path": "projects/core/src/lib/classes/mask-model/utils/is-fixed-character.ts",
    "content": "export function isFixedCharacter(char: RegExp | string): char is string {\n    return typeof char === 'string';\n}\n"
  },
  {
    "path": "projects/core/src/lib/classes/mask-model/utils/remove-fixed-mask-characters.ts",
    "content": "import type {ElementState, MaskitoMaskExpression} from '../../../types';\nimport {isFixedCharacter} from './is-fixed-character';\n\nexport function removeFixedMaskCharacters(\n    initialElementState: ElementState,\n    mask: MaskitoMaskExpression,\n): ElementState {\n    if (!Array.isArray(mask)) {\n        return initialElementState;\n    }\n\n    const [from, to] = initialElementState.selection;\n    const selection: number[] = [];\n\n    const unmaskedValue = Array.from(initialElementState.value).reduce(\n        (rawValue, char, i) => {\n            const charConstraint = mask[i] || '';\n\n            if (i === from) {\n                selection.push(rawValue.length);\n            }\n\n            if (i === to) {\n                selection.push(rawValue.length);\n            }\n\n            return isFixedCharacter(charConstraint) && charConstraint === char\n                ? rawValue\n                : `${rawValue}${char}`;\n        },\n        '',\n    );\n\n    if (selection.length < 2) {\n        selection.push(\n            ...Array.from<number>({length: 2 - selection.length}).fill(\n                unmaskedValue.length,\n            ),\n        );\n    }\n\n    return {\n        value: unmaskedValue,\n        selection: [selection[0]!, selection[1]!],\n    };\n}\n"
  },
  {
    "path": "projects/core/src/lib/classes/mask-model/utils/validate-value-with-mask.ts",
    "content": "import type {MaskitoMaskExpression} from '../../../types';\nimport {isFixedCharacter} from './is-fixed-character';\n\nexport function validateValueWithMask(\n    value: string,\n    maskExpression: MaskitoMaskExpression,\n): boolean {\n    if (Array.isArray(maskExpression)) {\n        return (\n            value.length === maskExpression.length &&\n            Array.from(value).every((char, i) => {\n                const charConstraint = maskExpression[i] || '';\n\n                return isFixedCharacter(charConstraint)\n                    ? char === charConstraint\n                    : char.match(charConstraint);\n            })\n        );\n    }\n\n    return maskExpression.test(value);\n}\n"
  },
  {
    "path": "projects/core/src/lib/constants/default-element-predicate.ts",
    "content": "import type {MaskitoElementPredicate} from '../types';\nimport {maskitoAdaptContentEditable} from '../utils/content-editable';\n\nexport const MASKITO_DEFAULT_ELEMENT_PREDICATE: MaskitoElementPredicate = (e) =>\n    e.isContentEditable\n        ? maskitoAdaptContentEditable(e)\n        : e.querySelector<HTMLInputElement | HTMLTextAreaElement>('input,textarea') ||\n          (e as HTMLInputElement | HTMLTextAreaElement);\n"
  },
  {
    "path": "projects/core/src/lib/constants/default-options.ts",
    "content": "import type {MaskitoOptions} from '../types';\n\nexport const MASKITO_DEFAULT_OPTIONS: Required<MaskitoOptions> = {\n    mask: /^.*$/,\n    preprocessors: [],\n    postprocessors: [],\n    plugins: [],\n    overwriteMode: 'shift',\n};\n"
  },
  {
    "path": "projects/core/src/lib/constants/index.ts",
    "content": "export * from './default-element-predicate';\nexport * from './default-options';\n"
  },
  {
    "path": "projects/core/src/lib/mask.ts",
    "content": "import {MaskHistory, MaskModel} from './classes';\nimport {MASKITO_DEFAULT_OPTIONS} from './constants';\nimport {createBrokenDefaultPlugin, createDoubleSpacePlugin} from './plugins';\nimport type {\n    ElementState,\n    MaskitoElement,\n    MaskitoOptions,\n    SelectionRange,\n    TypedInputEvent,\n} from './types';\nimport {\n    areElementStatesEqual,\n    areElementValuesEqual,\n    EventListener,\n    getLineSelection,\n    getNotEmptySelection,\n    getWordSelection,\n    isRedo,\n    isUndo,\n    maskitoPipe,\n    maskitoTransform,\n} from './utils';\n\nconst BUILT_IN_PLUGINS = [createDoubleSpacePlugin(), createBrokenDefaultPlugin()];\n\nexport class Maskito extends MaskHistory {\n    private readonly isTextArea = this.element.nodeName === 'TEXTAREA';\n    private readonly eventListener = new EventListener(this.element);\n    private readonly options: Required<MaskitoOptions> = {\n        ...MASKITO_DEFAULT_OPTIONS,\n        ...this.maskitoOptions,\n    };\n\n    private upcomingElementState: ElementState | null = null;\n\n    private readonly preprocessor = maskitoPipe(this.options.preprocessors);\n\n    private readonly postprocessor = maskitoPipe(this.options.postprocessors);\n\n    private readonly teardowns = this.options.plugins\n        .concat(BUILT_IN_PLUGINS)\n        .map((plugin) => plugin(this.element, this.options));\n\n    constructor(\n        private readonly element: MaskitoElement,\n        private readonly maskitoOptions: MaskitoOptions,\n    ) {\n        super();\n        this.updateHistory(this.elementState);\n\n        this.eventListener.listen('keydown', (event) => {\n            if (isRedo(event)) {\n                event.preventDefault();\n\n                return this.redo();\n            }\n\n            if (isUndo(event)) {\n                event.preventDefault();\n\n                return this.undo();\n            }\n        });\n\n        this.eventListener.listen('beforeinput', (event) => {\n            const isForward = event.inputType.includes('Forward');\n\n            this.updateHistory(this.elementState);\n\n            switch (event.inputType) {\n                case 'deleteByCut':\n                case 'deleteContentBackward':\n                case 'deleteContentForward':\n                    return this.handleDelete({\n                        event,\n                        isForward,\n                        selection: getNotEmptySelection(this.elementState, isForward),\n                    });\n                case 'deleteHardLineBackward':\n                case 'deleteHardLineForward':\n                case 'deleteSoftLineBackward':\n                case 'deleteSoftLineForward':\n                    return this.handleDelete({\n                        event,\n                        isForward,\n                        selection: getLineSelection(this.elementState, isForward),\n                        force: true,\n                    });\n                case 'deleteWordBackward':\n                case 'deleteWordForward':\n                    return this.handleDelete({\n                        event,\n                        isForward,\n                        selection: getWordSelection(this.elementState, isForward),\n                    });\n                case 'historyRedo':\n                    event.preventDefault();\n\n                    return this.redo();\n                // historyUndo/historyRedo will not be triggered if value was modified programmatically\n                case 'historyUndo':\n                    event.preventDefault();\n\n                    return this.undo();\n                case 'insertCompositionText':\n                    return; // will be handled inside `compositionend` event\n                case 'insertLineBreak':\n                case 'insertParagraph':\n                    return this.handleEnter(event);\n                case 'insertReplacementText':\n                    /**\n                     * According {@link https://www.w3.org/TR/input-events-2 W3C specification}:\n                     * > `insertReplacementText` – insert or replace existing text by means of a spell checker,\n                     * > auto-correct, writing suggestions or similar.\n                     * ___\n                     * Firefox emits `insertReplacementText` event for its suggestion/autofill and for spell checker.\n                     * However, it is impossible to detect which part of the textfield value is going to be replaced\n                     * (`selectionStart` and `selectionEnd` just equal to the last caret position).\n                     * ___\n                     * Chrome does not fire `beforeinput` event for its suggestion/autofill.\n                     * It emits only `input` event with `inputType` and `data` set to `undefined`.\n                     * ___\n                     * All these browser limitations make us to validate the result value later in `input` event.\n                     */\n                    return;\n                case 'insertFromDrop':\n                case 'insertFromPaste':\n                case 'insertText':\n                default:\n                    return this.handleInsert(\n                        event,\n                        event.data ??\n                            // `event.data` for `contentEditable` is always `null` for paste/drop events\n                            event.dataTransfer?.getData('text/plain') ??\n                            '',\n                    );\n            }\n        });\n\n        this.eventListener.listen(\n            'input',\n            () => {\n                if (\n                    this.upcomingElementState &&\n                    !areElementStatesEqual(this.upcomingElementState, this.elementState)\n                ) {\n                    this.updateElementState(this.upcomingElementState);\n                }\n\n                this.upcomingElementState = null;\n            },\n            {capture: true},\n        );\n\n        this.eventListener.listen('input', ({inputType}) => {\n            if (inputType === 'insertCompositionText') {\n                return; // will be handled inside `compositionend` event\n            }\n\n            this.ensureValueFitsMask();\n            this.updateHistory(this.elementState);\n        });\n\n        this.eventListener.listen('compositionend', () => {\n            this.ensureValueFitsMask();\n            this.updateHistory(this.elementState);\n        });\n    }\n\n    public destroy(): void {\n        this.eventListener.destroy();\n        this.teardowns.forEach((teardown) => teardown?.());\n    }\n\n    protected updateElementState(\n        {value, selection}: ElementState,\n        eventInit?: Pick<TypedInputEvent, 'data' | 'inputType'>,\n    ): void {\n        const initialValue = this.elementState.value;\n\n        this.updateValue(value);\n        this.updateSelectionRange(selection);\n\n        if (eventInit && initialValue !== value) {\n            this.dispatchInputEvent(eventInit);\n        }\n    }\n\n    private get elementState(): ElementState {\n        const {value, selectionStart, selectionEnd} = this.element;\n\n        return {\n            value,\n            selection: [selectionStart ?? 0, selectionEnd ?? 0],\n        };\n    }\n\n    private get maxLength(): number {\n        const {maxLength} = this.element;\n\n        return maxLength === -1 ? Infinity : maxLength;\n    }\n\n    private updateSelectionRange([from, to]: SelectionRange): void {\n        const {element} = this;\n\n        if (\n            element.matches(':focus') &&\n            (element.selectionStart !== from || element.selectionEnd !== to)\n        ) {\n            element.setSelectionRange(from, to);\n        }\n    }\n\n    private updateValue(value: string): void {\n        /**\n         * Don't \"disturb\" unnecessarily `value`-setter\n         * (i.e. it breaks React controlled input behavior)\n         */\n        if (this.element.value !== value || this.element.isContentEditable) {\n            this.element.value = value;\n        }\n    }\n\n    private ensureValueFitsMask(): void {\n        this.updateElementState(maskitoTransform(this.elementState, this.options), {\n            inputType: 'insertText',\n            data: null,\n        });\n    }\n\n    private dispatchInputEvent(\n        eventInit: Pick<TypedInputEvent, 'data' | 'inputType'> = {\n            inputType: 'insertText',\n            data: null,\n        },\n    ): void {\n        this.element.dispatchEvent(\n            new InputEvent('input', {\n                ...eventInit,\n                bubbles: true,\n                cancelable: false,\n            }),\n        );\n    }\n\n    private handleDelete({\n        event,\n        selection,\n        isForward,\n    }: {\n        event: TypedInputEvent;\n        selection: SelectionRange;\n        isForward: boolean;\n        force?: boolean;\n    }): void {\n        const initialState: ElementState = {\n            value: this.elementState.value,\n            selection,\n        };\n        const {elementState} = this.preprocessor(\n            {\n                elementState: initialState,\n                data: '',\n            },\n            isForward ? 'deleteForward' : 'deleteBackward',\n        );\n        const maskModel = new MaskModel(elementState, this.options);\n\n        maskModel.deleteCharacters();\n\n        const newElementState = this.postprocessor(maskModel, initialState);\n\n        if (\n            areElementValuesEqual(initialState, elementState, maskModel, newElementState)\n        ) {\n            const [from, to] = elementState.selection;\n\n            event.preventDefault();\n\n            // User presses Backspace/Delete for the fixed value\n            return this.updateSelectionRange(isForward ? [to, to] : [from, from]);\n        }\n\n        this.upcomingElementState = newElementState;\n    }\n\n    private handleInsert(event: TypedInputEvent, data: string): void {\n        const {options, maxLength, elementState: initialElementState} = this;\n        const [from, to] = initialElementState.selection;\n        const {elementState, data: insertedText = data} = this.preprocessor(\n            {\n                data,\n                elementState: initialElementState,\n            },\n            'insert',\n        );\n        const maskModel = new MaskModel(elementState, options);\n\n        try {\n            maskModel.addCharacters(insertedText);\n        } catch {\n            return event.preventDefault();\n        }\n\n        this.upcomingElementState = this.clampState(\n            this.postprocessor(maskModel, initialElementState),\n        );\n\n        /**\n         * When textfield value length is already equal to attribute `maxlength`,\n         * pressing any key (even with valid value) does not emit `input` event\n         * (except to the case when user replaces some characters by selection).\n         */\n        const noInputEventDispatch =\n            initialElementState.value.length >= maxLength && from === to;\n\n        if (noInputEventDispatch) {\n            if (\n                options.overwriteMode === 'replace' &&\n                !areElementStatesEqual(this.upcomingElementState, initialElementState)\n            ) {\n                this.dispatchInputEvent({inputType: 'insertText', data});\n            } else {\n                /**\n                 * This `beforeinput` event will not be followed by `input` event –\n                 * clear computed state to avoid any possible side effect\n                 * for new possible `input` event without preceding `beforeinput` event\n                 * (e.g. browser autofill, `document.execCommand('delete')` etc.)\n                 */\n                this.upcomingElementState = null;\n            }\n        }\n    }\n\n    private handleEnter(event: TypedInputEvent): void {\n        if (this.isTextArea || this.element.isContentEditable) {\n            this.handleInsert(event, '\\n');\n        }\n    }\n\n    private clampState({value, selection}: ElementState): ElementState {\n        const [from, to] = selection;\n        const max = this.maxLength;\n\n        return {\n            value: value.slice(0, max),\n            selection: [Math.min(from, max), Math.min(to, max)],\n        };\n    }\n}\n"
  },
  {
    "path": "projects/core/src/lib/plugins/broken-prevent-default.plugin.ts",
    "content": "import type {MaskitoPlugin} from '@maskito/core';\n\nimport type {TypedInputEvent} from '../types';\nimport {EventListener} from '../utils';\n\n/**\n * All `input` events with `inputType=deleteContentBackward` always follows `beforeinput` event with the same `inputType`.\n * If `beforeinput[inputType=deleteContentBackward]` is prevented, subsequent `input[inputType=deleteContentBackward]` is prevented too.\n * There is an exception – Android devices with Microsoft SwiftKey Keyboard in Mobile Chrome.\n * These devices ignores `preventDefault` for `beforeinput` event if Backspace is pressed.\n * @see https://github.com/taiga-family/maskito/issues/2135#issuecomment-2980729647\n * ___\n * TODO: track Chromium bug report and delete this plugin after bug fix\n * https://issues.chromium.org/issues/40885402\n */\nexport function createBrokenDefaultPlugin(): MaskitoPlugin {\n    return (element) => {\n        const eventListener = new EventListener(element);\n\n        let isVirtualAndroidKeyboard = false;\n        let beforeinputEvent: TypedInputEvent;\n        let value = element.value;\n\n        eventListener.listen('keydown', ({key}) => {\n            isVirtualAndroidKeyboard = key === 'Unidentified';\n        });\n\n        eventListener.listen('beforeinput', (event) => {\n            beforeinputEvent = event;\n            value = element.value;\n        });\n\n        eventListener.listen(\n            'input',\n            (event) => {\n                if (\n                    isVirtualAndroidKeyboard &&\n                    beforeinputEvent.defaultPrevented &&\n                    beforeinputEvent.inputType === 'deleteContentBackward' &&\n                    event.inputType === 'deleteContentBackward'\n                ) {\n                    element.value = value;\n                }\n            },\n            {capture: true},\n        );\n\n        return () => eventListener.destroy();\n    };\n}\n"
  },
  {
    "path": "projects/core/src/lib/plugins/change-event-plugin.ts",
    "content": "import type {MaskitoPlugin} from '../types';\n\nexport function maskitoChangeEventPlugin(): MaskitoPlugin {\n    return (element) => {\n        if (element.isContentEditable) {\n            return;\n        }\n\n        let value = element.value;\n\n        const valueListener = (): void => {\n            value = element.value;\n        };\n        const blurListener = (): void => {\n            if (element.value !== value) {\n                element.dispatchEvent(new Event('change', {bubbles: true}));\n            }\n        };\n\n        element.addEventListener('focus', valueListener);\n        element.addEventListener('change', valueListener);\n        element.addEventListener('blur', blurListener);\n\n        return () => {\n            element.removeEventListener('focus', valueListener);\n            element.removeEventListener('change', valueListener);\n            element.removeEventListener('blur', blurListener);\n        };\n    };\n}\n"
  },
  {
    "path": "projects/core/src/lib/plugins/double-space.plugin.ts",
    "content": "import type {MaskitoPlugin} from '@maskito/core';\n\nimport type {TypedInputEvent} from '../types';\nimport {EventListener} from '../utils';\n\nconst SPACE = ' ';\n\n/**\n * 1. Android user (with G-board keyboard or similar) presses 1st space\n * ```\n * {type: \"beforeinput\", data: \" \", inputType: \"insertText\"}\n * ```\n * 2. User presses 2nd space\n * ```\n * // Android tries to delete previously inserted space\n * {type: \"beforeinput\", inputType: \"deleteContentBackward\"}\n * {type: \"beforeinput\", data: \". \", inputType: \"insertText\"}\n * ```\n * ---------\n * 1. MacOS user presses 1st space\n * ```\n * {type: \"beforeinput\", data: \" \", inputType: \"insertText\"}\n * ```\n * 2. User presses 2nd space\n * ```\n * // MacOS automatically run `element.setSelectionRange(indexBeforeSpace, indexAfterSpace)` and then\n * {type: \"beforeinput\", data: \". \", inputType: \"insertText\"}\n * ```\n * ---------\n * @see https://github.com/taiga-family/maskito/issues/2023\n */\nexport function createDoubleSpacePlugin(): MaskitoPlugin {\n    let prevValue = '';\n    let prevCaretIndex = 0;\n    let prevEvent: TypedInputEvent | null = null;\n    let prevRejectedSpace = false;\n\n    return (element) => {\n        const eventListener = new EventListener(element);\n\n        eventListener.listen('beforeinput', (event) => {\n            const {value, selectionStart, selectionEnd} = element;\n            const rejectedSpace =\n                prevEvent?.inputType === 'insertText' &&\n                prevEvent.data === SPACE &&\n                !value.slice(0, Number(selectionEnd)).endsWith(SPACE);\n\n            if (event.inputType === 'insertText' && event.data === `.${SPACE}`) {\n                if (\n                    prevEvent?.inputType === 'deleteContentBackward' &&\n                    prevRejectedSpace\n                ) {\n                    // Android\n                    element.value = prevValue;\n                    element.setSelectionRange(prevCaretIndex, prevCaretIndex);\n                } else if (rejectedSpace) {\n                    // Mac OS\n                    element.setSelectionRange(selectionStart, selectionStart);\n                }\n            }\n\n            prevRejectedSpace = rejectedSpace;\n            prevEvent = event;\n            prevValue = value;\n            prevCaretIndex = Number(\n                (rejectedSpace ? prevCaretIndex : selectionEnd) === value.length\n                    ? selectionEnd\n                    : selectionStart,\n            );\n        });\n\n        return () => eventListener.destroy();\n    };\n}\n"
  },
  {
    "path": "projects/core/src/lib/plugins/index.ts",
    "content": "export * from './broken-prevent-default.plugin';\nexport * from './change-event-plugin';\nexport * from './double-space.plugin';\nexport * from './initial-calibration-plugin';\nexport * from './strict-composition-plugin';\n"
  },
  {
    "path": "projects/core/src/lib/plugins/initial-calibration-plugin.ts",
    "content": "import type {MaskitoOptions, MaskitoPlugin} from '../types';\nimport {maskitoTransform, maskitoUpdateElement} from '../utils';\n\nexport function maskitoInitialCalibrationPlugin(\n    customOptions?: MaskitoOptions,\n): MaskitoPlugin {\n    return (element, options) => {\n        const from = element.selectionStart ?? 0;\n        const to = element.selectionEnd ?? 0;\n\n        maskitoUpdateElement(element, {\n            value: maskitoTransform(element.value, customOptions || options),\n            selection: [from, to],\n        });\n    };\n}\n"
  },
  {
    "path": "projects/core/src/lib/plugins/strict-composition-plugin.ts",
    "content": "import type {ElementState, MaskitoPlugin, TypedInputEvent} from '../types';\nimport {areElementStatesEqual, maskitoTransform, maskitoUpdateElement} from '../utils';\n\nexport function maskitoStrictCompositionPlugin(): MaskitoPlugin {\n    return (element, maskitoOptions) => {\n        const listener = (event: TypedInputEvent): void => {\n            if (event.inputType !== 'insertCompositionText') {\n                return;\n            }\n\n            const selection = [\n                element.selectionStart ?? 0,\n                element.selectionEnd ?? 0,\n            ] as const;\n            const elementState: ElementState = {\n                selection,\n                value: element.value,\n            };\n            const validatedState = maskitoTransform(elementState, maskitoOptions);\n\n            if (!areElementStatesEqual(elementState, validatedState)) {\n                event.preventDefault();\n                maskitoUpdateElement(element, validatedState);\n            }\n        };\n\n        element.addEventListener('input', listener as EventListener);\n\n        return () => element.removeEventListener('input', listener as EventListener);\n    };\n}\n"
  },
  {
    "path": "projects/core/src/lib/types/element-predicate.ts",
    "content": "import type {MaskitoElement} from './maskito-element';\n\nexport type MaskitoElementPredicate = (\n    element: HTMLElement,\n) => MaskitoElement | Promise<MaskitoElement>;\n"
  },
  {
    "path": "projects/core/src/lib/types/element-state.ts",
    "content": "import type {SelectionRange} from './selection-range';\n\nexport interface ElementState {\n    readonly value: string;\n    readonly selection: SelectionRange;\n}\n"
  },
  {
    "path": "projects/core/src/lib/types/index.ts",
    "content": "export * from './element-predicate';\nexport * from './element-state';\nexport * from './mask';\nexport * from './mask-options';\nexport * from './mask-processors';\nexport * from './maskito-element';\nexport * from './plugin';\nexport * from './selection-range';\nexport * from './typed-input-event';\n"
  },
  {
    "path": "projects/core/src/lib/types/mask-options.ts",
    "content": "import type {ElementState} from './element-state';\nimport type {MaskitoMask} from './mask';\nimport type {MaskitoPostprocessor, MaskitoPreprocessor} from './mask-processors';\nimport type {MaskitoPlugin} from './plugin';\n\nexport interface MaskitoOptions {\n    readonly mask: MaskitoMask;\n    readonly preprocessors?: readonly MaskitoPreprocessor[];\n    readonly postprocessors?: readonly MaskitoPostprocessor[];\n    readonly plugins?: readonly MaskitoPlugin[];\n    readonly overwriteMode?:\n        | 'replace'\n        | 'shift'\n        | ((elementState: ElementState) => 'replace' | 'shift');\n}\n"
  },
  {
    "path": "projects/core/src/lib/types/mask-processors.ts",
    "content": "import type {ElementState} from './element-state';\n\nexport type MaskitoPreprocessor = (\n    _: {\n        elementState: ElementState;\n        data: string;\n    },\n    actionType: 'deleteBackward' | 'deleteForward' | 'insert' | 'validation',\n) => {\n    elementState: ElementState;\n    data?: string;\n};\n\nexport type MaskitoPostprocessor = (\n    elementState: ElementState,\n    initialElementState: ElementState,\n) => ElementState;\n"
  },
  {
    "path": "projects/core/src/lib/types/mask.ts",
    "content": "import type {ElementState} from './element-state';\n\nexport type MaskitoMaskExpression = Array<RegExp | string> | RegExp;\n\nexport type MaskitoMask =\n    | MaskitoMaskExpression\n    | ((elementState: ElementState) => MaskitoMaskExpression);\n"
  },
  {
    "path": "projects/core/src/lib/types/maskito-element.ts",
    "content": "export type TextfieldLike = Pick<\n    HTMLInputElement,\n    | 'maxLength'\n    | 'select'\n    | 'selectionEnd'\n    | 'selectionStart'\n    | 'setSelectionRange'\n    | 'value'\n>;\nexport type MaskitoElement = HTMLElement & TextfieldLike;\n"
  },
  {
    "path": "projects/core/src/lib/types/plugin.ts",
    "content": "import type {MaskitoOptions} from './mask-options';\nimport type {MaskitoElement} from './maskito-element';\n\nexport type MaskitoPlugin = (\n    element: MaskitoElement,\n    options: Required<MaskitoOptions>,\n) => (() => void) | void;\n"
  },
  {
    "path": "projects/core/src/lib/types/selection-range.ts",
    "content": "export type SelectionRange = readonly [from: number, to: number];\n"
  },
  {
    "path": "projects/core/src/lib/types/typed-input-event.ts",
    "content": "export interface TypedInputEvent extends InputEvent {\n    inputType:\n        | 'deleteByCut' // Ctrl (Command) + X\n        | 'deleteContentBackward' // Backspace\n        | 'deleteContentForward' // Delete (Fn + Backspace)\n        | 'deleteHardLineBackward' // Ctrl (Command) + Backspace\n        | 'deleteHardLineForward'\n        | 'deleteSoftLineBackward' // Ctrl (Command) + Backspace\n        | 'deleteSoftLineForward'\n        | 'deleteWordBackward' // Alt (Option) + Backspace\n        | 'deleteWordForward' // Alt (Option) + Delete (Fn + Backspace)\n        | 'historyRedo' // Ctrl (Command) + Shift + Z\n        | 'historyUndo' // Ctrl (Command) + Z\n        | 'insertCompositionText'\n        | 'insertFromDrop'\n        | 'insertFromPaste' // Ctrl (Command) + V\n        | 'insertLineBreak'\n        | 'insertParagraph'\n        | 'insertReplacementText'\n        | 'insertText';\n}\n"
  },
  {
    "path": "projects/core/src/lib/utils/content-editable.ts",
    "content": "import type {MaskitoElement, TextfieldLike} from '../types';\nimport {getContentEditableSelection} from './dom/get-content-editable-selection';\nimport {setContentEditableSelection} from './dom/set-content-editable-selection';\n\nclass ContentEditableAdapter implements TextfieldLike {\n    public maxLength = Infinity;\n\n    constructor(private readonly element: HTMLElement) {}\n\n    public get value(): string {\n        return this.element.innerText.replace(/\\n\\n$/, '\\n');\n    }\n\n    public set value(value) {\n        // Setting into innerHTML of element with `white-space: pre;` style\n        this.element.innerHTML = value.replace(/\\n$/, '\\n\\n');\n    }\n\n    public get selectionStart(): number | null {\n        return getContentEditableSelection(this.element)[0];\n    }\n\n    public get selectionEnd(): number | null {\n        return getContentEditableSelection(this.element)[1];\n    }\n\n    public setSelectionRange(from: number | null, to: number | null): void {\n        setContentEditableSelection(this.element, [from ?? 0, to ?? 0]);\n    }\n\n    public select(): void {\n        this.setSelectionRange(0, this.value.length);\n    }\n}\n\nexport function maskitoAdaptContentEditable(element: HTMLElement): MaskitoElement {\n    const adapter = new ContentEditableAdapter(element);\n\n    return new Proxy(element, {\n        get(target, prop: keyof HTMLElement) {\n            if (prop in adapter) {\n                return adapter[prop as keyof ContentEditableAdapter];\n            }\n\n            const nativeProperty = target[prop];\n\n            return typeof nativeProperty === 'function'\n                ? nativeProperty.bind(target)\n                : nativeProperty;\n        },\n        set(target, prop: keyof HTMLElement, val, receiver) {\n            return Reflect.set(prop in adapter ? adapter : target, prop, val, receiver);\n        },\n    }) as MaskitoElement;\n}\n"
  },
  {
    "path": "projects/core/src/lib/utils/dom/event-listener.ts",
    "content": "import type {TypedInputEvent} from '../../types';\n\nexport class EventListener {\n    private readonly listeners: Array<() => void> = [];\n\n    constructor(private readonly element: HTMLElement) {}\n\n    public listen<E extends keyof HTMLElementEventMap>(\n        eventType: E,\n        fn: (\n            event: E extends 'beforeinput' | 'input'\n                ? TypedInputEvent\n                : HTMLElementEventMap[E],\n        ) => unknown,\n        options?: AddEventListenerOptions,\n    ): void {\n        const untypedFn = fn as (event: HTMLElementEventMap[E]) => unknown;\n\n        this.element.addEventListener(eventType, untypedFn, options);\n        this.listeners.push(() =>\n            this.element.removeEventListener(eventType, untypedFn, options),\n        );\n    }\n\n    public destroy(): void {\n        this.listeners.forEach((stopListen) => stopListen());\n    }\n}\n"
  },
  {
    "path": "projects/core/src/lib/utils/dom/get-content-editable-selection.ts",
    "content": "import type {SelectionRange} from '../../types';\n\nexport function getContentEditableSelection(element: HTMLElement): SelectionRange {\n    const {anchorOffset = 0, focusOffset = 0} =\n        element.ownerDocument.getSelection() || {};\n\n    const from = Math.min(anchorOffset, focusOffset);\n    const to = Math.max(anchorOffset, focusOffset);\n\n    return [from, to];\n}\n"
  },
  {
    "path": "projects/core/src/lib/utils/dom/history-events.ts",
    "content": "import {HotkeyCode, HotkeyModifier, isHotkey} from './hotkey';\n\nexport function isRedo(event: KeyboardEvent): boolean {\n    return (\n        isHotkey(event, HotkeyModifier.CTRL, HotkeyCode.Y) || // Windows\n        isHotkey(event, HotkeyModifier.CTRL | HotkeyModifier.SHIFT, HotkeyCode.Z) || // Windows & Android\n        isHotkey(event, HotkeyModifier.META | HotkeyModifier.SHIFT, HotkeyCode.Z) // macOS & iOS\n    );\n}\n\nexport function isUndo(event: KeyboardEvent): boolean {\n    return (\n        isHotkey(event, HotkeyModifier.CTRL, HotkeyCode.Z) || // Windows & Android\n        isHotkey(event, HotkeyModifier.META, HotkeyCode.Z) // macOS & iOS\n    );\n}\n"
  },
  {
    "path": "projects/core/src/lib/utils/dom/hotkey.ts",
    "content": "export const HotkeyModifier = {\n    CTRL: 1 << 0,\n    ALT: 1 << 1,\n    SHIFT: 1 << 2,\n    META: 1 << 3,\n} as const;\n\n// TODO add variants that can be processed correctly\nexport const HotkeyCode = {\n    Y: 89,\n    Z: 90,\n} as const;\n\n/**\n * Checks if the passed keyboard event match the required hotkey.\n *\n * @example\n * input.addEventListener('keydown', (event) => {\n *     if (isHotkey(event, HotkeyModifier.CTRL | HotkeyModifier.SHIFT, HotkeyCode.Z)) {\n *         // redo hotkey pressed\n *     }\n * })\n *\n * @return will return `true` only if the {@link HotkeyCode} matches and only the necessary\n * {@link HotkeyModifier modifiers} have been pressed\n */\nexport function isHotkey(\n    event: KeyboardEvent,\n    modifiers: (typeof HotkeyModifier)[keyof typeof HotkeyModifier],\n    hotkeyCode: (typeof HotkeyCode)[keyof typeof HotkeyCode],\n): boolean {\n    return (\n        event.ctrlKey === !!(modifiers & HotkeyModifier.CTRL) &&\n        event.altKey === !!(modifiers & HotkeyModifier.ALT) &&\n        event.shiftKey === !!(modifiers & HotkeyModifier.SHIFT) &&\n        event.metaKey === !!(modifiers & HotkeyModifier.META) &&\n        /**\n         * We intentionally use legacy {@link KeyboardEvent#keyCode `keyCode`} property. It is more\n         * \"keyboard-layout\"-independent than {@link KeyboardEvent#key `key`} or {@link KeyboardEvent#code `code`} properties.\n         * @see {@link https://github.com/taiga-family/maskito/issues/315 `KeyboardEvent#code` issue}\n         */\n        event.keyCode === hotkeyCode\n    );\n}\n"
  },
  {
    "path": "projects/core/src/lib/utils/dom/set-content-editable-selection.ts",
    "content": "import type {SelectionRange} from '../../types';\n\nexport function setContentEditableSelection(\n    element: HTMLElement,\n    [from, to]: SelectionRange,\n): void {\n    const document = element.ownerDocument;\n    const range = document.createRange();\n\n    range.setStart(\n        element.firstChild || element,\n        Math.min(from, element.textContent?.length ?? 0),\n    );\n    range.setEnd(\n        element.lastChild || element,\n        Math.min(to, element.textContent?.length ?? 0),\n    );\n    const selection = document.getSelection();\n\n    if (selection) {\n        selection.removeAllRanges();\n        selection.addRange(range);\n    }\n}\n"
  },
  {
    "path": "projects/core/src/lib/utils/dom/update-element.ts",
    "content": "import type {ElementState, MaskitoElement} from '../../types';\n\n/**\n * Sets value to element, and dispatches input event\n * if you passed ELementState, it also sets selection range\n *\n * @example\n * maskitoUpdateElement(input, newValue);\n * maskitoUpdateElement(input, elementState);\n *\n * @see {@link https://github.com/taiga-family/maskito/issues/804 issue}\n *\n * @return void\n */\nexport function maskitoUpdateElement(\n    element: MaskitoElement,\n    valueOrElementState: ElementState | string,\n): void {\n    const initialValue = element.value;\n\n    if (typeof valueOrElementState === 'string') {\n        element.value = valueOrElementState;\n    } else {\n        const [from, to] = valueOrElementState.selection;\n\n        element.value = valueOrElementState.value;\n\n        if (element.matches(':focus')) {\n            element.setSelectionRange(from, to);\n        }\n    }\n\n    if (element.value !== initialValue) {\n        element.dispatchEvent(\n            new Event(\n                'input',\n                /**\n                 * React handles this event only on bubbling phase\n                 *\n                 * here is the list of events that are processed in the capture stage, others are processed in the bubbling stage\n                 * https://github.com/facebook/react/blob/cb2439624f43c510007f65aea5c50a8bb97917e4/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js#L222\n                 */\n                {bubbles: true},\n            ),\n        );\n    }\n}\n"
  },
  {
    "path": "projects/core/src/lib/utils/element-states-equality.ts",
    "content": "import type {ElementState} from '../types';\n\nexport function areElementValuesEqual(\n    sampleState: ElementState,\n    ...states: ElementState[]\n): boolean {\n    return states.every(({value}) => value === sampleState.value);\n}\n\nexport function areElementStatesEqual(\n    sampleState: ElementState,\n    ...states: ElementState[]\n): boolean {\n    return states.every(\n        ({value, selection}) =>\n            value === sampleState.value &&\n            selection[0] === sampleState.selection[0] &&\n            selection[1] === sampleState.selection[1],\n    );\n}\n"
  },
  {
    "path": "projects/core/src/lib/utils/get-line-selection.ts",
    "content": "import type {ElementState, SelectionRange} from '../types';\n\nexport function getLineSelection(\n    {value, selection}: ElementState,\n    isForward: boolean,\n): SelectionRange {\n    const [from, to] = selection;\n\n    if (from !== to) {\n        return [from, to];\n    }\n\n    const nearestBreak = isForward\n        ? value.slice(from).indexOf('\\n') + 1 || value.length\n        : value.slice(0, to).lastIndexOf('\\n') + 1;\n\n    const selectFrom = isForward ? from : nearestBreak;\n    const selectTo = isForward ? nearestBreak : to;\n\n    return [selectFrom, selectTo];\n}\n"
  },
  {
    "path": "projects/core/src/lib/utils/get-not-empty-selection.ts",
    "content": "import type {ElementState, SelectionRange} from '../types';\n\nexport function getNotEmptySelection(\n    {value, selection}: ElementState,\n    isForward: boolean,\n): SelectionRange {\n    const [from, to] = selection;\n\n    if (from !== to) {\n        return [from, to];\n    }\n\n    const notEmptySelection = isForward ? [from, to + 1] : [from - 1, to];\n\n    return notEmptySelection.map((x) => Math.min(Math.max(x, 0), value.length)) as [\n        number,\n        number,\n    ];\n}\n"
  },
  {
    "path": "projects/core/src/lib/utils/get-word-selection.ts",
    "content": "import type {ElementState, SelectionRange} from '../types';\n\nconst TRAILING_SPACES_REG = /\\s+$/g;\nconst LEADING_SPACES_REG = /^\\s+/g;\nconst SPACE_REG = /\\s/;\n\nexport function getWordSelection(\n    {value, selection}: ElementState,\n    isForward: boolean,\n): SelectionRange {\n    const [from, to] = selection;\n\n    if (from !== to) {\n        return [from, to];\n    }\n\n    if (isForward) {\n        const valueAfterSelectionStart = value.slice(from);\n        const [leadingSpaces] = valueAfterSelectionStart.match(LEADING_SPACES_REG) || [\n            '',\n        ];\n        const nearestWordEndIndex = valueAfterSelectionStart\n            .trimStart()\n            .search(SPACE_REG);\n\n        return [\n            from,\n            nearestWordEndIndex === -1\n                ? value.length\n                : from + leadingSpaces.length + nearestWordEndIndex,\n        ];\n    }\n\n    const valueBeforeSelectionEnd = value.slice(0, to);\n    const [trailingSpaces] = valueBeforeSelectionEnd.match(TRAILING_SPACES_REG) || [''];\n    const selectedWordLength = valueBeforeSelectionEnd\n        .trimEnd()\n        .split('')\n        .reverse()\n        .findIndex((char) => SPACE_REG.exec(char));\n\n    return [\n        selectedWordLength === -1 ? 0 : to - trailingSpaces.length - selectedWordLength,\n        to,\n    ];\n}\n"
  },
  {
    "path": "projects/core/src/lib/utils/index.ts",
    "content": "export * from './content-editable';\nexport * from './dom/event-listener';\nexport * from './dom/get-content-editable-selection';\nexport * from './dom/history-events';\nexport * from './dom/set-content-editable-selection';\nexport * from './dom/update-element';\nexport * from './element-states-equality';\nexport * from './get-line-selection';\nexport * from './get-not-empty-selection';\nexport * from './get-word-selection';\nexport * from './pipe';\nexport * from './transform';\n"
  },
  {
    "path": "projects/core/src/lib/utils/pipe.ts",
    "content": "import type {MaskitoPostprocessor, MaskitoPreprocessor} from '../types';\n\n/**\n * @internal\n */\nexport function maskitoPipe(\n    processors?: readonly MaskitoPreprocessor[],\n): MaskitoPreprocessor;\n\n/**\n * @internal\n */\nexport function maskitoPipe(\n    processors?: readonly MaskitoPostprocessor[],\n): MaskitoPostprocessor;\n\n/**\n * @internal\n */\nexport function maskitoPipe<State extends object, Args extends unknown[]>(\n    processors: ReadonlyArray<(state: State, ...args: Args) => Partial<State>> = [],\n): (state: State, ...args: Args) => State {\n    return (initialData: State, ...args: Args) =>\n        processors.reduce((data, fn) => ({...data, ...fn(data, ...args)}), initialData);\n}\n"
  },
  {
    "path": "projects/core/src/lib/utils/test/get-not-empty-selection.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport type {ElementState} from '../../types';\nimport {getNotEmptySelection} from '../get-not-empty-selection';\n\ndescribe('getNotEmptySelection', () => {\n    it('should return the same selection when selection positions are not equal', () => {\n        const elementStateStub: ElementState = {\n            value: 'testValue',\n            selection: [1, 3],\n        };\n\n        expect(getNotEmptySelection(elementStateStub, true)).toEqual(\n            elementStateStub.selection,\n        );\n        expect(getNotEmptySelection(elementStateStub, false)).toEqual(\n            elementStateStub.selection,\n        );\n    });\n\n    describe('backward direction', () => {\n        it('should decrease by one start position value', () => {\n            const elementStateStub: ElementState = {\n                value: 'testValue',\n                selection: [4, 4],\n            };\n\n            expect(getNotEmptySelection(elementStateStub, false)).toEqual([3, 4]);\n        });\n\n        it('should not change everything when start value is 0', () => {\n            const elementStateStub: ElementState = {\n                value: 'testValue',\n                selection: [0, 4],\n            };\n\n            expect(getNotEmptySelection(elementStateStub, false)).toEqual([0, 4]);\n        });\n\n        it('should decrease by one start value', () => {\n            const elementStateStub: ElementState = {\n                value: 'testValue',\n                selection: [1, 1],\n            };\n\n            expect(getNotEmptySelection(elementStateStub, false)).toEqual([0, 1]);\n        });\n    });\n\n    describe('forward direction', () => {\n        it('should increase by one end position, when value`s length more then end position', () => {\n            const elementStateStub: ElementState = {\n                value: 'testValue',\n                selection: [2, 2],\n            };\n\n            expect(getNotEmptySelection(elementStateStub, true)).toEqual([2, 3]);\n        });\n\n        it('should return value length as end position, when value`s length less or equal then end position', () => {\n            const elementStateStub: ElementState = {\n                value: 'sx',\n                selection: [4, 4],\n            };\n\n            expect(getNotEmptySelection(elementStateStub, true)).toEqual([2, 2]);\n        });\n\n        it('should increase by one end position, when value`s length equal end position increased by one', () => {\n            const elementStateStub: ElementState = {\n                value: 'test1',\n                selection: [4, 4],\n            };\n\n            expect(getNotEmptySelection(elementStateStub, true)).toEqual([4, 5]);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/core/src/lib/utils/test/get-word-selection.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {getWordSelection} from '../get-word-selection';\n\ndescribe('getWordSelection', () => {\n    describe('Backward', () => {\n        it('\"1 23 456|\" => select \"456\" => [5,8]', () => {\n            expect(\n                getWordSelection(\n                    {\n                        value: '1 23 456',\n                        selection: [\n                            8, // '1 23 456'.length\n                            8,\n                        ],\n                    },\n                    false,\n                ),\n            ).toEqual([\n                5, // '1 23 '.length\n                8,\n            ]);\n        });\n\n        it('\"1 23 45|6\" => select \"45\" => [5,7]', () => {\n            expect(\n                getWordSelection(\n                    {\n                        value: '1 23 456',\n                        selection: [\n                            7, // '1 23 45'.length\n                            7,\n                        ],\n                    },\n                    false,\n                ),\n            ).toEqual([\n                5, // '1 23 '.length\n                7,\n            ]);\n        });\n\n        it('\"1 23 |456\" => select \"23 \" => [2,5]', () => {\n            expect(\n                getWordSelection(\n                    {\n                        value: '1 23 456',\n                        selection: [\n                            5, // '1 23 '.length\n                            5,\n                        ],\n                    },\n                    false,\n                ),\n            ).toEqual([\n                2, // '1 '.length\n                5,\n            ]);\n        });\n\n        it('\"1 23| 456\" => select \"23\" => [2,4]', () => {\n            expect(\n                getWordSelection(\n                    {\n                        value: '1 23 456',\n                        selection: [\n                            4, // '1 23'.length\n                            4,\n                        ],\n                    },\n                    false,\n                ),\n            ).toEqual([\n                2, // '1 '.length\n                4,\n            ]);\n        });\n\n        it('\"|1 23 456\" => select \"\" => [0,0]', () => {\n            expect(\n                getWordSelection({value: '1 23 456', selection: [0, 0]}, false),\n            ).toEqual([0, 0]);\n        });\n\n        it('\"1 2  |3\" (two spaces after 2) => select \"2  \" => [2,5]', () => {\n            expect(\n                getWordSelection(\n                    {\n                        value: '1 2  3',\n                        selection: [\n                            5, // space before 3\n                            5,\n                        ],\n                    },\n                    false,\n                ),\n            ).toEqual([2, 5]);\n        });\n    });\n\n    describe('Forward', () => {\n        it('\"1 23 456|\" => select \"\" => [8,8]', () => {\n            expect(\n                getWordSelection(\n                    {\n                        value: '1 23 456',\n                        selection: [\n                            8, // '1 23 456'.length\n                            8,\n                        ],\n                    },\n                    true,\n                ),\n            ).toEqual([8, 8]);\n        });\n\n        it('\"1 23 45|6\" => select \"6\" => [7,8]', () => {\n            expect(\n                getWordSelection(\n                    {\n                        value: '1 23 456',\n                        selection: [\n                            7, // '1 23 45'.length\n                            7,\n                        ],\n                    },\n                    true,\n                ),\n            ).toEqual([7, 8]);\n        });\n\n        it('\"1 23 |456\" => select \"456\" => [5,8]', () => {\n            expect(\n                getWordSelection(\n                    {\n                        value: '1 23 456',\n                        selection: [\n                            5, // '1 23 '.length\n                            5,\n                        ],\n                    },\n                    true,\n                ),\n            ).toEqual([5, 8]);\n        });\n\n        it('\"1 23| 456\" => select \" 456\" => [4,8]', () => {\n            expect(\n                getWordSelection(\n                    {\n                        value: '1 23 456',\n                        selection: [\n                            4, // '1 23'.length\n                            4,\n                        ],\n                    },\n                    true,\n                ),\n            ).toEqual([4, 8]);\n        });\n\n        it('\"1 |23 456\" => select \"23\" => [2,4]', () => {\n            expect(\n                getWordSelection({value: '1 23 456', selection: [2, 2]}, true),\n            ).toEqual([2, 4]);\n        });\n\n        it('\"1| 23 456\" => select \" 23\" => [1,4]', () => {\n            expect(\n                getWordSelection({value: '1 23 456', selection: [1, 1]}, true),\n            ).toEqual([1, 4]);\n        });\n\n        it('\"1 2|  3\" (two spaces after 2) => select \"  3\" => [3,6]', () => {\n            expect(\n                getWordSelection(\n                    {\n                        value: '1 2  3',\n                        selection: [3, 3],\n                    },\n                    true,\n                ),\n            ).toEqual([3, 6]);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/core/src/lib/utils/test/pipe.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport type {ElementState, MaskitoPostprocessor, MaskitoPreprocessor} from '../../types';\nimport {maskitoPipe} from '../pipe';\n\ndescribe('maskitoPipe', () => {\n    describe('Preprocessor', () => {\n        const preprocessorData: Parameters<MaskitoPreprocessor>[0] = {\n            elementState: {value: '2', selection: [2, 2]},\n            data: '0',\n        };\n\n        const add0ToValue: MaskitoPreprocessor = ({elementState}) => ({\n            elementState: {\n                ...elementState,\n                value: `${elementState.value}0`,\n            },\n        });\n        const add1ToValue: MaskitoPreprocessor = ({elementState}) => ({\n            elementState: {\n                ...elementState,\n                value: `${elementState.value}1`,\n            },\n        });\n        const add5ToData: MaskitoPreprocessor = ({elementState, data}) => ({\n            elementState,\n            data: `${data}5`,\n        });\n        const add3ToData: MaskitoPreprocessor = ({elementState, data}) => ({\n            elementState,\n            data: `${data}3`,\n        });\n\n        it('take the last valid `data` if the previous processor did not modify it', () => {\n            expect(\n                maskitoPipe([add3ToData, add0ToValue, add5ToData])(\n                    preprocessorData,\n                    'insert',\n                ),\n            ).toEqual({\n                elementState: {value: '20', selection: [2, 2]},\n                data: '035',\n            });\n        });\n\n        describe('Order matters', () => {\n            it('for `elementState`', () => {\n                expect(\n                    maskitoPipe([add0ToValue, add1ToValue])(preprocessorData, 'insert'),\n                ).toEqual({\n                    elementState: {value: '201', selection: [2, 2]},\n                    data: '0',\n                });\n\n                expect(\n                    maskitoPipe([add1ToValue, add0ToValue])(preprocessorData, 'insert'),\n                ).toEqual({\n                    elementState: {value: '210', selection: [2, 2]},\n                    data: '0',\n                });\n            });\n\n            it('for `data`', () => {\n                expect(\n                    maskitoPipe([add3ToData, add5ToData])(preprocessorData, 'insert'),\n                ).toEqual({\n                    elementState: {value: '2', selection: [2, 2]},\n                    data: '035',\n                });\n\n                expect(\n                    maskitoPipe([add5ToData, add3ToData])(preprocessorData, 'insert'),\n                ).toEqual({\n                    elementState: {value: '2', selection: [2, 2]},\n                    data: '053',\n                });\n            });\n\n            it('for `elementState` & `data` in one pipe', () => {\n                expect(\n                    maskitoPipe([add5ToData, add0ToValue, add3ToData, add1ToValue])(\n                        preprocessorData,\n                        'insert',\n                    ),\n                ).toEqual({\n                    elementState: {value: '201', selection: [2, 2]},\n                    data: '053',\n                });\n            });\n        });\n    });\n\n    describe('Postprocessor', () => {\n        const initialElementState: Parameters<MaskitoPostprocessor>[1] = {\n            value: '',\n            selection: [0, 0],\n        };\n        const postprocessorData: Parameters<MaskitoPostprocessor>[0] = {\n            value: '0',\n            selection: [5, 5],\n        };\n\n        const add3: MaskitoPostprocessor = ({value, selection}) => ({\n            selection,\n            value: `${value}3`,\n        });\n        const add5: MaskitoPostprocessor = ({value, selection}) => ({\n            selection,\n            value: `${value}5`,\n        });\n        const doubleCaretIndex: MaskitoPostprocessor = ({value, selection}) => ({\n            value,\n            selection: [selection[0] * 2, selection[1] * 2],\n        });\n        const shiftCaretIndexBy5: MaskitoPostprocessor = ({value, selection}) => ({\n            value,\n            selection: [selection[0] + 5, selection[1] + 5],\n        });\n\n        describe('Order matters', () => {\n            it('for `value`', () => {\n                expect(\n                    maskitoPipe([add3, add5])(postprocessorData, initialElementState),\n                ).toEqual({\n                    value: '035',\n                    selection: [5, 5],\n                });\n\n                expect(\n                    maskitoPipe([add5, add3])(postprocessorData, initialElementState),\n                ).toEqual({\n                    value: '053',\n                    selection: [5, 5],\n                });\n            });\n\n            it('for `selection`', () => {\n                expect(\n                    maskitoPipe([doubleCaretIndex, shiftCaretIndexBy5])(\n                        postprocessorData,\n                        initialElementState,\n                    ),\n                ).toEqual({\n                    value: '0',\n                    selection: [15, 15],\n                });\n\n                expect(\n                    maskitoPipe([shiftCaretIndexBy5, doubleCaretIndex])(\n                        postprocessorData,\n                        initialElementState,\n                    ),\n                ).toEqual({\n                    value: '0',\n                    selection: [20, 20],\n                });\n            });\n\n            it('for `value` & `selection` in one pipe', () => {\n                expect(\n                    maskitoPipe([add5, doubleCaretIndex, add3, shiftCaretIndexBy5])(\n                        postprocessorData,\n                        initialElementState,\n                    ),\n                ).toEqual({\n                    value: '053',\n                    selection: [15, 15],\n                });\n            });\n        });\n    });\n\n    describe('Readonly arguments are passed to all processors', () => {\n        const elementState: ElementState = {value: '', selection: [0, 0]};\n\n        it('preprocessor', () => {\n            const checkActionType: MaskitoPreprocessor = (data, actionType) => {\n                expect(actionType).toBe('deleteBackward');\n\n                return data;\n            };\n\n            maskitoPipe([checkActionType, checkActionType, checkActionType])(\n                {elementState, data: ''},\n                'deleteBackward',\n            );\n        });\n\n        it('postprocessor', () => {\n            const initialElementState: ElementState = {\n                value: '27',\n                selection: [2, 7],\n            };\n\n            const checkActionType: MaskitoPostprocessor = (data, actionType) => {\n                expect(actionType).toEqual(initialElementState);\n\n                return data;\n            };\n\n            maskitoPipe([checkActionType, checkActionType, checkActionType])(\n                elementState,\n                initialElementState,\n            );\n        });\n    });\n});\n"
  },
  {
    "path": "projects/core/src/lib/utils/test/transform.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport type {MaskitoOptions} from '../../types';\nimport {maskitoTransform} from '../transform';\n\ndescribe('maskitoTransform', () => {\n    const numberOptions: MaskitoOptions = {\n        mask: /^\\d+(,\\d*)?$/,\n        preprocessors: [\n            ({elementState, data}) => {\n                const {value, selection} = elementState;\n\n                return {\n                    elementState: {\n                        selection,\n                        value: value.replace('.', ','),\n                    },\n                    data: data.replace('.', ','),\n                };\n            },\n        ],\n        postprocessors: [\n            ({value, selection}) => {\n                const newValue = value.replace(/^0+/, '0');\n                const deletedChars = value.length - newValue.length;\n                const [from, to] = selection;\n\n                return {\n                    value: newValue,\n                    selection: [from - deletedChars, to - deletedChars],\n                };\n            },\n        ],\n    };\n    const usPhoneOptions: MaskitoOptions = {\n        mask: [\n            '+',\n            '1',\n            ' ',\n            '(',\n            /\\d/,\n            /\\d/,\n            /\\d/,\n            ')',\n            ' ',\n            /\\d/,\n            /\\d/,\n            /\\d/,\n            '-',\n            /\\d/,\n            /\\d/,\n            /\\d/,\n            /\\d/,\n        ],\n    };\n\n    describe('Basic API', () => {\n        it('returns string if the first argument is a string', () => {\n            expect(typeof maskitoTransform('100', numberOptions)).toBe('string');\n        });\n\n        it('returns ElementState if the first argument is a ElementState', () => {\n            const res = maskitoTransform({value: '', selection: [0, 0]}, numberOptions);\n\n            expect(res).toBeTruthy();\n            expect(typeof res).toBe('object');\n        });\n\n        it('formats value using preprocessor', () => {\n            expect(maskitoTransform('100.42', numberOptions)).toBe('100,42');\n            expect(\n                maskitoTransform(\n                    {\n                        value: '100.42',\n                        selection: [2, 2],\n                    },\n                    numberOptions,\n                ),\n            ).toEqual({value: '100,42', selection: [2, 2]});\n        });\n\n        it('formats value using postprocessor', () => {\n            expect(maskitoTransform('0000,1234', numberOptions)).toBe('0,1234');\n            expect(\n                maskitoTransform(\n                    {\n                        value: '0000,1234',\n                        selection: [6, 6],\n                    },\n                    numberOptions,\n                ),\n            ).toEqual({value: '0,1234', selection: [3, 3]});\n        });\n\n        it('drops invalid characters (mask expression works)', () => {\n            expect(maskitoTransform('42Taiga UI42', numberOptions)).toBe('4242');\n            expect(\n                maskitoTransform(\n                    {\n                        value: '42Taiga UI42',\n                        selection: [11, 11],\n                    },\n                    numberOptions,\n                ),\n            ).toEqual({value: '4242', selection: [3, 3]});\n        });\n    });\n\n    describe('Drop / Browser autofill cases', () => {\n        it('`US` Phone mask | Drops \"+1(21\"', () => {\n            expect(maskitoTransform('+1(21', usPhoneOptions)).toBe('+1 (21');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/core/src/lib/utils/transform.ts",
    "content": "import {MaskModel} from '../classes';\nimport {MASKITO_DEFAULT_OPTIONS} from '../constants';\nimport type {ElementState, MaskitoOptions} from '../types';\nimport {maskitoPipe} from './pipe';\n\nexport function maskitoTransform(value: string, maskitoOptions: MaskitoOptions): string;\nexport function maskitoTransform(\n    state: ElementState,\n    maskitoOptions: MaskitoOptions,\n): ElementState;\n\nexport function maskitoTransform(\n    valueOrState: ElementState | string,\n    maskitoOptions: MaskitoOptions,\n): ElementState | string {\n    const options: Required<MaskitoOptions> = {\n        ...MASKITO_DEFAULT_OPTIONS,\n        ...maskitoOptions,\n    };\n    const preprocessor = maskitoPipe(options.preprocessors);\n    const postprocessor = maskitoPipe(options.postprocessors);\n    const initialElementState =\n        typeof valueOrState === 'string'\n            ? {value: valueOrState, selection: [0, 0] as const}\n            : valueOrState;\n\n    const {elementState} = preprocessor(\n        {elementState: initialElementState, data: ''},\n        'validation',\n    );\n    const maskModel = new MaskModel(elementState, options);\n    const {value, selection} = postprocessor(maskModel, initialElementState);\n\n    return typeof valueOrState === 'string' ? value : {value, selection};\n}\n"
  },
  {
    "path": "projects/demo/.gitignore",
    "content": "# compiled output\n/dist\n/tmp\n/out-tsc\n# Only exists if Bazel was run\n/bazel-out\n\n# dependencies\n/node_modules\n\n# profiling files\nchrome-profiler-events.json\nspeed-measure-plugin.json\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "projects/demo/esbuild-plugins/maskito-as-taiga-ui-dep.plugin.js",
    "content": "const {resolve, join} = require('node:path');\nconst {existsSync} = require('node:fs');\n\n/**\n * Maskito repo uses Taiga UI to build demo application.\n * Taiga UI uses Maskito packages as a dependency.\n * Maskito <=> Taiga UI circular dependency.\n * ESBuild is capable to use path aliases from tsconfig, to resolve local imports\n * (e.g. source code of @maskito/kit imports @maskito/core utility).\n * However, ESBuild ignores tsconfig paths when resolving packages inside node_modules.\n */\nmodule.exports = {\n    name: 'maskito-as-taiga-ui-dep',\n    setup(build) {\n        build.onResolve({filter: /^@maskito/}, (args) => {\n            if (!args.importer.includes(join('node_modules', '@taiga-ui'))) {\n                // Ignore for local path aliases (ESBuild handles them properly)\n                return;\n            }\n\n            const library = args.path // e.g., '@maskito/kit'\n                .split('/')[1];\n            const entryPoint = resolve(\n                __dirname,\n                '../../../projects',\n                library,\n                'src/index.ts',\n            );\n\n            return existsSync(entryPoint)\n                ? {\n                      path: entryPoint,\n                  }\n                : null;\n        });\n    },\n};\n"
  },
  {
    "path": "projects/demo/esbuild-plugins/vue-esm.plugin.js",
    "content": "const {resolve} = require('node:path');\n\n/**\n * Otherwise, demo application logs warning:\n * ```\n * Component provided template option but runtime compilation is not supported in this build of Vue.\n * Configure your bundler to alias \"vue\" to \"vue/dist/vue.esm-bundler.js\"`.\n * ```\n * Equivalent to webpack's alias: { vue$: 'vue/dist/vue.esm-bundler.js' }\n */\nmodule.exports = {\n    name: 'vue-esm',\n    setup(build) {\n        build.onResolve({filter: /^vue$/}, () => ({\n            path: resolve(\n                build.initialOptions.absWorkingDir,\n                './node_modules/vue/dist/vue.esm-bundler.js',\n            ),\n        }));\n    },\n};\n"
  },
  {
    "path": "projects/demo/jest.config.ts",
    "content": "export default {\n    displayName: 'demo',\n    preset: '../../jest.preset.js',\n    setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],\n    coverageDirectory: '../../coverage/demo',\n    transformIgnorePatterns: ['node_modules/(?!.*.mjs$)'],\n    snapshotSerializers: [\n        'jest-preset-angular/build/serializers/no-ng-attributes',\n        'jest-preset-angular/build/serializers/ng-snapshot',\n        'jest-preset-angular/build/serializers/html-comment',\n    ],\n};\n"
  },
  {
    "path": "projects/demo/package.json",
    "content": "{\n    \"name\": \"@maskito/demo\",\n    \"private\": true,\n    \"devDependencies\": {\n        \"@angular/animations\": \"19.2.20\",\n        \"@angular/cdk\": \"19.2.19\",\n        \"@angular/common\": \"19.2.20\",\n        \"@angular/core\": \"19.2.20\",\n        \"@angular/forms\": \"19.2.20\",\n        \"@angular/platform-browser\": \"19.2.20\",\n        \"@angular/platform-browser-dynamic\": \"19.2.20\",\n        \"@angular/platform-server\": \"19.2.20\",\n        \"@angular/router\": \"19.2.20\",\n        \"@angular/ssr\": \"19.2.22\",\n        \"@maskito/angular\": \"*\",\n        \"@maskito/core\": \"*\",\n        \"@maskito/kit\": \"*\",\n        \"@ng-web-apis/common\": \"4.14.0\",\n        \"@ng-web-apis/universal\": \"4.14.0\",\n        \"@stackblitz/sdk\": \"1.11.0\",\n        \"@taiga-ui/addon-doc\": \"4.76.0\",\n        \"@taiga-ui/addon-mobile\": \"4.76.0\",\n        \"@taiga-ui/cdk\": \"4.76.0\",\n        \"@taiga-ui/core\": \"4.76.0\",\n        \"@taiga-ui/icons\": \"4.76.0\",\n        \"@taiga-ui/kit\": \"4.76.0\",\n        \"@taiga-ui/layout\": \"4.76.0\",\n        \"@taiga-ui/legacy\": \"4.76.0\",\n        \"@taiga-ui/styles\": \"4.76.0\",\n        \"@taiga-ui/testing\": \"4.76.0\",\n        \"browser-sync\": \"3.0.4\",\n        \"ngx-highlightjs\": \"10.0.0\",\n        \"react\": \"19.2.5\",\n        \"react-dom\": \"19.2.5\",\n        \"rxjs\": \"7.8.2\",\n        \"tslib\": \"2.8.1\",\n        \"vue\": \"3.5.32\"\n    }\n}\n"
  },
  {
    "path": "projects/demo/project.json",
    "content": "{\n    \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n    \"name\": \"demo\",\n    \"prefix\": \"app\",\n    \"projectType\": \"application\",\n    \"sourceRoot\": \"projects/demo/src\",\n    \"targets\": {\n        \"build\": {\n            \"configurations\": {\n                \"development\": {\n                    \"extractLicenses\": false,\n                    \"optimization\": false,\n                    \"outputMode\": \"static\",\n                    \"server\": false,\n                    \"sourceMap\": true,\n                    \"ssr\": false\n                },\n                \"production\": {\n                    \"outputHashing\": \"all\"\n                },\n                \"typecheck\": {\n                    \"tsConfig\": \"{projectRoot}/tsconfig.typecheck.json\"\n                }\n            },\n            \"defaultConfiguration\": \"production\",\n            \"executor\": \"@nx/angular:application\",\n            \"options\": {\n                \"allowedCommonJsDependencies\": [\n                    \"react\",\n                    \"react-dom/client\",\n                    \"react/jsx-runtime\",\n                    \"@vue/compiler-dom\",\n                    \"@vue/runtime-dom\",\n                    \"@vue/shared\"\n                ],\n                \"assets\": [\n                    {\n                        \"glob\": \"**/*\",\n                        \"input\": \"{projectRoot}/src/assets/\",\n                        \"output\": \"./assets/\"\n                    },\n                    {\n                        \"glob\": \"**/*\",\n                        \"input\": \"node_modules/@taiga-ui/icons/src\",\n                        \"output\": \"assets/taiga-ui/icons\"\n                    }\n                ],\n                \"baseHref\": \"/\",\n                \"browser\": \"{projectRoot}/src/main.ts\",\n                \"externalDependencies\": [\"react-hook-form\"],\n                \"index\": \"{projectRoot}/src/index.html\",\n                \"loader\": {\n                    \".css\": \"text\",\n                    \".html\": \"text\",\n                    \".md\": \"text\"\n                },\n                \"outputMode\": \"server\",\n                \"outputPath\": \"dist/{projectName}\",\n                \"plugins\": [\"{projectRoot}/esbuild-plugins/maskito-as-taiga-ui-dep.plugin.js\", \"{projectRoot}/esbuild-plugins/vue-esm.plugin.js\"],\n                \"polyfills\": [\"zone.js\"],\n                \"server\": \"{projectRoot}/src/main.server.ts\",\n                \"ssr\": {\n                    \"entry\": \"{projectRoot}/src/server.ts\"\n                },\n                \"styles\": [\n                    \"node_modules/@taiga-ui/core/styles/taiga-ui-theme.less\",\n                    \"node_modules/@taiga-ui/core/styles/taiga-ui-fonts.less\",\n                    \"node_modules/@taiga-ui/styles/taiga-ui-global.less\",\n                    \"{projectRoot}/src/styles.less\"\n                ],\n                \"tsConfig\": \"{projectRoot}/tsconfig.app.json\"\n            },\n            \"outputs\": [\"{options.outputPath}\"]\n        },\n        \"build-gh-pages\": {\n            \"executor\": \"nx:run-commands\",\n            \"options\": {\n                \"commands\": [\n                    \"echo 'Github pages require special 404.html'\",\n                    \"echo 'Read more: https://angular.io/guide/deployment#deploy-to-github-pages'\",\n                    \"echo ------\",\n                    \"nx build {projectName} -c production\",\n                    \"cp dist/{projectName}/browser/index.html dist/{projectName}/browser/404.html\"\n                ],\n                \"parallel\": false\n            }\n        },\n        \"serve\": {\n            \"configurations\": {\n                \"development\": {\n                    \"buildTarget\": \"{projectName}:build:development\"\n                },\n                \"production\": {\n                    \"buildTarget\": \"{projectName}:build:production\"\n                }\n            },\n            \"continuous\": true,\n            \"defaultConfiguration\": \"development\",\n            \"executor\": \"@nx/angular:dev-server\",\n            \"options\": {\n                \"port\": 3333,\n                \"prebundle\": {\n                    \"exclude\": [\n                        \"@taiga-ui/addon-doc\",\n                        \"@taiga-ui/addon-mobile\",\n                        \"@taiga-ui/cdk\",\n                        \"@taiga-ui/core\",\n                        \"@taiga-ui/icons\",\n                        \"@taiga-ui/kit\",\n                        \"@taiga-ui/layout\",\n                        \"@taiga-ui/legacy\",\n                        \"@taiga-ui/styles\"\n                    ]\n                }\n            }\n        },\n        \"serve-ip\": {\n            \"executor\": \"nx:run-commands\",\n            \"options\": {\n                \"command\": \"nx serve {projectName} --open --host 0.0.0.0 --disable-host-check\"\n            }\n        },\n        \"serve-ssl\": {\n            \"executor\": \"nx:run-commands\",\n            \"options\": {\n                \"commands\": [\n                    \"echo \\\"mkcert is a simple tool for making locally-trusted development certificates\\\"\",\n                    \"echo \\\"Read about installation and more: https://github.com/FiloSottile/mkcert\\\"\",\n                    \"echo ------\",\n                    \"mkcert -install\",\n                    \"mkdir -p .ssl\",\n                    \"mkcert -key-file .ssl/localhost-key.pem -cert-file .ssl/localhost.pem localhost 127.0.0.1 ::1\",\n                    \"nx serve --ssl\"\n                ],\n                \"parallel\": false\n            }\n        },\n        \"serve-static\": {\n            \"continuous\": true,\n            \"executor\": \"@nx/web:file-server\",\n            \"options\": {\n                \"buildTarget\": \"{projectName}:build\",\n                \"port\": 3333,\n                \"spa\": true,\n                \"staticFilePath\": \"dist/{projectName}/browser\"\n            }\n        },\n        \"test\": {\n            \"executor\": \"@nx/jest:jest\",\n            \"options\": {\n                \"jestConfig\": \"{projectRoot}/jest.config.ts\",\n                \"tsConfig\": \"tsconfig.spec.json\"\n            },\n            \"outputs\": [\"{workspaceRoot}/coverage/{projectName}\"]\n        }\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/app/app.component.html",
    "content": "<tui-doc-main>\n    <ng-container ngProjectAs=\"tuiDocHeader\">\n        <a\n            appearance=\"icon\"\n            href=\"https://github.com/taiga-family/maskito\"\n            iconStart=\"assets/icons/github.svg\"\n            rel=\"noreferrer\"\n            target=\"_blank\"\n            title=\"Maskito source code on GitHub\"\n            tuiLink\n            class=\"link\"\n        ></a>\n\n        <a\n            appearance=\"icon\"\n            href=\"https://t.me/taiga_ui/10600\"\n            iconStart=\"assets/icons/telegram.svg\"\n            rel=\"noreferrer\"\n            target=\"_blank\"\n            title=\"Get help on Telegram\"\n            tuiLink\n            class=\"link\"\n        ></a>\n\n        <a\n            appearance=\"icon\"\n            iconStart=\"assets/icons/stackblitz.svg\"\n            title=\"Maskito StackBlitz Starter\"\n            tuiLink\n            class=\"link\"\n            [routerLink]=\"stackblitzStarterPath\"\n        ></a>\n    </ng-container>\n</tui-doc-main>\n"
  },
  {
    "path": "projects/demo/src/app/app.component.spec.ts",
    "content": "import {APP_BASE_HREF} from '@angular/common';\nimport {TestBed} from '@angular/core/testing';\nimport {RouterTestingHarness} from '@angular/router/testing';\nimport {DemoPath} from '@demo/constants';\nimport {beforeEach, describe, expect, it} from '@jest/globals';\n\nimport {App} from './app.component';\nimport {APP_CONFIG} from './app.config';\n\ndescribe('Ensure unit tests work for demo application', () => {\n    beforeEach(() => {\n        TestBed.configureTestingModule({\n            imports: [App],\n            providers: [...APP_CONFIG.providers, {provide: APP_BASE_HREF, useValue: '/'}],\n        });\n    });\n\n    it('appComponent compiles properly', () => {\n        const fixture = TestBed.createComponent(App);\n\n        fixture.detectChanges();\n\n        expect(fixture.nativeElement.textContent).toContain('Getting started');\n        expect(fixture.nativeElement.textContent).toContain('Core concepts');\n    });\n\n    it('router works', async () => {\n        const router = await RouterTestingHarness.create();\n\n        await router.navigateByUrl(DemoPath.CoreConceptsOverview);\n\n        expect(router.routeNativeElement?.textContent).toContain(\n            'The main entity of Maskito core library is Maskito class which accepts 2 arguments in constructor',\n        );\n    });\n});\n"
  },
  {
    "path": "projects/demo/src/app/app.component.ts",
    "content": "import {ViewportScroller} from '@angular/common';\nimport {ChangeDetectionStrategy, Component, inject} from '@angular/core';\nimport {takeUntilDestroyed} from '@angular/core/rxjs-interop';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath} from '@demo/constants';\nimport {ResizeObserverService} from '@ng-web-apis/resize-observer';\nimport {TUI_DOC_PAGE_LOADED, TuiDocMain} from '@taiga-ui/addon-doc';\nimport {tuiInjectElement} from '@taiga-ui/cdk';\nimport {TuiLink} from '@taiga-ui/core';\nimport {debounceTime, map, startWith} from 'rxjs';\n\n@Component({\n    selector: 'app',\n    imports: [RouterLink, TuiDocMain, TuiLink],\n    templateUrl: './app.component.html',\n    styleUrl: './app.style.less',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n    providers: [\n        ResizeObserverService,\n        {\n            provide: TUI_DOC_PAGE_LOADED,\n            useFactory: () => {\n                const host = tuiInjectElement();\n\n                return inject(ResizeObserverService).pipe(\n                    startWith(null),\n                    debounceTime(0), // Synchronous scrollIntoView (after click) does not work https://stackoverflow.com/a/56971002\n                    map(() => {\n                        const exampleElements = Array.from(\n                            host.querySelectorAll('tui-doc-example'),\n                        );\n                        const codeElements = Array.from(\n                            host.querySelectorAll('tui-doc-code'),\n                        );\n\n                        return (\n                            exampleElements.every((el) =>\n                                el.querySelector('.t-example'),\n                            ) && codeElements.every((el) => el.querySelector('.t-code'))\n                        );\n                    }),\n                    takeUntilDestroyed(),\n                );\n            },\n        },\n    ],\n})\nexport class App {\n    protected readonly stackblitzStarterPath = `/${DemoPath.Stackblitz}`;\n\n    constructor() {\n        inject(ViewportScroller).setOffset([0, 64]);\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/app/app.config.ts",
    "content": "import {isPlatformBrowser, LocationStrategy, PathLocationStrategy} from '@angular/common';\nimport {provideHttpClient} from '@angular/common/http';\nimport {type ApplicationConfig, inject, PLATFORM_ID} from '@angular/core';\nimport {provideAnimations} from '@angular/platform-browser/animations';\nimport {provideRouter, withInMemoryScrolling} from '@angular/router';\nimport {DocExamplePrimaryTab} from '@demo/constants';\nimport {\n    TUI_DOC_CODE_EDITOR,\n    TUI_DOC_DEFAULT_TABS,\n    TUI_DOC_EXAMPLE_CONTENT_PROCESSOR,\n    TUI_DOC_LOGO,\n    TUI_DOC_PAGES,\n    TUI_DOC_SOURCE_CODE,\n    TUI_DOC_TITLE,\n    TUI_DOC_TYPE_REFERENCE_HANDLER,\n    tuiDocExampleOptionsProvider,\n    type TuiDocSourceCodePathOptions,\n} from '@taiga-ui/addon-doc';\nimport {NG_EVENT_PLUGINS} from '@taiga-ui/event-plugins';\nimport type {PolymorpheusContent} from '@taiga-ui/polymorpheus';\nimport {HIGHLIGHT_OPTIONS} from 'ngx-highlightjs';\n\nimport {DEMO_PAGES} from '../pages/pages';\nimport {StackblitzService} from '../pages/stackblitz';\nimport {ROUTES} from './app.routes';\nimport {\n    ANGULAR_LOGO,\n    JAVASCRIPT_LOGO,\n    REACT_LOGO,\n} from './modules/example-primary-tabs-icons';\nimport {VUE_LOGO} from './modules/example-primary-tabs-icons/vue-logo.component';\nimport {LOGO_CONTENT} from './modules/logo/logo.component';\nimport {addDefaultTabsProcessor} from './utils';\n\nexport const APP_CONFIG: ApplicationConfig = {\n    providers: [\n        provideAnimations(),\n        provideRouter(\n            ROUTES,\n            withInMemoryScrolling({\n                scrollPositionRestoration: 'enabled',\n                anchorScrolling: 'enabled',\n            }),\n        ),\n        NG_EVENT_PLUGINS,\n        provideHttpClient(),\n        {\n            provide: LocationStrategy,\n            useClass: PathLocationStrategy,\n        },\n        {\n            provide: TUI_DOC_TITLE,\n            useValue: 'Maskito | ',\n        },\n        {\n            provide: TUI_DOC_LOGO,\n            useValue: LOGO_CONTENT,\n        },\n        {\n            provide: TUI_DOC_DEFAULT_TABS,\n            useValue: ['Description and examples', 'API'],\n        },\n        {\n            provide: TUI_DOC_PAGES,\n            useValue: DEMO_PAGES,\n        },\n        {\n            provide: TUI_DOC_SOURCE_CODE,\n            useValue: (context: TuiDocSourceCodePathOptions) => {\n                const link = 'https://github.com/taiga-family/maskito/tree/main/projects';\n\n                if (context.path) {\n                    return `${link}/${context.path}`;\n                }\n\n                if (context.package.toLowerCase() !== 'kit') {\n                    return null;\n                }\n\n                return `${link}/${context.package.toLowerCase()}/src/lib/masks/${`${context.header.slice(0, 1).toLowerCase()}${context.header.slice(1)}`.replaceAll(\n                    /[A-Z]/g,\n                    (m) => `-${m.toLowerCase()}`,\n                )}`;\n            },\n        },\n        {\n            provide: TUI_DOC_CODE_EDITOR,\n            useClass: StackblitzService,\n        },\n        {\n            provide: TUI_DOC_EXAMPLE_CONTENT_PROCESSOR,\n            useValue: addDefaultTabsProcessor,\n        },\n        tuiDocExampleOptionsProvider({\n            codeEditorVisibilityHandler: (files) => {\n                const fileNames = Object.keys(files);\n\n                return (\n                    fileNames.includes(DocExamplePrimaryTab.MaskitoOptions) &&\n                    fileNames.includes(DocExamplePrimaryTab.JavaScript)\n                );\n            },\n            tabTitles: new Map<string, PolymorpheusContent>([\n                [DocExamplePrimaryTab.Angular, ANGULAR_LOGO],\n                [DocExamplePrimaryTab.JavaScript, JAVASCRIPT_LOGO],\n                [DocExamplePrimaryTab.React, REACT_LOGO],\n                [DocExamplePrimaryTab.Vue, VUE_LOGO],\n            ]),\n        }),\n        {\n            provide: HIGHLIGHT_OPTIONS,\n            useFactory: () => {\n                const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n                return {\n                    coreLibraryLoader: async () => import('highlight.js/lib/core'),\n                    lineNumbersLoader: async () =>\n                        // SSR ReferenceError: window is not defined\n                        isBrowser\n                            ? import('ngx-highlightjs/line-numbers')\n                            : Promise.resolve(),\n                    languages: {\n                        typescript: async () =>\n                            import('highlight.js/lib/languages/typescript'),\n                        less: async () => import('highlight.js/lib/languages/less'),\n                        xml: async () => import('highlight.js/lib/languages/xml'),\n                    },\n                };\n            },\n        },\n        {\n            provide: TUI_DOC_TYPE_REFERENCE_HANDLER,\n            useValue: (type: string) => {\n                if (type.toLowerCase().startsWith('maskito')) {\n                    return `https://github.com/search?q=%2F%28enum%7Ctype%7Cinterface%7Cclass%7Cfunction%7Cconst%29+${type}%28%3C%7C%5Cs%29%2F+language%3ATypeScript+repo%3Ataiga-family%2Fmaskito+&type=code`;\n                }\n\n                switch (type) {\n                    case 'Date':\n                        return 'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date';\n                    case 'MetadataJson':\n                        return 'https://github.com/catamphetamine/libphonenumber-js?tab=readme-ov-file#min-vs-max-vs-mobile-vs-core';\n                    default:\n                        return null;\n                }\n            },\n        },\n    ],\n};\n"
  },
  {
    "path": "projects/demo/src/app/app.routes.ts",
    "content": "import type {Routes} from '@angular/router';\nimport {DemoPath} from '@demo/constants';\nimport {tuiProvideRoutePageTab} from '@taiga-ui/addon-doc';\n\n/* eslint-disable @typescript-eslint/promise-function-async */\n\nexport const ROUTES: Routes = [\n    // Getting started\n    {\n        path: '',\n        loadComponent: () =>\n            import('../pages/documentation/what-is-maskito/what-is-maskito.component'),\n        title: 'What is Maskito?',\n    },\n    {\n        path: DemoPath.WhatIsMaskito,\n        loadComponent: () =>\n            import('../pages/documentation/what-is-maskito/what-is-maskito.component'),\n        title: 'What is Maskito?',\n    },\n    {\n        path: DemoPath.MaskitoLibraries,\n        loadComponent: () =>\n            import('../pages/documentation/maskito-libraries/maskito-libraries.component'),\n        title: 'Maskito libraries',\n    },\n    // Core concepts\n    {\n        path: DemoPath.CoreConceptsOverview,\n        loadComponent: () =>\n            import('../pages/documentation/core-concepts-overview/core-concepts-overview.component'),\n        title: 'Core concepts',\n    },\n    {\n        path: DemoPath.MaskExpression,\n        loadComponent: () =>\n            import('../pages/documentation/mask-expression/mask-expression.component'),\n        title: 'Mask expression',\n    },\n    {\n        path: DemoPath.ElementState,\n        loadComponent: () =>\n            import('../pages/documentation/element-state/element-state.component'),\n        title: 'Element state',\n    },\n    {\n        path: DemoPath.Processors,\n        loadComponent: () =>\n            import('../pages/documentation/processors/processors.component'),\n        title: 'Processors',\n    },\n    {\n        path: DemoPath.Plugins,\n        loadComponent: () => import('../pages/documentation/plugins/plugins.component'),\n        title: 'Plugins',\n    },\n    {\n        path: DemoPath.OverwriteMode,\n        loadComponent: () =>\n            import('../pages/documentation/overwrite-mode/overwrite-mode.component'),\n        title: 'Overwrite mode',\n    },\n    {\n        path: DemoPath.Transformer,\n        loadComponent: () =>\n            import('../pages/documentation/transformer/transformer.component'),\n        title: 'Transformer',\n    },\n    // Frameworks\n    {\n        path: DemoPath.Angular,\n        loadComponent: () => import('../pages/frameworks/angular/angular-doc.component'),\n        title: 'Angular',\n    },\n    {\n        path: DemoPath.React,\n        loadComponent: () => import('../pages/frameworks/react/react-doc.component'),\n        title: 'React',\n    },\n    {\n        path: DemoPath.Vue,\n        loadComponent: () => import('../pages/frameworks/vue/vue-doc.component'),\n        title: 'Vue',\n    },\n    // Kit\n    {\n        path: DemoPath.Number,\n        loadComponent: () => import('../pages/kit/number/number-mask-doc.component'),\n        title: 'Number',\n    },\n    {\n        path: DemoPath.Time,\n        loadComponent: () => import('../pages/kit/time/time-mask-doc.component'),\n        title: 'Time',\n    },\n    {\n        path: DemoPath.Date,\n        loadComponent: () => import('../pages/kit/date/date-mask-doc.component'),\n        title: 'Date',\n    },\n    {\n        path: DemoPath.DateRange,\n        loadComponent: () =>\n            import('../pages/kit/date-range/date-range-mask-doc.component'),\n        title: 'DateRange',\n    },\n    {\n        path: DemoPath.KitPlugins,\n        loadComponent: () => import('../pages/kit/plugins/kit-plugins-doc.component'),\n        title: 'Plugins | @maskito/kit',\n    },\n    // Recipes\n    {\n        path: DemoPath.DateTime,\n        loadComponent: () =>\n            import('../pages/kit/date-time/date-time-mask-doc.component'),\n        title: 'DateTime',\n    },\n    {\n        path: DemoPath.Card,\n        loadComponent: () => import('../pages/recipes/card/card-doc.component'),\n        title: 'Card',\n    },\n    {\n        path: DemoPath.Phone,\n        loadComponent: () => import('../pages/recipes/phone/phone-doc.component'),\n        title: 'Phone',\n    },\n    {\n        path: DemoPath.PhonePackage,\n        loadComponent: () => import('../pages/phone/phone-doc.component'),\n        title: 'Phone',\n    },\n    {\n        path: DemoPath.Textarea,\n        loadComponent: () => import('../pages/recipes/textarea/textarea-doc.component'),\n        title: 'Textarea',\n    },\n    {\n        path: DemoPath.ContentEditable,\n        loadComponent: () =>\n            import('../pages/recipes/content-editable/content-editable-doc.component'),\n        title: 'ContentEditable',\n    },\n    {\n        path: DemoPath.Prefix,\n        loadComponent: () => import('../pages/recipes/prefix/prefix-doc.component'),\n        title: 'With prefix',\n    },\n    {\n        path: DemoPath.Postfix,\n        loadComponent: () => import('../pages/recipes/postfix/postfix-doc.component'),\n        title: 'With postfix',\n    },\n    {\n        path: DemoPath.Placeholder,\n        loadComponent: () =>\n            import('../pages/recipes/placeholder/placeholder-doc.component'),\n        title: 'With placeholder',\n    },\n    {\n        path: DemoPath.NetworkAddress,\n        loadComponent: () =>\n            import('../pages/recipes/network-address/network-address-doc.component'),\n        title: 'Network address',\n    },\n    // Other\n    {\n        path: DemoPath.BrowserSupport,\n        loadComponent: () =>\n            import('../pages/documentation/browser-support/browser-support.component'),\n        title: 'Browser support',\n    },\n    {\n        path: DemoPath.SupportedInputTypes,\n        loadComponent: () =>\n            import('../pages/documentation/supported-input-types/supported-input-types.component'),\n        title: 'Supported <input /> types',\n    },\n    {\n        path: DemoPath.RealWorldForm,\n        loadComponent: () => import('../pages/documentation/real-world-form'),\n        title: 'Maskito in Real World Form',\n    },\n    {\n        path: DemoPath.Stackblitz,\n        loadComponent: () =>\n            import('../pages/stackblitz').then((m) => m.StackblitzStarterComponent),\n        title: 'Stackblitz Starter',\n    },\n]\n    .map(tuiProvideRoutePageTab)\n    .concat({\n        path: '**',\n        redirectTo: DemoPath.WhatIsMaskito,\n    });\n"
  },
  {
    "path": "projects/demo/src/app/app.style.less",
    "content": "@import '@taiga-ui/core/styles/taiga-ui-local.less';\n\n:host {\n    display: block;\n    font: var(--tui-font-text-m);\n    color: var(--tui-text-primary);\n}\n\n.link {\n    margin-inline-start: 1rem;\n}\n"
  },
  {
    "path": "projects/demo/src/app/constants/demo-path.ts",
    "content": "export const DemoPath = {\n    WhatIsMaskito: 'getting-started/what-is-maskito',\n    MaskitoLibraries: 'getting-started/maskito-libraries',\n    CoreConceptsOverview: 'core-concepts/overview',\n    MaskExpression: 'core-concepts/mask-expression',\n    ElementState: 'core-concepts/element-state',\n    Processors: 'core-concepts/processors',\n    Plugins: 'core-concepts/plugins',\n    OverwriteMode: 'core-concepts/overwrite-mode',\n    Transformer: 'core-concepts/transformer',\n    Angular: 'frameworks/angular',\n    React: 'frameworks/react',\n    Vue: 'frameworks/vue',\n    Number: 'kit/number',\n    Time: 'kit/time',\n    Date: 'kit/date',\n    DateRange: 'kit/date-range',\n    DateTime: 'kit/date-time',\n    KitPlugins: 'kit/plugins',\n    PhonePackage: 'addons/phone',\n    Card: 'recipes/card',\n    Phone: 'recipes/phone',\n    Textarea: 'recipes/textarea',\n    ContentEditable: 'recipes/content-editable',\n    Prefix: 'recipes/prefix',\n    Postfix: 'recipes/postfix',\n    Placeholder: 'recipes/placeholder',\n    NetworkAddress: 'recipes/network-address',\n    BrowserSupport: 'browser-support',\n    SupportedInputTypes: 'supported-input-types',\n    RealWorldForm: 'real-world-form',\n    Stackblitz: 'stackblitz',\n} as const;\n"
  },
  {
    "path": "projects/demo/src/app/constants/doc-example-primary-tab.ts",
    "content": "export const DocExamplePrimaryTab = {\n    MaskitoOptions: 'mask',\n    JavaScript: 'JavaScript',\n    Angular: 'Angular',\n    React: 'React',\n    Vue: 'Vue',\n} as const;\n"
  },
  {
    "path": "projects/demo/src/app/constants/index.ts",
    "content": "export * from './demo-path';\nexport * from './doc-example-primary-tab';\n"
  },
  {
    "path": "projects/demo/src/app/modules/example-primary-tabs-icons/angular-logo.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {PolymorpheusComponent} from '@taiga-ui/polymorpheus';\n\n@Component({\n    selector: 'angular-logo',\n    template: '<img src=\"assets/icons/angular.svg\" />',\n    styles: ['img {display: flex; width: 1.5rem}'],\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AngularLogoComponent {}\n\nexport const ANGULAR_LOGO = new PolymorpheusComponent(AngularLogoComponent);\n"
  },
  {
    "path": "projects/demo/src/app/modules/example-primary-tabs-icons/index.ts",
    "content": "export * from './angular-logo.component';\nexport * from './javascript-logo.component';\nexport * from './react-logo.component';\n"
  },
  {
    "path": "projects/demo/src/app/modules/example-primary-tabs-icons/javascript-logo.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {PolymorpheusComponent} from '@taiga-ui/polymorpheus';\n\n@Component({\n    selector: 'javascript-logo',\n    template: '<img src=\"assets/icons/javascript.svg\" />',\n    styles: ['img {display: flex; width: 1.5rem}'],\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class JavaScriptLogoComponent {}\n\nexport const JAVASCRIPT_LOGO = new PolymorpheusComponent(JavaScriptLogoComponent);\n"
  },
  {
    "path": "projects/demo/src/app/modules/example-primary-tabs-icons/react-logo.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {PolymorpheusComponent} from '@taiga-ui/polymorpheus';\n\n@Component({\n    selector: 'react-logo',\n    template: '<img src=\"assets/icons/react.svg\" />',\n    styles: ['img {display: flex; width: 1.5rem}'],\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ReactLogoComponent {}\n\nexport const REACT_LOGO = new PolymorpheusComponent(ReactLogoComponent);\n"
  },
  {
    "path": "projects/demo/src/app/modules/example-primary-tabs-icons/vue-logo.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {PolymorpheusComponent} from '@taiga-ui/polymorpheus';\n\n@Component({\n    selector: 'vue-logo',\n    template: '<img src=\"assets/icons/vue.svg\" />',\n    styles: ['img {display: flex; width: 1.5rem}'],\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class VueLogoComponent {}\n\nexport const VUE_LOGO = new PolymorpheusComponent(VueLogoComponent);\n"
  },
  {
    "path": "projects/demo/src/app/modules/logo/logo.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {TuiLink} from '@taiga-ui/core';\nimport {PolymorpheusComponent} from '@taiga-ui/polymorpheus';\n\n@Component({\n    selector: 'logo',\n    imports: [RouterLink, TuiLink],\n    templateUrl: './logo.template.html',\n    styleUrl: './logo.style.less',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class LogoComponent {}\n\nexport const LOGO_CONTENT = new PolymorpheusComponent(LogoComponent);\n"
  },
  {
    "path": "projects/demo/src/app/modules/logo/logo.style.less",
    "content": "@import '@taiga-ui/core/styles/taiga-ui-local.less';\n\n:host {\n    display: flex;\n    align-items: center;\n\n    @media @tui-mobile {\n        font-size: 0;\n    }\n}\n\n.logo-link {\n    display: flex;\n}\n\n.logo-name {\n    display: flex;\n    color: var(--tui-text-primary);\n}\n\n.logo {\n    margin-inline-end: 0.625rem;\n}\n\n.by {\n    margin-inline-start: 0.875rem;\n\n    @media @tui-mobile {\n        display: none;\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/app/modules/logo/logo.template.html",
    "content": "<a\n    routerLink=\"/\"\n    tuiLink\n    class=\"logo-link\"\n>\n    <span class=\"logo-name\">\n        <img\n            alt=\"Maskito logo\"\n            src=\"assets/icons/maskito.svg\"\n            class=\"logo\"\n        />\n        Maskito\n    </span>\n</a>\n"
  },
  {
    "path": "projects/demo/src/app/server-error-handler.ts",
    "content": "import {type ErrorHandler, Injectable} from '@angular/core';\n\n// TODO\nconst KNOWN_ISSUES: ReadonlyArray<RegExp | string> = [\n    /**\n     * ```\n     * // mask.ts\n     * export default {mask: '...'}\n     *\n     * // another-file.ts\n     * import('./mask.ts', {with: {loader: 'text'}})\n     *     .then(x => x.default)\n     * ```\n     * During SERVER side rendering, `x.default` invalidly equals to `{mask: '...'}` object.\n     * During CLIENT side rendering, `x.default` correctly equals to raw file content.\n     *\n     * TODO(v6): no more relevant for Angular >= 20\n     */\n    'Input data should be a String',\n    // Same here\n    /Cannot find (module|package) 'react-hook-form' imported from/, // TODO(v6): remove after Angular bump to >= 20\n];\n\n@Injectable()\nexport class ServerErrorHandler implements ErrorHandler {\n    public handleError(error: Error | string): void {\n        const errorMessage = (typeof error === 'string' ? error : error.message) || '';\n\n        if (\n            KNOWN_ISSUES.some((issue) =>\n                typeof issue === 'string'\n                    ? errorMessage.includes(issue)\n                    : errorMessage.match(issue),\n            )\n        ) {\n            return;\n        }\n\n        console.error(errorMessage);\n\n        if (\n            // Default environment variables for GitHub CI\n            process.env.CI // https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables\n        ) {\n            process.exit(1);\n        }\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/app/utils/add-default-tabs-processor/add-default-tabs-processor.ts",
    "content": "import {DocExamplePrimaryTab} from '@demo/constants';\nimport type {TuiHandler} from '@taiga-ui/cdk';\n\nimport {ANGULAR_DEFAULT_TAB} from './default-tabs/angular-default-tab';\nimport {JS_DEFAULT_TAB} from './default-tabs/js-default-tab';\nimport {REACT_DEFAULT_TAB} from './default-tabs/react-default-tab';\nimport {VUE_DEFAULT_TAB} from './default-tabs/vue-default-tab';\n\nexport const addDefaultTabsProcessor: TuiHandler<\n    Record<string, string>,\n    Record<string, string>\n> = (files) => {\n    const fileNames = Object.keys(files);\n\n    return fileNames.length === 1 && fileNames[0] === DocExamplePrimaryTab.MaskitoOptions\n        ? {\n              ...files,\n              [DocExamplePrimaryTab.JavaScript]: JS_DEFAULT_TAB,\n              [DocExamplePrimaryTab.Angular]: ANGULAR_DEFAULT_TAB,\n              [DocExamplePrimaryTab.React]: REACT_DEFAULT_TAB,\n              [DocExamplePrimaryTab.Vue]: VUE_DEFAULT_TAB,\n          }\n        : files;\n};\n"
  },
  {
    "path": "projects/demo/src/app/utils/add-default-tabs-processor/default-tabs/angular-default-tab.ts",
    "content": "export const ANGULAR_DEFAULT_TAB = `import {Component} from '@angular/core';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {MaskitoOptions} from '@maskito/core';\n\nimport mask from './mask';\n\n@Component({\n  selector: 'my-app',\n  imports: [MaskitoDirective],\n  template: '<input [maskito]=\"options\" />',\n})\nexport class App {\n  readonly options: MaskitoOptions = mask;\n}`;\n"
  },
  {
    "path": "projects/demo/src/app/utils/add-default-tabs-processor/default-tabs/js-default-tab.ts",
    "content": "export const JS_DEFAULT_TAB = `import {Maskito, MaskitoOptions} from '@maskito/core';\nimport maskitoOptions from './mask';\n\nconst element = document.querySelector('input,textarea')!;\nconst maskedInput = new Maskito(element, maskitoOptions);\n\n// Call this function when the element is detached from DOM\nmaskedInput.destroy();`;\n"
  },
  {
    "path": "projects/demo/src/app/utils/add-default-tabs-processor/default-tabs/react-default-tab.ts",
    "content": "export const REACT_DEFAULT_TAB = `import * as React from 'react';\nimport {useMaskito} from '@maskito/react';\n\nimport options from './mask';\n\nexport default function App() {\n  const maskedInputRef = useMaskito({options});\n\n  return <input ref={maskedInputRef} />;\n}`;\n"
  },
  {
    "path": "projects/demo/src/app/utils/add-default-tabs-processor/default-tabs/vue-default-tab.ts",
    "content": "export const VUE_DEFAULT_TAB = `import {createApp} from 'vue';\nimport {maskito} from '@maskito/vue';\n\nimport options from './mask';\n\nconst app = createApp({\n  template: '<input v-maskito=\"options\" />',\n  directives: {maskito},\n  data: () => ({ options }),\n});`;\n"
  },
  {
    "path": "projects/demo/src/app/utils/index.ts",
    "content": "export * from './add-default-tabs-processor/add-default-tabs-processor';\n"
  },
  {
    "path": "projects/demo/src/assets/manifest.webmanifest",
    "content": "{\n    \"icons\": [\n        {\"src\": \"/favicon-192.png\", \"type\": \"image/png\", \"sizes\": \"192x192\"},\n        {\"src\": \"/favicon-512.png\", \"type\": \"image/png\", \"sizes\": \"512x512\"}\n    ]\n}\n"
  },
  {
    "path": "projects/demo/src/environments/environment.prod.ts",
    "content": "export const environment = {production: true};\n"
  },
  {
    "path": "projects/demo/src/environments/environment.ts",
    "content": "export const environment = {production: false};\n"
  },
  {
    "path": "projects/demo/src/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\" />\n        <meta\n            content=\"text/html; charset=utf-8\"\n            http-equiv=\"Content-Type\"\n        />\n        <meta\n            content=\"ie=edge\"\n            http-equiv=\"x-ua-compatible\"\n        />\n        <meta\n            content=\"width=device-width, initial-scale=1, maximum-scale=1\"\n            name=\"viewport\"\n        />\n        <meta\n            content=\"Acpekt\"\n            name=\"author\"\n        />\n        <meta\n            content=\"Acpekt\"\n            name=\"copyright\"\n        />\n\n        <link\n            href=\"assets/favicon/safari-favicon.png\"\n            rel=\"icon\"\n            sizes=\"any\"\n        />\n\n        <link\n            href=\"assets/icons/maskito.svg\"\n            rel=\"icon\"\n        />\n\n        <link\n            href=\"assets/favicon/apple-touch-icon.png\"\n            rel=\"apple-touch-icon\"\n        />\n\n        <link\n            href=\"assets/manifest.webmanifest\"\n            rel=\"manifest\"\n        />\n\n        <title>Maskito</title>\n    </head>\n    <body>\n        <app>Loading...</app>\n    </body>\n</html>\n"
  },
  {
    "path": "projects/demo/src/main.server.ts",
    "content": "import {type ApplicationRef, ErrorHandler, mergeApplicationConfig} from '@angular/core';\nimport {bootstrapApplication, type BootstrapContext} from '@angular/platform-browser';\nimport {provideServerRendering} from '@angular/platform-server';\nimport {provideServerRouting, RenderMode, type ServerRoute} from '@angular/ssr';\nimport {DemoPath} from '@demo/constants';\nimport {UNIVERSAL_PROVIDERS} from '@ng-web-apis/universal';\n\nimport {App} from './app/app.component';\nimport {APP_CONFIG} from './app/app.config';\nimport {ROUTES} from './app/app.routes';\nimport {ServerErrorHandler} from './app/server-error-handler';\n\n/* eslint-disable @typescript-eslint/require-await */\n\nconst serverConfig = mergeApplicationConfig(APP_CONFIG, {\n    providers: [\n        provideServerRendering(),\n        provideServerRouting(\n            ROUTES.map((route) => {\n                const path = route.path ?? '';\n\n                switch (path) {\n                    case DemoPath.Angular:\n                        return withTabs(path, ['Setup']);\n                    case DemoPath.Number:\n                        return withTabs(path, ['API', 'Helpers']);\n                    case DemoPath.Plugins:\n                        return withTabs(path, ['Built-in_core_plugins']);\n                    default:\n                        return path.startsWith('kit') || path.startsWith('addons')\n                            ? withTabs(path, ['API'])\n                            : {\n                                  path,\n                                  renderMode: RenderMode.Prerender,\n                                  async getPrerenderParams() {\n                                      return [];\n                                  },\n                              };\n                }\n            }),\n        ),\n        UNIVERSAL_PROVIDERS,\n        {provide: ErrorHandler, useClass: ServerErrorHandler},\n    ],\n});\n\nfunction withTabs(path: string, tabs: string[]): ServerRoute {\n    return {\n        path: `${path}/:tab`,\n        renderMode: RenderMode.Prerender,\n        async getPrerenderParams() {\n            return tabs.map((tab) => ({tab}));\n        },\n    };\n}\n\nexport default async (context: BootstrapContext): Promise<ApplicationRef> =>\n    bootstrapApplication(App, serverConfig, context);\n"
  },
  {
    "path": "projects/demo/src/main.ts",
    "content": "import {bootstrapApplication} from '@angular/platform-browser';\n\nimport {App} from './app/app.component';\nimport {APP_CONFIG} from './app/app.config';\n\nbootstrapApplication(App, APP_CONFIG).catch((err: unknown) => console.error(err));\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/browser-support/browser-support.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {TuiAddonDoc} from '@taiga-ui/addon-doc';\n\n@Component({\n    selector: 'browser-support',\n    imports: [TuiAddonDoc],\n    templateUrl: './browser-support.template.html',\n    styles: ['td {width: 18.75rem}'],\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class BrowserSupportComponent {\n    protected readonly desktopBrowsers = [\n        {name: 'Google Chrome', version: '88+'},\n        {name: 'Mozilla Firefox', version: '120+'},\n        {name: 'Safari', version: '14.1+'},\n        {name: 'Opera', version: '74+'},\n        {name: 'Edge', version: '88+'},\n        {name: 'Yandex Browser', version: '21.2+'},\n        {name: 'Microsoft Internet Explorer', version: null},\n    ] as const;\n\n    protected readonly mobileBrowsers = [\n        {name: 'Google Chrome', version: '88+'},\n        {name: 'Mozilla Firefox', version: '120+'},\n        {name: 'Safari', version: '14.5+'},\n        {name: 'Opera', version: '63+'},\n        {name: 'Samsung Mobile', version: '15+'},\n        {name: 'Yandex Browser', version: '21.2+'},\n    ];\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/browser-support/browser-support.template.html",
    "content": "<tui-doc-page header=\"Browser support\">\n    <h2 class=\"tui-text_h4 tui-space_top-0 tui-space_bottom-3\">Desktop</h2>\n    <table class=\"tui-table\">\n        <tbody>\n            <tr class=\"tui-table__tr\">\n                <th class=\"tui-table__th\">Browser</th>\n                <th class=\"tui-table__th\">Version</th>\n            </tr>\n            @for (browser of desktopBrowsers; track browser) {\n                <tr class=\"tui-table__tr\">\n                    <td class=\"tui-table__td\">{{ browser.name }}</td>\n                    <td class=\"tui-table__td\">\n                        @if (browser.version) {\n                            {{ browser.version }}\n                        } @else {\n                            <strong>Not supported</strong>\n                        }\n                    </td>\n                </tr>\n            }\n        </tbody>\n    </table>\n    <h2 class=\"tui-text_h4 tui-space_top-6 tui-space_bottom-3\">Mobile</h2>\n    <table class=\"tui-table\">\n        <tbody>\n            <tr class=\"tui-table__tr\">\n                <th class=\"tui-table__th\">Browser</th>\n                <th class=\"tui-table__th\">Version</th>\n            </tr>\n            @for (browser of mobileBrowsers; track browser) {\n                <tr class=\"tui-table__tr\">\n                    <td class=\"tui-table__td\">{{ browser.name }}</td>\n                    <td class=\"tui-table__td\">{{ browser.version }}</td>\n                </tr>\n            }\n        </tbody>\n    </table>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/core-concepts-overview/core-concepts-overview.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath} from '@demo/constants';\nimport {TuiAddonDoc} from '@taiga-ui/addon-doc';\nimport {TuiIcon, TuiLink, TuiNotification, TuiSurface, TuiTitle} from '@taiga-ui/core';\nimport {TuiTooltip} from '@taiga-ui/kit';\nimport {TuiCardLarge, TuiHeader} from '@taiga-ui/layout';\n\n@Component({\n    selector: 'core-concepts-overview-doc-page',\n    imports: [\n        RouterLink,\n        TuiAddonDoc,\n        TuiCardLarge,\n        TuiHeader,\n        TuiIcon,\n        TuiLink,\n        TuiNotification,\n        TuiSurface,\n        TuiTitle,\n        TuiTooltip,\n    ],\n    templateUrl: './core-concepts-overview.template.html',\n    styleUrl: './core-concepts-overview.styles.less',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class CoreConceptsOverviewDocPageComponent {\n    protected readonly maskitoPublicApiDemo =\n        import('./examples/maskito-public-api-demo.md');\n\n    protected readonly maskExpressionDocPage = `/${DemoPath.MaskExpression}`;\n    protected readonly processorsDocPage = `/${DemoPath.Processors}`;\n    protected readonly pluginsDocPage = `/${DemoPath.Plugins}`;\n    protected readonly overwriteModeDocPage = `/${DemoPath.OverwriteMode}`;\n    protected readonly transformerDocPage = `/${DemoPath.Transformer}`;\n    protected readonly supportedInputTypesDocPage = `/${DemoPath.SupportedInputTypes}`;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/core-concepts-overview/core-concepts-overview.styles.less",
    "content": "@import '@taiga-ui/core/styles/taiga-ui-local.less';\n\n.cards {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: center;\n    gap: 2rem;\n\n    @media @tui-mobile {\n        flex-direction: column;\n    }\n\n    & [tuiCardLarge] {\n        flex: 1;\n        min-inline-size: 14rem;\n\n        @media @tui-desktop-min {\n            max-inline-size: 40%;\n        }\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/core-concepts-overview/core-concepts-overview.template.html",
    "content": "<tui-doc-page header=\"Core concepts\">\n    <section class=\"tui-space_bottom-4\">\n        <p class=\"tui-space_top-0\">\n            The main entity of Maskito core library is\n            <code>Maskito</code>\n            class which accepts 2 arguments in constructor:\n        </p>\n\n        <ol class=\"tui-list tui-list_ordered\">\n            <li class=\"tui-list__item\">\n                native\n                <code>\n                    HTMLInputElement\n                    <tui-icon\n                        tuiHintDirection=\"top\"\n                        [tuiTooltip]=\"tooltipContent\"\n                    />\n\n                    <ng-template #tooltipContent>\n                        <strong>Maskito</strong>\n                        supports only limited types of\n                        <code>HTMLInputElement</code>\n                        due to some browser limitations!\n\n                        <p class=\"tui-space_bottom-0\">\n                            <a\n                                appearance=\"icon\"\n                                tuiLink\n                                tuiTheme=\"dark\"\n                                [pseudo]=\"true\"\n                                [routerLink]=\"supportedInputTypesDocPage\"\n                            >\n                                See a full list of supported types\n                            </a>\n                        </p>\n                    </ng-template>\n                </code>\n                or\n                <code>HTMLTextAreaElement</code>\n            </li>\n            <li class=\"tui-list__item\">\n                set of configurable\n                <a\n                    href=\"https://github.com/taiga-family/maskito/blob/main/projects/core/src/lib/types/mask-options.ts\"\n                    rel=\"noreferrer\"\n                    target=\"_blank\"\n                    tuiLink\n                >\n                    <code>MaskitoOptions</code>\n                </a>\n            </li>\n        </ol>\n    </section>\n\n    <tui-doc-code\n        class=\"tui-space_bottom-4\"\n        [code]=\"maskitoPublicApiDemo\"\n    />\n\n    <tui-notification\n        appearance=\"warning\"\n        size=\"m\"\n    >\n        <div>\n            <strong>Avoid wasting computation power or memory resources!</strong>\n\n            <p class=\"tui-space_bottom-0\">\n                The only available public method\n                <code>destroy</code>\n                removes all created event listeners. Call it to clean everything up when the work is finished.\n            </p>\n        </div>\n    </tui-notification>\n\n    <section>\n        <p>\n            To understand the capabilities of the Maskito library, you need to learn about the following features and\n            concepts:\n        </p>\n\n        <div class=\"cards\">\n            <a\n                tuiCardLarge\n                tuiHeader\n                tuiSurface=\"elevated\"\n                [routerLink]=\"maskExpressionDocPage\"\n            >\n                <h1 tuiTitle>\n                    Mask expression\n                    <span tuiSubtitle>\n                        Learn how to predefine your mask format via mask expression. This section describes\n                        different types of mask expression and explains meaning of \"fixed character\" term.\n                    </span>\n                </h1>\n            </a>\n\n            <a\n                tuiCardLarge\n                tuiHeader\n                tuiSurface=\"elevated\"\n                [routerLink]=\"processorsDocPage\"\n            >\n                <h1 tuiTitle>\n                    Processors\n                    <span tuiSubtitle>Learn about preprocessors and postprocessors.</span>\n                </h1>\n            </a>\n\n            <a\n                tuiCardLarge\n                tuiHeader\n                tuiSurface=\"elevated\"\n                [routerLink]=\"pluginsDocPage\"\n            >\n                <h1 tuiTitle>\n                    Plugins\n                    <span tuiSubtitle>\n                        Learn how you can augment masking with some custom logic bound to the masked HTML element.\n                    </span>\n                </h1>\n            </a>\n\n            <a\n                tuiCardLarge\n                tuiHeader\n                tuiSurface=\"elevated\"\n                [routerLink]=\"overwriteModeDocPage\"\n            >\n                <h1 tuiTitle>\n                    Overwrite mode\n                    <span tuiSubtitle>\n                        Maskito can behave differently when user inserts new character in the middle of text field\n                        value. Learn how to control this behaviour via\n                        <code>overwriteMode</code>\n                        parameter.\n                    </span>\n                </h1>\n            </a>\n\n            <a\n                tuiCardLarge\n                tuiHeader\n                tuiSurface=\"elevated\"\n                [routerLink]=\"transformerDocPage\"\n            >\n                <h1 tuiTitle>\n                    Transformer\n                    <span tuiSubtitle>\n                        Learn how to correctly programmatically update element's value via\n                        <code>maskitoTransform</code>\n                        .\n                    </span>\n                </h1>\n            </a>\n        </div>\n    </section>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/core-concepts-overview/examples/maskito-public-api-demo.md",
    "content": "```ts\nimport {Maskito, maskitoInitialCalibrationPlugin} from '@maskito/core';\n\nconst maskedInput = new Maskito(element, {\n  mask: /^\\d+$/,\n  preprocessors: [preprocessor1, preprocessor2],\n  postprocessors: [\n    ({value, selection}) => {\n      // ...\n    },\n  ],\n  plugins: [myCustomPlugin, maskitoInitialCalibrationPlugin()],\n  overwriteMode: 'shift',\n});\n\n// Call it when the element is destroyed\nmaskedInput.destroy();\n```\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/element-state/element-state.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath} from '@demo/constants';\nimport {TuiAddonDoc} from '@taiga-ui/addon-doc';\nimport {TuiLink} from '@taiga-ui/core';\n\n@Component({\n    selector: 'element-state-doc-page',\n    imports: [RouterLink, TuiAddonDoc, TuiLink],\n    templateUrl: './element-state.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class ElementStateDocPageComponent {\n    protected readonly elementStateDemo = import('./examples/element-state-demo.md');\n\n    protected readonly maskExpressionDocPage = `/${DemoPath.MaskExpression}`;\n    protected readonly processorsDocPage = `/${DemoPath.Processors}`;\n    protected readonly overwriteModeDocPage = `/${DemoPath.OverwriteMode}`;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/element-state/element-state.template.html",
    "content": "<tui-doc-page header=\"Element state\">\n    <section class=\"tui-space_bottom-6\">\n        <p class=\"tui-space_top-0\">\n            <strong>Element state</strong>\n            is a concept which describes the main properties of the masked element at the certain period of time.\n        </p>\n\n        <p>It is an object which implements the following interface:</p>\n\n        <tui-doc-code [code]=\"elementStateDemo\" />\n    </section>\n\n    <section>\n        <p>\n            This concept is actively used throughout\n            <strong>Maskito</strong>\n            libraries, and you can find its usage in the following topics:\n        </p>\n\n        <ul class=\"tui-list\">\n            <li class=\"tui-list__item\">\n                <a\n                    tuiLink\n                    [routerLink]=\"maskExpressionDocPage\"\n                >\n                    Mask expression\n                </a>\n            </li>\n            <li class=\"tui-list__item\">\n                <a\n                    tuiLink\n                    [routerLink]=\"processorsDocPage\"\n                >\n                    Processors\n                </a>\n            </li>\n            <li class=\"tui-list__item\">\n                <a\n                    tuiLink\n                    [routerLink]=\"overwriteModeDocPage\"\n                >\n                    Overwrite mode\n                </a>\n            </li>\n        </ul>\n    </section>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/element-state/examples/element-state-demo.md",
    "content": "```ts\ninterface ElementState {\n  // the value of a masked <input> or <textarea>\n  readonly value: string;\n  readonly selection: readonly [\n    // The 0-based index of the first selected character\n    from: number,\n    // Index of the character after the last selected character\n    to: number,\n  ];\n}\n```\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/mask-expression/examples/basic-time-example.md",
    "content": "```ts\nimport {Maskito} from '@maskito/core';\n\nconst timeInput = new Maskito(element, {\n  mask: [/\\d/, /\\d/, ':', /\\d/, /\\d/],\n});\n```\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/mask-expression/examples/dynamic-mask-expression-demo.md",
    "content": "```ts\nimport {Maskito} from '@maskito/core';\n\nlet howManyWordsAllowed = 5;\n\nconst maxWordInput = new Maskito(element, {\n  mask: (elementState) => new RegExp('^(\\\\w+\\\\s?){0,' + howManyWordsAllowed + '}$'),\n});\n```\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/mask-expression/examples/reg-exp-mask-expression-demo.md",
    "content": "```ts\nimport {Maskito} from '@maskito/core';\n\nconst onlyDigitsInput = new Maskito(element, {\n  mask: /^\\d+$/,\n});\n```\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/mask-expression/mask-expression.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath} from '@demo/constants';\nimport {TuiAddonDoc} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\n\nimport {NextStepsComponent} from '../next-steps/next-steps.component';\n\n@Component({\n    selector: 'mask-expression-doc-page',\n    imports: [NextStepsComponent, RouterLink, TuiAddonDoc, TuiLink, TuiNotification],\n    templateUrl: './mask-expression.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class MaskExpressionDocPageComponent {\n    protected readonly elementStateDocPage = `/${DemoPath.ElementState}`;\n    protected readonly regExpMaskExpDemo =\n        import('./examples/reg-exp-mask-expression-demo.md');\n\n    protected readonly basicTimeDemo = import('./examples/basic-time-example.md');\n    protected readonly dynamicMaskExpDemo =\n        import('./examples/dynamic-mask-expression-demo.md');\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/mask-expression/mask-expression.template.html",
    "content": "<tui-doc-page header=\"Mask expression\">\n    <div>\n        <strong>Mask expression</strong>\n        is the the main concept of Maskito core library. It provides the developer with opportunity to predefine format\n        of user's input. For example, you can set mask expression to accept only digits, only Latin letters or you can\n        configure more complex patterns like a date string.\n    </div>\n\n    <p>\n        You can set mask expression using\n        <code>mask</code>\n        parameter of\n        <a\n            href=\"https://github.com/taiga-family/maskito/blob/main/projects/core/src/lib/types/mask-options.ts\"\n            rel=\"noreferrer\"\n            target=\"_blank\"\n            tuiLink\n        >\n            <code>MaskitoOptions</code>\n        </a>\n        .\n    </p>\n\n    <section class=\"tui-space_top-8\">\n        <h2>Types of mask expression</h2>\n        <ul class=\"tui-list\">\n            <li class=\"tui-list__item\">\n                <strong>RegExp mask expression</strong>\n\n                <p class=\"tui-space_top-0\">\n                    The most basic and comprehensible type. The only required knowledge is understanding of native\n                    JavaScript\n                    <a\n                        href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions\"\n                        rel=\"noreferrer\"\n                        target=\"_blank\"\n                        tuiLink\n                    >\n                        Regular expression\n                    </a>\n                    .\n                </p>\n\n                <p>See the following example:</p>\n\n                <tui-doc-code [code]=\"regExpMaskExpDemo\" />\n\n                <tui-notification\n                    appearance=\"warning\"\n                    size=\"m\"\n                    class=\"tui-space_top-3\"\n                >\n                    <div>\n                        Make sure that mask expression works with any of intermediate states, not just the final value.\n\n                        <p>For example, imagine that you have to create mask for 4-digits PIN code.</p>\n\n                        <p>\n                            <code>{{ '/^\\\\d{4}$/' }}</code>\n                            is a wrong mask expression. It does not match intermediate states (you cannot complete\n                            4-digit string without possibility to type 1-, 2- or 3-digit string).\n                        </p>\n\n                        <p>\n                            <code>{{ '/^\\\\d{0,4}$/' }}</code>\n                            is the right solution for our example.\n                        </p>\n                    </div>\n                </tui-notification>\n            </li>\n            <li class=\"tui-list__item\">\n                <strong>Pattern mask expression</strong>\n\n                <p class=\"tui-space_top-0\">\n                    It is a good choice for more complex masks that are fixed in size. This type of mask expression is\n                    presented as array. Each element in the array has to be either a string or a regular expression.\n                    Each string is a\n                    <em>fixed character</em>\n                    and each regular expression is validator of character at the same index.\n                </p>\n\n                <tui-notification\n                    appearance=\"info\"\n                    size=\"m\"\n                >\n                    <div>\n                        <strong>Fixed character</strong>\n                        — a predefined character at a certain position (the same as its index inside mask expression\n                        array). It is automatically added when user forgets to type it. It cannot be erased or replaced\n                        with another character.\n                    </div>\n                </tui-notification>\n\n                <p>\n                    For example, imagine that you have to create mask for a time-string with\n                    <code>HH:MM</code>\n                    format. It consists of 4 digits and 1 fixed-character separator\n                    <code>:</code>\n                    .\n                </p>\n\n                <tui-doc-code [code]=\"basicTimeDemo\" />\n\n                <p>\n                    This mask expression forbids anything excepts digits and limits length of the value to 5 characters.\n                </p>\n\n                <p>Also, it manages user interactions with fixed character.</p>\n\n                <p>\n                    For example, user can just type four digits\n                    <code>1159</code>\n                    and the value becomes\n                    <code>11:59</code>\n                </p>\n\n                <p>\n                    Another example, if caret position is after the colon and user presses\n                    <kbd>Backspace</kbd>\n                    , the input's value will not change but caret will be moved to the left of the colon.\n                </p>\n            </li>\n            <li class=\"tui-list__item\">\n                <strong>Dynamic mask expression</strong>\n\n                <p class=\"tui-space_top-0\">\n                    <code>mask</code>\n                    parameter can also accepts function which generates mask expression. This function will be called\n                    <strong>every time before</strong>\n                    input changes to generate a new version of mask expression.\n                </p>\n\n                <tui-notification\n                    appearance=\"info\"\n                    size=\"m\"\n                    class=\"tui-space_bottom-3\"\n                >\n                    <div>\n                        An\n                        <a\n                            tuiLink\n                            [routerLink]=\"elementStateDocPage\"\n                        >\n                            \"Element state\"\n                        </a>\n                        object with raw value and current selection is passed as an argument to the function.\n                    </div>\n                </tui-notification>\n\n                <tui-doc-code [code]=\"dynamicMaskExpDemo\" />\n\n                <tui-notification\n                    appearance=\"warning\"\n                    size=\"m\"\n                    class=\"tui-space_top-6\"\n                >\n                    <div>\n                        <div>\n                            Be careful! It can be not performance-friendly to generate new mask expression on every\n                            input change.\n                        </div>\n\n                        <p class=\"tui-space_bottom-0\">Think about optimization and memoization of the such function.</p>\n                    </div>\n                </tui-notification>\n            </li>\n        </ul>\n    </section>\n\n    <next-steps />\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/maskito-libraries/maskito-libraries.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath} from '@demo/constants';\nimport {TuiAddonDoc} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\n\n@Component({\n    selector: 'maskito-libraries-doc-page',\n    imports: [RouterLink, TuiAddonDoc, TuiLink, TuiNotification],\n    templateUrl: './maskito-libraries.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class MaskitoLibrariesDocPageComponent {\n    protected readonly numberMaskDocPage = `/${DemoPath.Number}`;\n    protected readonly phoneMaskDocPage = `/${DemoPath.PhonePackage}`;\n    protected readonly timeMaskDocPage = `/${DemoPath.Time}`;\n    protected readonly coreConceptsOverviewDocPage = `/${DemoPath.CoreConceptsOverview}`;\n    protected readonly angularDocPage = `/${DemoPath.Angular}`;\n    protected readonly reactDocPage = `/${DemoPath.React}`;\n    protected readonly vueDocPage = `/${DemoPath.Vue}`;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/maskito-libraries/maskito-libraries.template.html",
    "content": "<tui-doc-page header=\"Maskito libraries\">\n    <p class=\"tui-space_top-0\">\n        <strong>Maskito</strong>\n        is a collection of libraries. Explore them and learn how to install and use them.\n    </p>\n\n    <ul class=\"tui-list\">\n        <li class=\"tui-list__item tui-space_bottom-12\">\n            <strong>&#64;maskito/core</strong>\n\n            <p class=\"tui-space_top-0\">\n                It is the main zero-dependency and framework-agnostic package. It can be used alone in vanilla\n                JavaScript project. It listens to\n                <code>beforeinput</code>\n                and\n                <code>input</code>\n                events to validate and calibrate text field value.\n            </p>\n\n            <tui-notification\n                appearance=\"info\"\n                size=\"m\"\n            >\n                <div>\n                    All other Maskito's packages require\n                    <code>&#64;maskito/core</code>\n                    as peer-dependency.\n                </div>\n            </tui-notification>\n\n            <tui-doc-code\n                code=\"npm install &#64;maskito/core\"\n                filename=\"/your/project/path>\"\n            />\n\n            <p>\n                Learn more about this library in\n                <a\n                    tuiLink\n                    [routerLink]=\"coreConceptsOverviewDocPage\"\n                >\n                    \"Core Concepts\"\n                </a>\n                section.\n            </p>\n        </li>\n\n        <li class=\"tui-list__item tui-space_bottom-12\">\n            <strong>&#64;maskito/kit</strong>\n\n            <p class=\"tui-space_top-0\">\n                The optional framework-agnostic package. It contains ready-to-use masks with configurable parameters.\n            </p>\n\n            <tui-doc-code\n                code=\"npm install &#64;maskito/kit\"\n                filename=\"/your/project/path>\"\n            />\n\n            <p>\n                See examples:\n                <a\n                    tuiLink\n                    [routerLink]=\"numberMaskDocPage\"\n                >\n                    Number\n                </a>\n                or\n                <a\n                    tuiLink\n                    [routerLink]=\"timeMaskDocPage\"\n                >\n                    Time\n                </a>\n                .\n            </p>\n        </li>\n\n        <li class=\"tui-list__item tui-space_bottom-12\">\n            <strong>&#64;maskito/phone</strong>\n\n            <p class=\"tui-space_top-0\">\n                The optional framework-agnostic package. It contains ready-to-use international phone mask based on\n                popular\n                <a\n                    href=\"https://www.npmjs.com/package/libphonenumber-js\"\n                    tuiLink\n                >\n                    libphonenumber-js\n                </a>\n                package.\n            </p>\n\n            <tui-doc-code\n                code=\"npm install &#64;maskito/phone\"\n                filename=\"/your/project/path>\"\n            />\n\n            <p>\n                See example\n                <a\n                    tuiLink\n                    [routerLink]=\"phoneMaskDocPage\"\n                >\n                    Phone Mask\n                </a>\n            </p>\n        </li>\n\n        <li class=\"tui-list__item tui-space_bottom-12\">\n            <strong>&#64;maskito/angular</strong>\n\n            <p class=\"tui-space_top-0\">\n                The Angular-specific library. It provides a convenient way to use Maskito as a directive.\n            </p>\n\n            <tui-doc-code\n                code=\"npm install &#64;maskito/angular\"\n                filename=\"/your/project/path>\"\n            />\n\n            <p>\n                Learn more about this library in\n                <a\n                    tuiLink\n                    [routerLink]=\"angularDocPage\"\n                >\n                    \"Angular\"\n                </a>\n                section.\n            </p>\n        </li>\n\n        <li class=\"tui-list__item tui-space_bottom-12\">\n            <strong>&#64;maskito/react</strong>\n\n            <p class=\"tui-space_top-0\">\n                The React-specific library. It provides a convenient way to use Maskito as a hook.\n            </p>\n\n            <tui-doc-code\n                code=\"npm install &#64;maskito/react\"\n                filename=\"/your/project/path>\"\n            />\n\n            <p>\n                Learn more about this library in\n                <a\n                    tuiLink\n                    [routerLink]=\"reactDocPage\"\n                >\n                    \"React\"\n                </a>\n                section.\n            </p>\n        </li>\n\n        <li class=\"tui-list__item\">\n            <strong>&#64;maskito/vue</strong>\n\n            <p class=\"tui-space_top-0\">\n                The Vue-specific library. It provides a convenient way to use Maskito as a directive.\n            </p>\n\n            <tui-doc-code\n                code=\"npm install &#64;maskito/vue\"\n                filename=\"/your/project/path>\"\n            />\n\n            <p>\n                Learn more about this library in\n                <a\n                    tuiLink\n                    [routerLink]=\"vueDocPage\"\n                >\n                    \"Vue\"\n                </a>\n                section.\n            </p>\n        </li>\n    </ul>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/next-steps/next-steps.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink, RouterLinkActive} from '@angular/router';\nimport {DemoPath} from '@demo/constants';\nimport {TuiLink} from '@taiga-ui/core';\n\n@Component({\n    selector: 'next-steps',\n    imports: [RouterLink, RouterLinkActive, TuiLink],\n    templateUrl: './next-steps.template.html',\n    styles: ['._hidden { display: none }'],\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NextStepsComponent {\n    protected readonly maskExpressionDocPage = `/${DemoPath.MaskExpression}`;\n    protected readonly processorsDocPage = `/${DemoPath.Processors}`;\n    protected readonly pluginsDocPage = `/${DemoPath.Plugins}`;\n    protected readonly overwriteModeDocPage = `/${DemoPath.OverwriteMode}`;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/next-steps/next-steps.template.html",
    "content": "<section class=\"tui-space_top-12\">\n    <h2>Next steps</h2>\n\n    <p>The following sections are recommended to explore core concepts further:</p>\n\n    <ul class=\"tui-list\">\n        <li\n            routerLinkActive=\"_hidden\"\n            class=\"tui-list__item\"\n        >\n            <a\n                tuiLink\n                [routerLink]=\"maskExpressionDocPage\"\n            >\n                Mask expression\n            </a>\n        </li>\n        <li\n            routerLinkActive=\"_hidden\"\n            class=\"tui-list__item\"\n        >\n            <a\n                tuiLink\n                [routerLink]=\"processorsDocPage\"\n            >\n                Processors\n            </a>\n        </li>\n        <li\n            routerLinkActive=\"_hidden\"\n            class=\"tui-list__item\"\n        >\n            <a\n                tuiLink\n                [routerLink]=\"pluginsDocPage\"\n            >\n                Plugins\n            </a>\n        </li>\n        <li\n            routerLinkActive=\"_hidden\"\n            class=\"tui-list__item\"\n        >\n            <a\n                tuiLink\n                [routerLink]=\"overwriteModeDocPage\"\n            >\n                Overwrite mode\n            </a>\n        </li>\n    </ul>\n</section>\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/overwrite-mode/examples/dynamic/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiTextareaModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'overwrite-mode-dynamic-doc-example-3',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiTextareaModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-textarea\n            [expandable]=\"true\"\n            [ngModel]=\"initialValue\"\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldLabelOutside]=\"true\"\n        >\n            <textarea\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            ></textarea>\n        </tui-textarea>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class OverwriteModeDocExample3 {\n    protected maskitoOptions = mask;\n\n    protected initialValue =\n        'This artificial example demonstrates the usage of dynamic mode. If this textarea contains only digits — \"replace\" mode is enabled. Otherwise, \"shift\" mode is enabled.';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/overwrite-mode/examples/dynamic/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nconst maskitoOptions: MaskitoOptions = {\n    mask: /^[^а-яё]+$/i,\n    overwriteMode: ({value}) => {\n        const includesOnlyDigits = /^\\d+$/.test(value);\n\n        return includesOnlyDigits ? 'replace' : 'shift';\n    },\n};\n\nexport default maskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/overwrite-mode/examples/replace/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiHint} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'overwrite-mode-replace-doc-example-2',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiHint,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiHintContent=\"Insert character somewhere in the middle\"\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class OverwriteModeDocExample2 {\n    protected readonly maskitoOptions = mask;\n    protected value = '0000';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/overwrite-mode/examples/replace/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nconst maskitoOptions: MaskitoOptions = {\n    mask: /^\\d+$/,\n    overwriteMode: 'replace',\n};\n\nexport default maskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/overwrite-mode/examples/shift/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiHint} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'overwrite-mode-shift-doc-example-1',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiHint,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiHintContent=\"Insert character somewhere in the middle\"\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class OverwriteModeDocExample1 {\n    protected readonly maskitoOptions = mask;\n    protected value = '0000';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/overwrite-mode/examples/shift/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nconst maskitoOptions: MaskitoOptions = {\n    mask: /^\\d+$/,\n    overwriteMode: 'shift',\n};\n\nexport default maskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/overwrite-mode/overwrite-mode.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath, DocExamplePrimaryTab} from '@demo/constants';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink} from '@taiga-ui/core';\n\nimport {NextStepsComponent} from '../next-steps/next-steps.component';\nimport {OverwriteModeDocExample3} from './examples/dynamic/component';\nimport {OverwriteModeDocExample2} from './examples/replace/component';\nimport {OverwriteModeDocExample1} from './examples/shift/component';\n\n@Component({\n    selector: 'overwrite-mode-doc-page',\n    imports: [\n        NextStepsComponent,\n        OverwriteModeDocExample1,\n        OverwriteModeDocExample2,\n        OverwriteModeDocExample3,\n        RouterLink,\n        TuiAddonDoc,\n        TuiLink,\n    ],\n    templateUrl: './overwrite-mode.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class OverwriteModeDocPageComponent {\n    protected readonly shiftExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/shift/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly replaceExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/replace/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly dynamicExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/dynamic/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly elementStateDocPage = `/${DemoPath.ElementState}`;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/overwrite-mode/overwrite-mode.template.html",
    "content": "<tui-doc-page header=\"Overwrite mode\">\n    <p class=\"tui-space_top-0\">\n        <strong>Overwrite mode</strong>\n        regulates behaviour of the mask when user inserts a new character somewhere in the middle of text field,\n        overwriting the character at the current index.\n    </p>\n\n    <section>\n        <p>\n            <code>overwriteMode</code>\n            can be of a following type:\n        </p>\n\n        <ul class=\"tui-list\">\n            <li class=\"tui-list__item\">\n                <code>shift</code>\n                (default)\n            </li>\n\n            <li class=\"tui-list__item\">\n                <code>replace</code>\n            </li>\n\n            <li class=\"tui-list__item\">\n                function that receives element state as an argument and returns\n                <code>shift</code>\n                or\n                <code>replace</code>\n            </li>\n        </ul>\n    </section>\n\n    <tui-doc-example\n        id=\"shift\"\n        heading=\"Shift mode\"\n        [content]=\"shiftExample\"\n        [description]=\"shiftModeDescription\"\n    >\n        <ng-template #shiftModeDescription>\n            The classic mode that everyone is used to. Inserting a new character in the middle of the text field value\n            <strong>shifts</strong>\n            all following characters to the right.\n        </ng-template>\n        <overwrite-mode-shift-doc-example-1 />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"replace\"\n        heading=\"Replace mode\"\n        [content]=\"replaceExample\"\n        [description]=\"replaceModeDescription\"\n    >\n        <ng-template #replaceModeDescription>\n            All new inserted characters\n            <strong>replace</strong>\n            the old characters at the same position. No character shifts. The length of the value remains the same after\n            inserting new character somewhere in middle of the text field.\n        </ng-template>\n        <overwrite-mode-replace-doc-example-2 />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"dynamic\"\n        heading=\"Dynamically detected mode\"\n        [content]=\"dynamicExample\"\n        [description]=\"dynamicModeDescription\"\n    >\n        <ng-template #dynamicModeDescription>\n            Parameter\n            <code>overwriteMode</code>\n            also accepts function that will called before each insertion of new characters. This function has one\n            argument — current element state (read more about it in the\n            <a\n                tuiLink\n                [routerLink]=\"elementStateDocPage\"\n            >\n                \"Element state\"\n            </a>\n            section). And this function should return one of two possible values:\n            <code>shift</code>\n            or\n            <code>replace</code>\n            .\n        </ng-template>\n        <overwrite-mode-dynamic-doc-example-3 />\n    </tui-doc-example>\n\n    <next-steps />\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/plugins/examples/1-initial-calibration/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'plugins-initial-calibration-doc-example-2',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"20\"\n            [(ngModel)]=\"value\"\n        >\n            Enter number\n\n            <input\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PluginsDocExample2 {\n    protected maskitoOptions = mask;\n    protected value = '12345';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/plugins/examples/1-initial-calibration/index.md",
    "content": "```ts\nimport {Maskito} from '@maskito/core';\n\nimport maskitoOptions from './mask';\n\nconst element = document.querySelector('input')!;\n\nelement.value = '12345'; // patch with invalid initial value\n\n// enable mask\nconst maskedInput = new Maskito(element, maskitoOptions);\n\nconsole.info(element.value); // 123\n\n// Call this function when the element is detached from DOM\nmaskedInput.destroy();\n```\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/plugins/examples/1-initial-calibration/mask.ts",
    "content": "import {maskitoInitialCalibrationPlugin, type MaskitoOptions} from '@maskito/core';\n\nconst maskitoOptions: MaskitoOptions = {\n    mask: /^\\d{0,3}$/,\n    plugins: [maskitoInitialCalibrationPlugin()],\n};\n\nexport default maskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/plugins/examples/2-strict-composition/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'plugins-strict-composition-doc-example-3',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [maskito]=\"maskitoOptions\"\n            [style.max-width.rem]=\"20\"\n            [(ngModel)]=\"value\"\n        >\n            Enter number\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PluginsDocExample3 {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/plugins/examples/2-strict-composition/mask.ts",
    "content": "import {type MaskitoOptions, maskitoStrictCompositionPlugin} from '@maskito/core';\n\nexport default {\n    mask: /^[0-9０-９]*$/,\n    plugins: [maskitoStrictCompositionPlugin()],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/plugins/examples/3-change-event/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'plugins-change-event-doc-example-4',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [maskito]=\"maskitoOptions\"\n            [style.max-width.rem]=\"20\"\n            [(ngModel)]=\"value\"\n        >\n            Enter number\n\n            <input\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n                (change)=\"log($event)\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PluginsDocExample4 {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n\n    protected log(anything: any): void {\n        console.info(anything);\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/plugins/examples/3-change-event/mask.ts",
    "content": "import {maskitoChangeEventPlugin, type MaskitoOptions} from '@maskito/core';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nconst numberOptions = maskitoNumberOptionsGenerator({maximumFractionDigits: 2});\n\nexport default {\n    ...numberOptions,\n    plugins: [\n        ...numberOptions.plugins,\n        maskitoChangeEventPlugin(), // <--- Enable it\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/plugins/examples/oversimplified-number-mask.md",
    "content": "```ts\nimport {MaskitoOptions} from '@maskito/core';\n\nexport default {\n  /**\n   * ^ – beginning of the string\n   * \\d – any digit\n   * \\d* – any number of digits\n   * \\.? – optional point to start decimal part\n   * $ – ending of the string\n   */\n  mask: /^\\d*\\.?\\d*$/,\n} satisfies MaskitoOptions;\n```\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/plugins/examples/pads-zero-plugin.ts",
    "content": "import {\n    /**\n     * HTMLElement + some common properties from:\n     * - HTMLInputElement\n     * - HTMLTextAreaElement\n     * - [contenteditable]\n     */\n    type MaskitoElement,\n    type MaskitoOptions,\n    maskitoUpdateElement,\n} from '@maskito/core';\n\nexport default {\n    mask: /^\\d*(?:\\.\\d*)?$/,\n    plugins: [\n        (element: MaskitoElement, _: MaskitoOptions) => {\n            const blurHandler = (): void => {\n                if (element.value.startsWith('.')) {\n                    /**\n                     * ❌ Anti-Pattern:\n                     * ```\n                     * element.value = `0${element.value}`;\n                     * ```\n                     */\n                    maskitoUpdateElement(element, `0${element.value}`);\n                }\n            };\n\n            element.addEventListener('blur', blurHandler);\n\n            // register a clean-up callback that is invoked when the mask is destroyed\n            return () => element.removeEventListener('blur', blurHandler);\n        },\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/plugins/plugins.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath, DocExamplePrimaryTab} from '@demo/constants';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiSurface, TuiTextfield, TuiTitle} from '@taiga-ui/core';\nimport {TuiCardLarge, TuiHeader} from '@taiga-ui/layout';\n\nimport {NextStepsComponent} from '../next-steps/next-steps.component';\nimport {PluginsDocExample2} from './examples/1-initial-calibration/component';\nimport {PluginsDocExample3} from './examples/2-strict-composition/component';\nimport {PluginsDocExample4} from './examples/3-change-event/component';\nimport documentationMask from './examples/pads-zero-plugin';\n\n@Component({\n    selector: 'plugins-mode-doc-page',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        NextStepsComponent,\n        PluginsDocExample2,\n        PluginsDocExample3,\n        PluginsDocExample4,\n        RouterLink,\n        TuiAddonDoc,\n        TuiCardLarge,\n        TuiHeader,\n        TuiLink,\n        TuiSurface,\n        TuiTextfield,\n        TuiTitle,\n    ],\n    templateUrl: './plugins.template.html',\n    styleUrl: './plugins.style.less',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class PluginsDocPageComponent {\n    protected readonly transformerDocPage = `/${DemoPath.Transformer}`;\n    protected readonly maskExpressionDocPage = `/${DemoPath.MaskExpression}`;\n    protected readonly kitPluginsDocPage = `/${DemoPath.KitPlugins}`;\n    protected readonly documentationMask = documentationMask;\n\n    protected readonly oversimplifiedNumberMask =\n        import('./examples/oversimplified-number-mask.md');\n\n    protected readonly padsZeroPlugin = import('./examples/pads-zero-plugin.ts?raw', {\n        with: {loader: 'text'},\n    });\n\n    protected readonly initialCalibrationExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/1-initial-calibration/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n        'index.ts': import('./examples/1-initial-calibration/index.md'),\n    };\n\n    protected readonly strictCompositionExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/2-strict-composition/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly changeEventExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/3-change-event/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/plugins/plugins.style.less",
    "content": "@import '@taiga-ui/core/styles/taiga-ui-local.less';\n\n.cards {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 1rem;\n\n    & [tuiCardLarge] {\n        flex: 1;\n        min-inline-size: 20rem;\n\n        @media @tui-desktop-min {\n            max-inline-size: 45%;\n        }\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/plugins/plugins.template.html",
    "content": "<tui-doc-page header=\"Plugins\">\n    <ng-template pageTab=\"Documentation\">\n        <p class=\"tui-space_top-0\">\n            <strong>Plugins</strong>\n            are functions that are called with input/textarea element and mask options as arguments upon mask\n            initialization. They can optionally return cleanup logic and allow you to extend mask with arbitrary\n            additional behavior.\n        </p>\n\n        <section class=\"tui-space_top-8\">\n            <h2>Create Your Own Plugin</h2>\n\n            <p>Let's explore this concept by solving an oversimplified task.</p>\n\n            <p>\n                Imagine that you've created a mask to allow users entering only number with decimal part. If you\n                explored documentation section\n                <a\n                    tuiLink\n                    [routerLink]=\"maskExpressionDocPage\"\n                >\n                    \"Mask expression\"\n                </a>\n                , it is piece of cake for you – everything is achieved by a base knowledge of\n                <code>RegExp</code>\n                and a few code lines:\n            </p>\n\n            <tui-doc-code [code]=\"oversimplifiedNumberMask\" />\n\n            <p>\n                It works fine, users and your boss are\n                <strong>almost</strong>\n                completely satisfied. The last desired detail - no empty integer part on blur.\n            </p>\n\n            <p>For example, imagine that you have the such textfield:</p>\n\n            <tui-textfield [style.max-width.rem]=\"20\">\n                <label tuiLabel>Enter number</label>\n                <input\n                    ngModel=\"1.23\"\n                    tuiTextfield\n                    [readOnly]=\"true\"\n                />\n            </tui-textfield>\n\n            <p>\n                User sets caret before point => press\n                <kbd>Backspace</kbd>\n                => blur textfield. The result is following:\n            </p>\n\n            <tui-textfield [style.max-width.rem]=\"20\">\n                <label tuiLabel>Enter number</label>\n                <input\n                    ngModel=\".23\"\n                    tuiTextfield\n                    [readOnly]=\"true\"\n                />\n            </tui-textfield>\n\n            <p>\n                Looks not perfect, right? You need to \"send signal\" for users that the such incomplete value will be\n                interpreted as\n                <code>0.23</code>\n                in your system. One way to do it – pads leading point with zero on blur event. Let's use the plugin for\n                achieve it!\n            </p>\n\n            <tui-doc-code [code]=\"padsZeroPlugin\" />\n\n            <p>\n                <strong>Good job!</strong>\n                Now, you are ready to create your own plugin. Explore the result in action (the created mask is applied\n                to the textfield below):\n            </p>\n\n            <tui-textfield\n                [style.max-width.rem]=\"20\"\n                [tuiTextfieldCleaner]=\"false\"\n            >\n                <label tuiLabel>Enter number</label>\n                <input\n                    ngModel=\"1.23\"\n                    tuiTextfield\n                    [maskito]=\"documentationMask\"\n                />\n            </tui-textfield>\n        </section>\n\n        <section class=\"tui-space_top-8\">\n            <h2 class=\"tui-space_top-8\">Explore built-in plugins</h2>\n\n            <div class=\"cards\">\n                <a\n                    routerLink=\"Built-in_core_plugins\"\n                    tuiCardLarge\n                    tuiHeader\n                    tuiSurface=\"elevated\"\n                >\n                    <h3 tuiTitle>\n                        &#64;maskito/core\n                        <span\n                            tuiSubtitle\n                            class=\"tui-space_top-2\"\n                        >\n                            <ul class=\"tui-list\">\n                                <li class=\"tui-list__item\">maskitoInitialCalibrationPlugin</li>\n                                <li class=\"tui-list__item\">maskitoStrictCompositionPlugin</li>\n                                <li class=\"tui-list__item\">maskitoChangeEventPlugin</li>\n                            </ul>\n                        </span>\n                    </h3>\n                </a>\n\n                <a\n                    tuiCardLarge\n                    tuiHeader\n                    tuiSurface=\"elevated\"\n                    [routerLink]=\"kitPluginsDocPage\"\n                >\n                    <h3 tuiTitle>\n                        &#64;maskito/kit\n\n                        <span\n                            tuiSubtitle\n                            class=\"tui-space_top-2\"\n                        >\n                            <ul class=\"tui-list\">\n                                <li class=\"tui-list__item\">maskitoSelectionChangeHandler, maskitoCaretGuard</li>\n                                <li class=\"tui-list__item\">\n                                    maskitoEventHandler, maskitoAddOnFocusPlugin, maskitoRemoveOnBlurPlugin\n                                </li>\n                                <li class=\"tui-list__item\">maskitoRejectEvent</li>\n                            </ul>\n                        </span>\n                    </h3>\n                </a>\n            </div>\n        </section>\n\n        <next-steps />\n    </ng-template>\n\n    <ng-template pageTab=\"Built-in core plugins\">\n        <tui-doc-example\n            id=\"initial-calibration\"\n            heading=\"For initial calibration\"\n            [content]=\"initialCalibrationExample\"\n            [description]=\"initialCalibrationDescription\"\n        >\n            <ng-template #initialCalibrationDescription>\n                <strong>Maskito</strong>\n                libraries were created to prevent\n                <u>only user</u>\n                from typing invalid value. However, sometimes you (developer) need to enable mask but you not sure that\n                you programmatically patched textfield with valid value. In this case you can use\n                <a\n                    tuiLink\n                    [routerLink]=\"transformerDocPage\"\n                >\n                    <code>maskitoTransform</code>\n                </a>\n                or just add\n                <code>maskitoInitialCalibrationPlugin</code>\n                to mask options.\n            </ng-template>\n            <plugins-initial-calibration-doc-example-2 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"strict-composition\"\n            heading=\"For composition event\"\n            [content]=\"strictCompositionExample\"\n            [description]=\"strictCompositionDescription\"\n        >\n            <ng-template #strictCompositionDescription>\n                <p class=\"tui-space_top-0\">\n                    By default,\n                    <strong>Maskito</strong>\n                    does not break IME Composition and waits until\n                    <a\n                        href=\"https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionend_event\"\n                        rel=\"noreferrer\"\n                        target=\"_blank\"\n                        tuiLink\n                    >\n                        <code>compositionend</code>\n                    </a>\n                    fires to begin calibration of the textfield's value. It is especially important for East Asian\n                    languages such as Chinese, Japanese, Korean, and other languages with complex characters.\n                </p>\n\n                <p>\n                    However, sometimes this behaviour is not desired and you can want to enable mask validation on every\n                    keystroke (to be like a classic not-composition input). For example, some Android devices with\n                    enabled system autocomplete can interpret user's input as part of composition event – waiting for\n                    <code>compositionend</code>\n                    can be not required for some cases (e.g. entering of numbers or your application is not used by East\n                    Asian clients). For this cases, you can use\n                    <code>maskitoStrictCompositionPlugin</code>\n                    . It applies mask's constraints on ANY intermediate value of IME composition.\n                </p>\n            </ng-template>\n            <plugins-strict-composition-doc-example-3 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"change-event\"\n            heading=\"For change event\"\n            [content]=\"changeEventExample\"\n            [description]=\"changeEventDescription\"\n        >\n            <ng-template #changeEventDescription>\n                Native\n                <a\n                    href=\"https://developer.mozilla.org/en-US/docs/Web/API/Element/beforeinput_event\"\n                    target=\"_blank\"\n                    tuiLink\n                >\n                    <code>beforeinput</code>\n                </a>\n                event default behavior is cancelled to process user entered invalid value. This causes native\n                <a\n                    href=\"https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event\"\n                    target=\"_blank\"\n                    tuiLink\n                >\n                    <code>change</code>\n                </a>\n                event to\n                <strong>NOT</strong>\n                be dispatched by browser. A\n                <code>change</code>\n                event, as opposed to\n                <code>input</code>\n                , is triggered only when user left the field and value was changed during interaction. If you rely on\n                this behavior, add\n                <code>maskitoChangeEventPlugin</code>\n                to your mask configuration. It will dispatch synthetic\n                <code>change</code>\n                event using the same logic.\n            </ng-template>\n            <plugins-change-event-doc-example-4 />\n        </tui-doc-example>\n    </ng-template>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/processors/examples/postprocessor-in-action.md",
    "content": "```ts\nimport {Maskito} from '@maskito/core';\n\nconst numberInput = new Maskito(element, {\n  mask: /^\\d+(,\\d*)?$/,\n  postprocessors: [\n    ({value, selection}, initialElementState) => {\n      const [from, to] = selection;\n      const noRepeatedLeadingZeroesValue = value.replace(/^0+/, '0');\n      const removedCharacters = value.length - noRepeatedLeadingZeroesValue.length;\n\n      return {\n        value: noRepeatedLeadingZeroesValue, // User types \"000000\" => 0|\n        selection: [Math.max(from - removedCharacters, 0), Math.max(to - removedCharacters, 0)],\n      };\n    },\n  ],\n});\n```\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/processors/examples/preprocessor-first-arg-demo.md",
    "content": "```ts\ntype firstArgDemo = {\n  // current input's element state BEFORE any changes are applied\n  elementState: {\n    value: string;\n    selection: [from: number, to: number];\n  };\n  // new typed characters which is going to be inserted to the element\n  data: string; // can be empty string if it is deletion or validation\n};\n```\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/processors/examples/preprocessor-in-action-demo.md",
    "content": "```ts\nimport {Maskito} from '@maskito/core';\n\nconst numberInput = new Maskito(element, {\n  mask: /^\\d+(,\\d*)?$/, // digits and comma (as decimal separator)\n  preprocessors: [\n    ({elementState, data}, actionType) => {\n      const {value, selection} = elementState;\n\n      return {\n        elementState: {\n          selection,\n          value: value.replace('.', ','),\n        },\n        data: data.replace('.', ','),\n      };\n    },\n  ],\n});\n```\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/processors/examples/processor-second-arg-demo.md",
    "content": "```ts\n'insert' | 'deleteForward' | 'deleteBackward' | 'validation';\n```\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/processors/processors.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath} from '@demo/constants';\nimport {TuiAddonDoc} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\n\nimport {NextStepsComponent} from '../next-steps/next-steps.component';\n\n@Component({\n    selector: 'processors-doc-page',\n    imports: [NextStepsComponent, RouterLink, TuiAddonDoc, TuiLink, TuiNotification],\n    templateUrl: './processors.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class ProcessorsDocPageComponent {\n    protected readonly preprocessorFirstArgDemo =\n        import('./examples/preprocessor-first-arg-demo.md');\n\n    protected readonly preprocessorsSecondArgDemo =\n        import('./examples/processor-second-arg-demo.md');\n\n    protected readonly preprocessorInActionDemo =\n        import('./examples/preprocessor-in-action-demo.md');\n\n    protected readonly postprocessorInActionDemo =\n        import('./examples/postprocessor-in-action.md');\n\n    protected readonly elementStateDocPage = `/${DemoPath.ElementState}`;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/processors/processors.template.html",
    "content": "<tui-doc-page header=\"Processors\">\n    <section class=\"tui-space_bottom-6\">\n        <p class=\"tui-space_top-0\">\n            <a\n                href=\"https://github.com/taiga-family/maskito/blob/main/projects/core/src/lib/types/mask-options.ts\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n                tuiLink\n            >\n                <code>MaskitoOptions</code>\n            </a>\n            have optional parameters\n            <code>preprocessors</code>\n            and\n            <code>postprocessors</code>\n            . Both accept array of pure functions. These functions are triggered on every user's input (\n            <a\n                href=\"https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n                tuiLink\n            >\n                beforeinput\n            </a>\n            and\n            <a\n                href=\"https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n                tuiLink\n            >\n                input\n            </a>\n            events). They provide an opportunity to modify value before / after the mask is applied.\n        </p>\n\n        <section>\n            <p>\n                Preprocessors and postprocessors accept different types of arguments but they have two important\n                similarities:\n            </p>\n            <ul class=\"tui-list\">\n                <li class=\"tui-list__item\">\n                    The first argument always contains object with information that you can change. Object with the same\n                    properties and updated values can be returned from the processor. It means that you can keep all\n                    properties untouched or you can change any or all of these properties.\n                </li>\n                <li class=\"tui-list__item\">\n                    The rest arguments contain information that can be useful to build some complex logic, but you\n                    cannot change it.\n                </li>\n            </ul>\n        </section>\n    </section>\n\n    <tui-notification\n        size=\"m\"\n        class=\"tui-space_bottom-12\"\n    >\n        <div>\n            Before you learn more about processors, you should learn a single prerequisite — meaning of the term\n            <a\n                tuiLink\n                [routerLink]=\"elementStateDocPage\"\n            >\n                \"Element state\"\n            </a>\n            .\n        </div>\n    </tui-notification>\n\n    <section class=\"tui-space_bottom-12\">\n        <h2>Preprocessors</h2>\n\n        <p>\n            Each preprocessor is a function that is called\n            <strong>before</strong>\n            mask is applied.\n        </p>\n\n        <p>\n            For example, if user types a new character, all preprocessors will be called first, and only then final\n            value that they returned will be passed into the mask, and finally the mask will accept or reject new typed\n            character and update actual value of the text field.\n        </p>\n\n        <section class=\"tui-space_bottom-6\">\n            <p>Preprocessor accepts two arguments:</p>\n            <ol class=\"tui-list tui-list_ordered\">\n                <li class=\"tui-list__item\">\n                    <strong>\n                        Object with two properties:\n                        <code>elementState</code>\n                        and\n                        <code>data</code>\n                        .\n                    </strong>\n                    Object of the same interface with updated or unchanged properties can be returned from the\n                    preprocessor.\n\n                    <tui-doc-code\n                        class=\"tui-space_top-1\"\n                        [code]=\"preprocessorFirstArgDemo\"\n                    />\n                </li>\n                <li class=\"tui-list__item\">\n                    Name of the action which triggers current execution. It can be one of the following possible values:\n                    <tui-doc-code [code]=\"preprocessorsSecondArgDemo\" />\n                </li>\n            </ol>\n        </section>\n\n        <p>Preprocessor returns an objects of the same interface as the first argument.</p>\n\n        <tui-doc-code [code]=\"preprocessorInActionDemo\" />\n    </section>\n\n    <section class=\"tui-space_bottom-12\">\n        <h2>Postprocessors</h2>\n\n        <p class=\"tui-space_bottom-0\">\n            Each postprocessor is a function that is called\n            <strong>after</strong>\n            the mask is applied. When all preprocessors are already called, all mask operations happened and the input's\n            value is about to be updated. You can change everything manually inside a postprocessor.\n        </p>\n\n        <section class=\"tui-space_bottom-6\">\n            <p>Postprocessor accepts two arguments:</p>\n            <ol class=\"tui-list tui-list_ordered\">\n                <li class=\"tui-list__item\">\n                    <strong>Element state after mask had been applied.</strong>\n                    Postprocessor can return updated element state which would then be reflected by the actual text\n                    field.\n                </li>\n                <li class=\"tui-list__item\">\n                    <strong>Initial element state before preprocessors and mask execution.</strong>\n                    It is a readonly argument, the past cannot be changed...\n                </li>\n            </ol>\n        </section>\n\n        <p>Postprocessor returns an objects of the same interface as the first argument.</p>\n\n        <tui-notification\n            appearance=\"warning\"\n            size=\"m\"\n            class=\"tui-space_bottom-4\"\n        >\n            <div>\n                <p class=\"tui-space_top-0 tui-space_bottom-0\">\n                    <strong>With great power comes great responsibility!</strong>\n                </p>\n\n                <p class=\"tui-space_bottom-0\">\n                    Postprocessor is the final step before input's value update which gives a lot of flexibility. Use\n                    postprocessor wisely and return a valid value!\n                </p>\n            </div>\n        </tui-notification>\n\n        <tui-doc-code [code]=\"postprocessorInActionDemo\" />\n    </section>\n\n    <tui-notification size=\"m\">\n        <div>\n            <strong>Stacking of multiple processors</strong>\n\n            <p>\n                The\n                <strong>Maskito</strong>\n                team likes code decomposition and promotes it! Don't put all complex logic inside a single processor.\n                Both parameters\n                <code>preprocessors</code>\n                and\n                <code>postprocessors</code>\n                accepts\n                <strong>array</strong>\n                of same type processors. Break your code into the several independent processors so that each processor\n                implements only a single task.\n            </p>\n        </div>\n    </tui-notification>\n\n    <next-steps />\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/real-world-form/index.html",
    "content": "<form\n    #formElement\n    autocomplete=\"on\"\n    class=\"tui-form\"\n    [formGroup]=\"form\"\n    (ngSubmit)=\"log(form.value); formElement.submit()\"\n>\n    <h3 class=\"tui-form__header tui-form__header_margin-top_none\">Real World Form</h3>\n    <div class=\"tui-form__row tui-form__row_multi-fields\">\n        <tui-input\n            formControlName=\"name\"\n            class=\"tui-form__multi-field\"\n        >\n            Name\n            <input\n                autocomplete=\"name\"\n                name=\"name\"\n                placeholder=\"Only latin letters\"\n                tuiTextfieldLegacy\n                [maskito]=\"nameMask\"\n            />\n        </tui-input>\n        <tui-input\n            formControlName=\"surname\"\n            class=\"tui-form__multi-field\"\n        >\n            Surname\n            <input\n                autocomplete=\"family-name\"\n                name=\"surname\"\n                placeholder=\"Only CAPITAL latin letters\"\n                tuiTextfieldLegacy\n                [maskito]=\"surnameMask\"\n            />\n        </tui-input>\n    </div>\n\n    <div class=\"tui-form__row\">\n        <tui-input\n            formControlName=\"phone\"\n            [tuiTextfieldCustomContent]=\"countryIsoCode ? flag : '@tui.phone'\"\n        >\n            Enter phone number\n            <input\n                autocomplete=\"tel\"\n                inputmode=\"tel\"\n                name=\"phone\"\n                tuiTextfieldLegacy\n                [attr.pattern]=\"phonePattern\"\n                [maskito]=\"phoneMask\"\n            />\n        </tui-input>\n\n        <ng-template #flag>\n            <img\n                width=\"28\"\n                [attr.alt]=\"countryIsoCode\"\n                [src]=\"countryIsoCode | tuiFlag\"\n                [style.border-radius.%]=\"50\"\n            />\n        </ng-template>\n    </div>\n\n    <div class=\"tui-form__row tui-form__row_multi-fields\">\n        <tui-input\n            formControlName=\"password\"\n            class=\"tui-form__multi-field\"\n            [tuiTextfieldCustomContent]=\"showHideIcon\"\n        >\n            Enter password\n            <input\n                autocomplete=\"new-password\"\n                name=\"password\"\n                placeholder=\"Only digits and one latin letter\"\n                tuiTextfieldLegacy\n                [maskito]=\"passwordMask\"\n                [type]=\"showPassword ? 'password' : 'text'\"\n            />\n        </tui-input>\n        <tui-input\n            formControlName=\"repeatedPassword\"\n            class=\"tui-form__multi-field\"\n            [tuiTextfieldCustomContent]=\"showHideIcon\"\n        >\n            Repeat password\n            <input\n                autocomplete=\"new-password\"\n                name=\"repeatPassword\"\n                placeholder=\"Only digits and one latin letter\"\n                tuiTextfieldLegacy\n                [maskito]=\"passwordMask\"\n                [type]=\"showPassword ? 'password' : 'text'\"\n            />\n        </tui-input>\n\n        <ng-template #showHideIcon>\n            <tui-icon\n                class=\"password-icon\"\n                [icon]=\"showPassword ? '@tui.eye' : '@tui.eye-off'\"\n                (click)=\"showPassword = !showPassword\"\n            />\n        </ng-template>\n    </div>\n\n    <div class=\"tui-form__row tui-form__row_multi-fields\">\n        <tui-input\n            formControlName=\"transactionDate\"\n            tuiTextfieldCustomContent=\"@tui.calendar\"\n            class=\"tui-form__multi-field\"\n        >\n            Transaction date\n            <input\n                inputmode=\"decimal\"\n                name=\"transactionDate\"\n                placeholder=\"dd.mm.yyyy\"\n                tuiTextfieldLegacy\n                [maskito]=\"transactionDateMask\"\n            />\n        </tui-input>\n\n        <tui-input\n            formControlName=\"transactionAmount\"\n            class=\"tui-form__multi-field\"\n        >\n            Transaction amount\n            <input\n                autocomplete=\"transaction-amount\"\n                inputmode=\"decimal\"\n                name=\"transactionAmount\"\n                placeholder=\"Enter amount\"\n                tuiTextfieldLegacy\n                [maskito]=\"transactionAmountMask\"\n            />\n        </tui-input>\n    </div>\n\n    <div class=\"tui-form__row\">\n        <tui-textarea\n            formControlName=\"address\"\n            [expandable]=\"true\"\n        >\n            Enter address\n\n            <textarea\n                autocomplete=\"street-address\"\n                name=\"address\"\n                placeholder=\"Only latin letters, digits and some punctuation signs are allowed\"\n                tuiTextfieldLegacy\n                [maskito]=\"addressMask\"\n            ></textarea>\n        </tui-textarea>\n    </div>\n\n    <div class=\"tui-form__buttons\">\n        <button\n            size=\"l\"\n            tuiButton\n            type=\"submit\"\n            class=\"tui-form__button\"\n        >\n            Submit\n        </button>\n\n        <button\n            appearance=\"flat\"\n            size=\"l\"\n            tuiButton\n            type=\"button\"\n            class=\"tui-form__button\"\n            (click)=\"form.reset()\"\n        >\n            Clear all\n        </button>\n    </div>\n</form>\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/real-world-form/index.less",
    "content": ":host {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    padding-block-start: 2rem;\n}\n\nform {\n    inline-size: 80%;\n    max-inline-size: 40rem;\n}\n\n.password-icon {\n    pointer-events: all;\n    cursor: pointer;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/real-world-form/index.ts",
    "content": "import {ChangeDetectionStrategy, Component, inject} from '@angular/core';\nimport {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {MaskitoOptions} from '@maskito/core';\nimport {\n    maskitoAddOnFocusPlugin,\n    maskitoDateOptionsGenerator,\n    maskitoNumberOptionsGenerator,\n    maskitoRemoveOnBlurPlugin,\n} from '@maskito/kit';\nimport {maskitoGetCountryFromNumber, maskitoPhoneOptionsGenerator} from '@maskito/phone';\nimport {isSafari} from '@ng-web-apis/platform';\nimport {TUI_IS_IOS, tuiInjectElement} from '@taiga-ui/cdk';\nimport {TuiButton, TuiFlagPipe, TuiIcon} from '@taiga-ui/core';\nimport {\n    TuiInputModule,\n    TuiTextareaModule,\n    TuiTextfieldControllerModule,\n} from '@taiga-ui/legacy';\nimport metadata from 'libphonenumber-js/min/metadata';\n\nconst MONEY_AMOUNT_MASK = maskitoNumberOptionsGenerator({\n    min: 0,\n    prefix: '$ ',\n    maximumFractionDigits: 2,\n});\n\nconst ONLY_LATIN_LETTERS_RE = /^[a-z]+$/i;\n\n@Component({\n    selector: 'real-world-form',\n    imports: [\n        MaskitoDirective,\n        ReactiveFormsModule,\n        TuiButton,\n        TuiFlagPipe,\n        TuiIcon,\n        TuiInputModule,\n        TuiTextareaModule,\n        TuiTextfieldControllerModule,\n    ],\n    templateUrl: './index.html',\n    styleUrl: './index.less',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class RealWorldForm {\n    /**\n     * https://github.com/taiga-family/maskito/pull/2165\n     * TODO: delete after bumping Safari support to 18+\n     */\n    protected readonly phonePattern =\n        isSafari(tuiInjectElement()) || inject(TUI_IS_IOS) ? '+[0-9-]{1,20}' : '';\n\n    protected readonly form = new FormGroup({\n        name: new FormControl(''),\n        surname: new FormControl(''),\n        phone: new FormControl(''),\n        password: new FormControl(''),\n        repeatedPassword: new FormControl(''),\n        transactionDate: new FormControl(''),\n        transactionAmount: new FormControl(''),\n        address: new FormControl(''),\n    });\n\n    protected nameMask: MaskitoOptions = {mask: ONLY_LATIN_LETTERS_RE};\n\n    protected surnameMask: MaskitoOptions = {\n        mask: ONLY_LATIN_LETTERS_RE,\n        postprocessors: [\n            ({value, selection}) => ({selection, value: value.toUpperCase()}),\n        ],\n    };\n\n    protected readonly phoneMask = maskitoPhoneOptionsGenerator({\n        metadata,\n        strict: false,\n    });\n\n    protected passwordMask: MaskitoOptions = {mask: /^\\d*(?:[a-z]\\d*)?$/i};\n\n    protected readonly transactionDateMask = maskitoDateOptionsGenerator({\n        mode: 'dd/mm/yyyy',\n    });\n\n    protected readonly transactionAmountMask: MaskitoOptions = {\n        ...MONEY_AMOUNT_MASK,\n        plugins: [\n            ...MONEY_AMOUNT_MASK.plugins,\n            maskitoAddOnFocusPlugin('$ '),\n            maskitoRemoveOnBlurPlugin('$ '),\n        ],\n    };\n\n    protected readonly addressMask: MaskitoOptions = {mask: /^[a-z1-9\\s.,/]+$/i};\n\n    protected showPassword = false;\n\n    protected get countryIsoCode(): string {\n        return maskitoGetCountryFromNumber(this.form.value.phone ?? '', metadata) ?? '';\n    }\n\n    protected log(something: any): void {\n        console.info(something);\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/supported-input-types/examples/password/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiHint} from '@taiga-ui/core';\nimport {TuiInputPasswordModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'input-type-password-example',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiHint,\n        TuiInputPasswordModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input-password\n            tuiHintContent=\"Only 3 digits are allowed\"\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            Enter password\n            <input\n                tuiTextfieldLegacy\n                type=\"password\"\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input-password>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class InputPasswordDocExample {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/supported-input-types/examples/password/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nexport default {mask: [/\\d/, /\\d/, /\\d/]} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/supported-input-types/examples/search/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'input-type-search-example',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldIconLeft=\"@tui.search\"\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            Enter any english word\n            <input\n                tuiTextfieldLegacy\n                type=\"search\"\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class InputSearchDocExample {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/supported-input-types/examples/search/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nexport default {mask: /^[a-z]+$/i} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/supported-input-types/examples/tel/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiFlagPipe} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'input-type-tel-example',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiFlagPipe,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldCustomContent]=\"usFlag\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            Enter phone number\n            <input\n                tuiTextfieldLegacy\n                type=\"tel\"\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n\n        <ng-template #usFlag>\n            <img\n                alt=\"Flag of the United States\"\n                width=\"28\"\n                [src]=\"'US' | tuiFlag\"\n                [style.border-radius.%]=\"50\"\n            />\n        </ng-template>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class InputTelDocExample {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/supported-input-types/examples/tel/mask.ts",
    "content": "import {maskitoPhoneOptionsGenerator} from '@maskito/phone';\nimport metadata from 'libphonenumber-js/metadata.min.json';\n\nexport default maskitoPhoneOptionsGenerator({\n    metadata,\n    countryIsoCode: 'US',\n});\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/supported-input-types/examples/text/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'input-type-text-example',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.clock\"\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            Enter time\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                type=\"text\"\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class InputTextDocExample {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/supported-input-types/examples/text/mask.ts",
    "content": "import {maskitoTimeOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoTimeOptionsGenerator({mode: 'HH:MM'});\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/supported-input-types/examples/url/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'input-type-url-example',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.globe\"\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            Enter url\n            <input\n                tuiTextfieldLegacy\n                type=\"url\"\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class InputURLDocExample {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/supported-input-types/examples/url/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nexport default {\n    // oversimplified version of url mask for demo purposes\n    mask: /^[\\w/:.@]+$/,\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/supported-input-types/supported-input-types.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {DocExamplePrimaryTab} from '@demo/constants';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\n\nimport {InputPasswordDocExample} from './examples/password/component';\nimport {InputSearchDocExample} from './examples/search/component';\nimport {InputTelDocExample} from './examples/tel/component';\nimport {InputTextDocExample} from './examples/text/component';\nimport {InputURLDocExample} from './examples/url/component';\n\n@Component({\n    selector: 'supported-input-types-doc-page',\n    imports: [\n        InputPasswordDocExample,\n        InputSearchDocExample,\n        InputTelDocExample,\n        InputTextDocExample,\n        InputURLDocExample,\n        TuiAddonDoc,\n        TuiLink,\n        TuiNotification,\n    ],\n    templateUrl: './supported-input-types.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class SupportedInputTypesDocPageComponent {\n    protected readonly textTypeExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/text/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly telTypeExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/tel/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly passwordTypeExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/password/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly urlTypeExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/url/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly searchTypeExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/search/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected getInput(type: HTMLInputElement['type']): string {\n        return `<input type=\"${type}\" />`;\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/supported-input-types/supported-input-types.template.html",
    "content": "<tui-doc-page header=\"Supported <input /> types\">\n    <tui-notification\n        appearance=\"warning\"\n        size=\"m\"\n    >\n        <div>\n            <strong>Maskito</strong>\n            supports only limited types of\n            <code>HTMLInputElement</code>\n            due to some browser limitations!\n        </div>\n    </tui-notification>\n\n    <section class=\"tui-space_top-4\">\n        <p>\n            <strong>Maskito</strong>\n            accepts only the types whose support the following native properties/methods:\n        </p>\n\n        <ul class=\"tui-list\">\n            <li class=\"tui-list__item\">\n                <code>selectionStart</code>\n            </li>\n            <li class=\"tui-list__item\">\n                <code>selectionEnd</code>\n            </li>\n            <li class=\"tui-list__item\">\n                <code>setSelectionRange</code>\n            </li>\n        </ul>\n\n        <p>\n            According to the\n            <a\n                href=\"https://html.spec.whatwg.org/multipage/input.html#concept-input-apply\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n                tuiLink\n            >\n                WHATWG forms spec\n            </a>\n            they apply only to inputs of types\n            <code>text</code>\n            ,\n            <code>search</code>\n            ,\n            <code>URL</code>\n            ,\n            <code>tel</code>\n            and\n            <code>password</code>\n            .\n            <br />\n            <strong>All other types will not work properly with Maskito!</strong>\n        </p>\n    </section>\n\n    <p>All examples below are demonstrations to see different supported types in action.</p>\n\n    <tui-doc-example\n        id=\"text\"\n        heading=\"text\"\n        [content]=\"textTypeExample\"\n        [description]=\"textDescription\"\n    >\n        <ng-template #textDescription>\n            <code>{{ getInput('text') }}</code>\n            is the default, the simplest and the most popular type of input-element.\n            <br />\n            Use it if you don't know which type to choose.\n        </ng-template>\n\n        <input-type-text-example />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"tel\"\n        heading=\"tel\"\n        [content]=\"telTypeExample\"\n        [description]=\"telDescription\"\n    >\n        <ng-template #telDescription>\n            <code>{{ getInput('tel') }}</code>\n            is a control for entering a telephone number.\n            <br />\n            Displays a telephone keypad in some devices with dynamic keypads.\n        </ng-template>\n\n        <input-type-tel-example />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"password\"\n        heading=\"password\"\n        [content]=\"passwordTypeExample\"\n        [description]=\"passwordDescription\"\n    >\n        <ng-template #passwordDescription>\n            <code>{{ getInput('password') }}</code>\n            is a single-line text field whose value is obscured.\n        </ng-template>\n\n        <input-type-password-example />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"url\"\n        heading=\"url\"\n        [content]=\"urlTypeExample\"\n        [description]=\"urlDescription\"\n    >\n        <ng-template #urlDescription>\n            <code>{{ getInput('url') }}</code>\n            is a field for entering a URL.\n            <br />\n            Looks like a text input, but has relevant keyboard in supporting browsers and devices with dynamic\n            keyboards.\n        </ng-template>\n\n        <input-type-url-example />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"search\"\n        heading=\"search\"\n        [content]=\"searchTypeExample\"\n        [description]=\"searchDescription\"\n    >\n        <ng-template #searchDescription>\n            <code>{{ getInput('search') }}</code>\n            is a single-line text field for entering search strings.\n            <br />\n            Displays a search icon instead of enter key on some devices with dynamic keypads.\n        </ng-template>\n\n        <input-type-search-example />\n    </tui-doc-example>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/transformer/examples/utility-in-action-demo.md",
    "content": "```ts\nimport {maskitoTransform} from '@maskito/core';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nconst maskitoOptions = maskitoNumberOptionsGenerator({\n  thousandSeparator: ' ',\n});\n\nconst definitelyValidValue = maskitoTransform('1000000', maskitoOptions);\n\nconsole.info(definitelyValidValue); // '1 000 000'\n\n// Framework agnostic way | index.ts\ninputElement.value = definitelyValidValue;\n\n// Angular way | app.component.ts\nthis.formControl.patchValue(definitelyValidValue);\n```\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/transformer/transformer.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {TuiAddonDoc} from '@taiga-ui/addon-doc';\nimport {TuiNotification} from '@taiga-ui/core';\n\nimport {NextStepsComponent} from '../next-steps/next-steps.component';\n\n@Component({\n    selector: 'transformer-doc-page',\n    imports: [NextStepsComponent, TuiAddonDoc, TuiNotification],\n    templateUrl: './transformer.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class TransformerDocPageComponent {\n    protected readonly utilityInActionDemo =\n        import('./examples/utility-in-action-demo.md');\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/transformer/transformer.template.html",
    "content": "<tui-doc-page\n    header=\"Transformer\"\n    package=\"CORE\"\n>\n    <section>\n        <p class=\"tui-space_top-0\">\n            <strong>Maskito</strong>\n            libraries were created to prevent user from typing invalid value.\n            <br />\n            <strong>Maskito</strong>\n            listens\n            <code>beforeinput</code>\n            and\n            <code>input</code>\n            events. Programmatic (by developer) changes of input's value don't trigger these events!\n        </p>\n\n        <tui-notification size=\"m\">\n            <div>\n                <strong>Maskito</strong>\n                is based on the assumption that developer is capable to programmatically patch input with\n                <u>valid</u>\n                value!\n            </div>\n        </tui-notification>\n\n        <p>\n            If you need to programmatically patch input's value but you are not sure that your value is valid (for\n            example, you get it from the server), you should use\n            <code>maskitoTransform</code>\n            utility .\n        </p>\n    </section>\n\n    <tui-doc-code [code]=\"utilityInActionDemo\" />\n\n    <next-steps />\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/what-is-maskito/what-is-maskito.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath} from '@demo/constants';\nimport {TuiAddonDoc} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiSurface, TuiTitle} from '@taiga-ui/core';\nimport {TuiAvatar} from '@taiga-ui/kit';\nimport {TuiCardLarge, TuiHeader} from '@taiga-ui/layout';\n\n@Component({\n    selector: 'what-is-maskito-doc-page',\n    imports: [\n        RouterLink,\n        TuiAddonDoc,\n        TuiAvatar,\n        TuiCardLarge,\n        TuiHeader,\n        TuiLink,\n        TuiSurface,\n        TuiTitle,\n    ],\n    templateUrl: './what-is-maskito.template.html',\n    styleUrl: './what-is-maskito.style.less',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class WhatIsMaskitoDocPageComponent {\n    protected readonly maskitoLibrariesDocPage = `/${DemoPath.MaskitoLibraries}`;\n    protected readonly coreConceptsOverviewDocPage = `/${DemoPath.CoreConceptsOverview}`;\n    protected readonly angularDocPage = `/${DemoPath.Angular}`;\n    protected readonly reactDocPage = `/${DemoPath.React}`;\n    protected readonly vueDocPage = `/${DemoPath.Vue}`;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/what-is-maskito/what-is-maskito.style.less",
    "content": ".cards {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: space-between;\n    gap: 2rem;\n\n    & [tuiCardLarge] {\n        flex: 1;\n        min-inline-size: 18rem;\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/documentation/what-is-maskito/what-is-maskito.template.html",
    "content": "<tui-doc-page header=\"What is Maskito?\">\n    <div>\n        <strong>Maskito</strong>\n        is a collection of libraries, built with TypeScript. It helps you to create an input mask which ensures that\n        users type values according to predefined format.\n    </div>\n\n    <p>\n        Core concepts of the libraries are simple but they provide flexible API to set any format you wish: numbers,\n        phone, date, credit card number etc.\n    </p>\n\n    <section class=\"tui-space_top-8\">\n        <h2>Why Maskito?</h2>\n\n        <ul class=\"tui-list\">\n            <li class=\"tui-list__item\">\n                <strong>Maskito</strong>\n                supports all user’s interactions with text fields: basic typing and deleting via keyboard, pasting,\n                dropping text inside with a pointer, browser autofill, predictive text from mobile native keyboard.\n            </li>\n            <li class=\"tui-list__item\">\n                <strong>Maskito</strong>\n                is robust. The whole project is developed with\n                <code>strict</code>\n                TypeScript mode. Our code is covered by hundreds of\n                <a\n                    href=\"https://www.cypress.io\"\n                    rel=\"noreferrer\"\n                    target=\"_blank\"\n                    tuiLink\n                >\n                    Cypress\n                </a>\n                tests.\n            </li>\n            <li class=\"tui-list__item\">Server Side Rendering and Shadow DOM support.</li>\n\n            <li class=\"tui-list__item\">\n                You can use it with\n                <code>HTMLInputElement</code>\n                /\n                <code>HTMLTextAreaElement</code>\n                or even with\n                <code>[contenteditable]</code>\n                element.\n            </li>\n\n            <li class=\"tui-list__item\">\n                <strong>Maskito</strong>\n                core is zero-dependency package. You can mask input in your vanilla JavaScript project. However, we have\n                separate packages for\n                <a\n                    tuiLink\n                    [routerLink]=\"angularDocPage\"\n                >\n                    Angular\n                </a>\n                ,\n                <a\n                    tuiLink\n                    [routerLink]=\"reactDocPage\"\n                >\n                    React\n                </a>\n                and\n                <a\n                    tuiLink\n                    [routerLink]=\"vueDocPage\"\n                >\n                    Vue\n                </a>\n                as well.\n            </li>\n\n            <li class=\"tui-list__item\">\n                <strong>Maskito</strong>\n                includes optional framework-agnostic package with configurable ready-to-use masks.\n            </li>\n        </ul>\n    </section>\n\n    <p>\n        No textfield with invalid value! Use Maskito.\n        <strong>Mask it!</strong>\n    </p>\n\n    <section class=\"tui-space_top-8\">\n        <h2>Learn about Maskito</h2>\n\n        <div class=\"cards\">\n            <a\n                tuiCardLarge\n                tuiHeader\n                tuiSurface=\"elevated\"\n                [routerLink]=\"maskitoLibrariesDocPage\"\n            >\n                <h1 tuiTitle>\n                    Explore libraries\n                    <span tuiSubtitle>\n                        Maskito consists of several libraries.\n                        <br />\n                        Explore them and learn how to install and use them.\n                    </span>\n                </h1>\n\n                <aside tuiAccessories>\n                    <tui-avatar\n                        appearance=\"secondary\"\n                        size=\"l\"\n                        src=\"@tui.layout-grid\"\n                    />\n                </aside>\n            </a>\n\n            <a\n                tuiCardLarge\n                tuiHeader\n                tuiSurface=\"elevated\"\n                [routerLink]=\"coreConceptsOverviewDocPage\"\n            >\n                <h1 tuiTitle>\n                    Core concepts\n                    <span tuiSubtitle>\n                        Learn about mask expression, preprocessors and postprocessors, overwrite mode etc.\n                    </span>\n                </h1>\n\n                <aside tuiAccessories>\n                    <tui-avatar\n                        appearance=\"secondary\"\n                        size=\"l\"\n                        src=\"@tui.settings\"\n                    />\n                </aside>\n            </a>\n        </div>\n    </section>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/angular-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath} from '@demo/constants';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\n\nimport {NestedDocExample1} from './examples/1-nested/component';\nimport {NestedDocExample2} from './examples/2-nested/component';\nimport {ProgrammaticallyDocExample3} from './examples/3-programmatically/component';\nimport {PipeDocExample4} from './examples/4-pipe/component';\nimport {UnmaskDocExample5} from './examples/5-custom-unmask-handler';\nimport {PatternDocExample6} from './examples/6-pattern/component';\n\n@Component({\n    selector: 'angular-doc-page',\n    imports: [\n        MaskitoDirective,\n        NestedDocExample1,\n        NestedDocExample2,\n        PatternDocExample6,\n        PipeDocExample4,\n        ProgrammaticallyDocExample3,\n        RouterLink,\n        TuiAddonDoc,\n        TuiLink,\n        TuiNotification,\n        UnmaskDocExample5,\n    ],\n    templateUrl: './angular-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class AngularDocPageComponent {\n    protected readonly coreConceptsOverviewDocPage = `/${DemoPath.CoreConceptsOverview}`;\n\n    protected readonly importMaskitoExample = import('./examples/import-maskito.md');\n\n    protected readonly basicDirectiveApproach =\n        import('./examples/basic-directive-approach.md');\n\n    protected readonly customInputExample = import('./examples/custom-input-example.md');\n\n    protected readonly nestedInputExample: Record<string, TuiRawLoaderContent> = {\n        TypeScript: import('./examples/1-nested/component.ts?raw', {\n            with: {loader: 'text'},\n        }),\n        Default: import('./examples/1-nested/template.html'),\n        Custom: import('./examples/2-nested/template.html'),\n    };\n\n    protected readonly programmaticallyExample: Record<string, TuiRawLoaderContent> = {\n        TypeScript: import('./examples/3-programmatically/component.ts?raw', {\n            with: {loader: 'text'},\n        }),\n        HTML: import('./examples/3-programmatically/template.html'),\n    };\n\n    protected readonly pipeExample: Record<string, TuiRawLoaderContent> = {\n        TypeScript: import('./examples/4-pipe/component.ts?raw', {\n            with: {loader: 'text'},\n        }),\n        HTML: import('./examples/4-pipe/template.html'),\n    };\n\n    protected readonly customUnmaskHandlerExample: Record<string, TuiRawLoaderContent> = {\n        'index.html': import('./examples/5-custom-unmask-handler/index.html'),\n        'index.ts': import('./examples/5-custom-unmask-handler/index.ts?raw', {\n            with: {loader: 'text'},\n        }),\n        'unmask.directive.ts': import(\n            './examples/5-custom-unmask-handler/unmask.directive.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly patternDirectiveExample: Record<string, TuiRawLoaderContent> = {\n        TypeScript: import('./examples/6-pattern/component.ts?raw', {\n            with: {loader: 'text'},\n        }),\n        HTML: import('./examples/6-pattern/template.html'),\n    };\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/angular-doc.template.html",
    "content": "<tui-doc-page\n    header=\"Angular\"\n    path=\"angular\"\n>\n    <ng-template pageTab=\"Overview\">\n        <code>&#64;maskito/angular</code>\n        is a light-weighted library to use\n        <strong>Maskito</strong>\n        in an Angular-way.\n\n        <tui-notification\n            appearance=\"warning\"\n            size=\"m\"\n            class=\"tui-space_top-6\"\n        >\n            <div>\n                <strong>Prerequisites</strong>\n                <p class=\"tui-space_bottom-0\">\n                    To get the most out of this guide, you should review the topic\n                    <a\n                        tuiLink\n                        [routerLink]=\"coreConceptsOverviewDocPage\"\n                    >\n                        \"Core Concepts\"\n                    </a>\n                    first.\n                </p>\n            </div>\n        </tui-notification>\n\n        <section class=\"tui-space_top-12\">\n            <h2>Write less code</h2>\n\n            <ul class=\"tui-list\">\n                <li class=\"tui-list__item\">\n                    <strong>No need to query element from DOM.</strong>\n                    Just pass all required options to\n                    <code>[maskito]</code>\n                    directive.\n                </li>\n                <li class=\"tui-list__item\">\n                    <strong>No need to worry about clean-ups.</strong>\n                    All created event listeners are automatically removed after element is detached from DOM.\n                </li>\n            </ul>\n        </section>\n\n        <section class=\"tui-space_top-12\">\n            <h2>Basic directive approach</h2>\n\n            <p>Use it when you have direct access to native input element.</p>\n\n            <tui-doc-code\n                filename=\"your.component.ts\"\n                [code]=\"basicDirectiveApproach\"\n            />\n        </section>\n\n        <section class=\"tui-space_top-12\">\n            <h2>Nested input element</h2>\n\n            <p>\n                Pass a predicate to\n                <strong>maskito</strong>\n                to find input element for you, if you do not have a direct access to it.\n            </p>\n\n            <tui-notification size=\"m\">\n                <div>\n                    By default\n                    <strong>maskito</strong>\n                    will try to find input/textarea by querying its host:\n                    <code>host.querySelector('input,textarea')</code>\n                    so that might be sufficient. Use custom predicate if you need custom logic.\n                </div>\n            </tui-notification>\n\n            <tui-doc-code\n                filename=\"your.component.ts\"\n                [code]=\"customInputExample\"\n            />\n        </section>\n\n        <tui-doc-example\n            id=\"custom-input\"\n            description=\"See querying nested input in action\"\n            heading=\"Custom input\"\n            [content]=\"nestedInputExample\"\n        >\n            <div [style.width.rem]=\"20\">\n                <tui-notification\n                    size=\"m\"\n                    class=\"tui-space_bottom-3\"\n                >\n                    <div>\n                        Default behavior is enough for\n                        <a\n                            href=\"https://github.com/taiga-family/taiga-ui\"\n                            tuiLink\n                        >\n                            Taiga UI\n                        </a>\n                        inputs\n                    </div>\n                </tui-notification>\n\n                <nested-doc-example-1 #example />\n\n                <tui-notification\n                    size=\"m\"\n                    class=\"tui-space_vertical-3\"\n                >\n                    Custom predicate is required if target input is not the first on in the DOM\n                </tui-notification>\n\n                <nested-doc-example-2\n                    [maskito]=\"example.nameMask\"\n                    [maskitoElement]=\"example.predicate\"\n                />\n            </div>\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"programmatically\"\n            heading=\"Set value programmatically\"\n            [content]=\"programmaticallyExample\"\n            [description]=\"programmaticallyDescription\"\n        >\n            <ng-template #programmaticallyDescription>\n                When directly on native input/textarea tag,\n                <code>MaskitoDirective</code>\n                formats value set programmatically with Angular forms.\n            </ng-template>\n            <programmatically-doc-example-3 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"pattern\"\n            description=\"Alternative way to apply mask on native input\"\n            heading=\"Pattern\"\n            [content]=\"patternDirectiveExample\"\n        >\n            <pattern-doc-example-6 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"pipe\"\n            description=\"Format arbitrary value with the same options\"\n            heading=\"Pipe\"\n            [content]=\"pipeExample\"\n        >\n            <pipe-doc-example-4 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"unmask\"\n            heading=\"Custom unmask handler\"\n            [content]=\"customUnmaskHandlerExample\"\n            [description]=\"customUnmaskHandlerDescription\"\n        >\n            <ng-template #customUnmaskHandlerDescription>\n                According to W3C specification, textfield value should always be only a\n                <code>string</code>\n                -type (not\n                <code>number</code>\n                -type, not\n                <code>object</code>\n                -type or etc.). However, you can sometimes need to store value without mask in Angular form control.\n                This example demonstrates how easily any Angular\n                <a\n                    href=\"https://angular.dev/api/forms/ControlValueAccessor\"\n                    target=\"_blank\"\n                    tuiLink\n                >\n                    Control Value Accessor\n                </a>\n                (default one or any custom one from a third-party UI Kit) can be monkey-patched to achieve this goal.\n            </ng-template>\n            <custom-unmask-handler-doc-example-5 />\n        </tui-doc-example>\n    </ng-template>\n\n    <ng-template pageTab=\"Setup\">\n        <section>\n            <ol class=\"tui-list tui-list_ordered\">\n                <li class=\"tui-list__item\">\n                    Install libraries\n                    <tui-doc-code\n                        code=\"npm install &#64;maskito/{core,angular}\"\n                        filename=\"/your/project/path>\"\n                    />\n                </li>\n                <li class=\"tui-list__item tui-space_top-8\">\n                    Import\n                    <code>MaskitoDirective</code>\n                    to your component / module\n\n                    <tui-doc-code\n                        filename=\"your.component.ts\"\n                        [code]=\"importMaskitoExample\"\n                    />\n                </li>\n            </ol>\n        </section>\n    </ng-template>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/1-nested/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {MaskitoElementPredicate, MaskitoOptions} from '@maskito/core';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\n@Component({\n    selector: 'nested-doc-example-1',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    templateUrl: './template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NestedDocExample1 {\n    protected value = '';\n\n    public readonly nameMask: MaskitoOptions = {\n        mask: /^[a-z\\s]+$/i,\n        postprocessors: [\n            ({value, selection}) => ({value: value.toUpperCase(), selection}),\n        ],\n    };\n\n    public readonly predicate: MaskitoElementPredicate = (element) =>\n        element.querySelector<HTMLInputElement>('tui-input input')!;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/1-nested/template.html",
    "content": "<tui-input\n    [maskito]=\"nameMask\"\n    [(ngModel)]=\"value\"\n>\n    Name on the card\n</tui-input>\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/2-nested/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {TuiLabel} from '@taiga-ui/core';\nimport {TuiCheckbox} from '@taiga-ui/kit';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\n@Component({\n    selector: 'nested-doc-example-2',\n    imports: [FormsModule, TuiCheckbox, TuiInputModule, TuiLabel],\n    templateUrl: './template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NestedDocExample2 {\n    protected show = false;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/2-nested/template.html",
    "content": "<!--\n    Maskito can be applied on this component, given the predicate\n    can properly target native input inside tui-input below\n-->\n<label\n    tuiLabel\n    class=\"tui-space_bottom-3\"\n>\n    <input\n        tuiCheckbox\n        type=\"checkbox\"\n        [(ngModel)]=\"show\"\n    />\n    Add card holder name\n</label>\n\n<tui-input\n    [disabled]=\"!show\"\n    [(ngModel)]=\"value\"\n>\n    Name on the card\n</tui-input>\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/3-programmatically/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormControl, ReactiveFormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\n@Component({\n    selector: 'programmatically-doc-example-3',\n    imports: [MaskitoDirective, ReactiveFormsModule],\n    templateUrl: './template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ProgrammaticallyDocExample3 {\n    protected readonly control = new FormControl('');\n\n    protected readonly maskito = maskitoNumberOptionsGenerator({\n        maximumFractionDigits: 2,\n    });\n\n    protected setValue(): void {\n        this.control.setValue(\n            '12345.6789', // This value will be formatted to \"12 345.67\"\n        );\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/3-programmatically/template.html",
    "content": "<input\n    [formControl]=\"control\"\n    [maskito]=\"maskito\"\n/>\n<button\n    type=\"button\"\n    (click)=\"setValue()\"\n>\n    Set 12345.6789\n</button>\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/4-pipe/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {MaskitoPipe} from '@maskito/angular';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\n@Component({\n    selector: 'pipe-doc-example-4',\n    imports: [MaskitoPipe],\n    templateUrl: './template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PipeDocExample4 {\n    protected value = 12345.67;\n\n    protected readonly options = maskitoNumberOptionsGenerator({\n        maximumFractionDigits: 2,\n    });\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/4-pipe/template.html",
    "content": "Balance: ${{ value | maskito: options }}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/5-custom-unmask-handler/index.html",
    "content": "<input\n    [maskito]=\"maskito\"\n    [stringifyHandler]=\"stringify\"\n    [unmaskHandler]=\"unmaskHandler\"\n    [(ngModel)]=\"value\"\n/>\n\n<p>\n    <strong>Control value:</strong>\n    <code>{{ value }}</code>\n</p>\n\n<button\n    type=\"button\"\n    (click)=\"value = 1234567.89\"\n>\n    Programmatically patch value\n</button>\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/5-custom-unmask-handler/index.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {\n    maskitoNumberOptionsGenerator,\n    type MaskitoNumberParams,\n    maskitoParseNumber,\n    maskitoStringifyNumber,\n} from '@maskito/kit';\n\nimport {UnmaskDirective} from './unmask.directive';\n\nconst NUMBER_PARAMS: MaskitoNumberParams = {\n    maximumFractionDigits: 2,\n    thousandSeparator: '.',\n    decimalSeparator: ',',\n};\n\n@Component({\n    selector: 'custom-unmask-handler-doc-example-5',\n    imports: [FormsModule, MaskitoDirective, UnmaskDirective],\n    templateUrl: './index.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class UnmaskDocExample5 {\n    /**\n     * Pay attention, this variable (form control value) always contains only NUMBER-type values.\n     * Despite it, textfield's value is always prettified formatted STRING.\n     */\n    protected value = 1000.42;\n\n    protected readonly maskito = maskitoNumberOptionsGenerator(NUMBER_PARAMS);\n\n    /**\n     * `maskitoParseNumber` is built-in utility to convert\n     * entered number (as prettified formatted STRING) to number-type value\n     */\n    protected readonly unmaskHandler = (x: string): number =>\n        maskitoParseNumber(x, NUMBER_PARAMS);\n\n    // `maskitoStringifyNumber` implements the reverse transformation\n    protected readonly stringify = (x: number): string =>\n        maskitoStringifyNumber(x, NUMBER_PARAMS);\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/5-custom-unmask-handler/unmask.directive.ts",
    "content": "import {type AfterViewInit, Directive, inject} from '@angular/core';\nimport {DefaultValueAccessor} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {maskitoTransform} from '@maskito/core';\nimport {identity} from 'rxjs';\n\n@Directive({\n    selector: '[maskito][unmaskHandler]',\n    inputs: ['unmaskHandler', 'stringifyHandler'],\n})\nexport class UnmaskDirective implements AfterViewInit {\n    private readonly accessor = inject(DefaultValueAccessor);\n    private readonly maskitoDirective = inject(MaskitoDirective);\n\n    public unmaskHandler: (value: string) => any = identity;\n\n    public stringifyHandler: (value: any) => string = (value) => {\n        const options = this.maskitoDirective.options();\n\n        return options ? maskitoTransform(String(value), options) : value;\n    };\n\n    public ngAfterViewInit(): void {\n        const originalOnChange = this.accessor.onChange.bind(this.accessor);\n        const originalWriteValue = this.accessor.writeValue.bind(this.accessor);\n\n        this.accessor.onChange = (value) => originalOnChange(this.unmaskHandler(value));\n        this.accessor.writeValue = (value) =>\n            originalWriteValue(this.stringifyHandler(value));\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/6-pattern/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoPattern} from '@maskito/angular';\n\n@Component({\n    selector: 'pattern-doc-example-6',\n    imports: [FormsModule, MaskitoPattern],\n    templateUrl: './template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PatternDocExample6 {\n    protected name = '';\n    protected cvc = '';\n\n    protected regExp = /^[a-z\\s]+$/i;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/6-pattern/template.html",
    "content": "<input\n    placeholder=\"Name on the card\"\n    [maskitoPattern]=\"regExp\"\n    [(ngModel)]=\"name\"\n/>\n\n<input\n    maskitoPattern=\"\\d{0,3}\"\n    placeholder=\"CVC\"\n    [(ngModel)]=\"cvc\"\n/>\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/basic-directive-approach.md",
    "content": "```ts\nimport {Component} from '@angular/core';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {MaskitoOptions} from '@maskito/core';\n\n@Component({\n  selector: 'your-component',\n  template: `\n    <input [maskito]=\"maskitoOptions\" />\n  `,\n  imports: [MaskitoDirective],\n})\nexport class YourComponent {\n  readonly maskitoOptions: MaskitoOptions = {\n    mask: /^\\d+$/,\n  };\n}\n```\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/custom-input-example.md",
    "content": "```ts\nimport {Component} from '@angular/core';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {MaskitoOptions, MaskitoElementPredicate} from '@maskito/core';\n\n@Component({\n  selector: 'your-component',\n  template: `\n    <custom-input-wrapper\n      [maskito]=\"maskitoOptions\"\n      [maskitoElement]=\"predicate\"\n    >\n      Using maskito with another library\n    </custom-input-wrapper>\n  `,\n  imports: [MaskitoDirective],\n})\nexport class YourComponent {\n  readonly maskitoOptions: MaskitoOptions = {\n    mask: /^\\d+$/,\n  };\n\n  readonly predicate: MaskitoElementPredicate = (element) => element.querySelector('input[id=\"my-input\"]')!;\n}\n```\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/angular/examples/import-maskito.md",
    "content": "```ts\nimport {Component} from '@angular/core';\nimport {MaskitoDirective} from '@maskito/angular';\n\n@Component({\n  // ...\n  imports: [\n    MaskitoDirective,\n    // ...\n  ],\n})\nexport class YourComponent {}\n```\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/examples/1-use-maskito-basic-usage/example.component.tsx",
    "content": "import {isPlatformBrowser} from '@angular/common';\nimport {ChangeDetectionStrategy, Component, ElementRef, inject, PLATFORM_ID} from '@angular/core';\nimport {createRoot} from 'react-dom/client';\n\nimport {App} from './useMaskitoBasicUsage';\n\n@Component({\n    selector: 'react-example-1',\n    template: '',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n    host: {\n        'comment-for-devtools': 'Everything inside this tag is really rendered by `react-dom` library',\n    },\n})\nexport class ReactExample1 {\n    constructor() {\n        if (isPlatformBrowser(inject(PLATFORM_ID))) {\n            createRoot(inject(ElementRef).nativeElement).render(<App />);\n        }\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/examples/1-use-maskito-basic-usage/useMaskitoBasicUsage.tsx",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {useMaskito} from '@maskito/react';\nimport type {ComponentType} from 'react';\n\nconst digitsOnlyMask: MaskitoOptions = {mask: /^\\d+$/};\n\nexport const App: ComponentType = () => {\n    const inputRef = useMaskito({options: digitsOnlyMask});\n\n    return (\n        <input\n            ref={inputRef}\n            placeholder=\"Enter a number\"\n        />\n    );\n};\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/examples/2-element-predicate/awesomeInput.tsx",
    "content": "import {forwardRef, type InputHTMLAttributes} from 'react';\n\nconst hiddenInputStyles = {display: 'none'};\n\nexport const AwesomeInput = forwardRef<HTMLDivElement, InputHTMLAttributes<HTMLInputElement>>((props, ref) => (\n    <div\n        id=\"awesome-input-wrapper\"\n        ref={ref}\n    >\n        <input style={hiddenInputStyles} />\n        <input\n            className=\"real-input\"\n            {...props}\n        />\n        <input style={hiddenInputStyles} />\n    </div>\n));\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/examples/2-element-predicate/example.component.tsx",
    "content": "import {isPlatformBrowser} from '@angular/common';\nimport {ChangeDetectionStrategy, Component, ElementRef, inject, PLATFORM_ID} from '@angular/core';\nimport {createRoot} from 'react-dom/client';\n\nimport {App} from '.';\n\n@Component({\n    selector: 'react-example-2',\n    template: '',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n    host: {\n        'comment-for-devtools': 'Everything inside this tag is really rendered by `react-dom` library',\n    },\n})\nexport class ReactExample2 {\n    constructor() {\n        if (isPlatformBrowser(inject(PLATFORM_ID))) {\n            createRoot(inject(ElementRef).nativeElement).render(<App />);\n        }\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/examples/2-element-predicate/index.tsx",
    "content": "import type {MaskitoElementPredicate} from '@maskito/core';\nimport {maskitoDateOptionsGenerator} from '@maskito/kit';\nimport {useMaskito} from '@maskito/react';\nimport type {ComponentType} from 'react';\n\nimport {AwesomeInput} from './awesomeInput';\n\nconst options = maskitoDateOptionsGenerator({mode: 'dd/mm/yyyy'});\n\nconst elementPredicate: MaskitoElementPredicate = (host) => host.querySelector<HTMLInputElement>('input.real-input')!;\n\nexport const App: ComponentType = () => {\n    const inputRef = useMaskito({options, elementPredicate});\n\n    return (\n        <AwesomeInput\n            ref={inputRef}\n            placeholder=\"Enter date\"\n        />\n    );\n};\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/examples/3-merge-ref/index.tsx",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {useMaskito} from '@maskito/react';\nimport {type ComponentType, useRef} from 'react';\n\nconst digitsOnlyMask: MaskitoOptions = {mask: /^\\d+$/};\n\nexport const App: ComponentType = () => {\n    const externalRef = useRef<HTMLInputElement | null>(null);\n    const maskitoRef = useMaskito({options: digitsOnlyMask});\n\n    return (\n        <input\n            ref={(node) => {\n                maskitoRef(node);\n                externalRef.current = node;\n            }}\n            placeholder=\"Enter a number\"\n        />\n    );\n};\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/examples/3-react-hook-form/index.tsx",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\nimport {useMaskito} from '@maskito/react';\nimport type {ComponentType} from 'react';\n// @ts-ignore\nimport {useForm} from 'react-hook-form';\n\nimport {withMaskitoRegister} from './with-maskito-register';\n\nconst options: MaskitoOptions = maskitoNumberOptionsGenerator({maximumFractionDigits: 2});\n\nexport const App: ComponentType = () => {\n    const maskitoRef = useMaskito({options});\n    const {register, watch} = useForm();\n\n    const value = watch('controlName');\n\n    console.info('[controlName]: ', value);\n\n    return (\n        <input\n            placeholder=\"Enter number\"\n            {...withMaskitoRegister(register('controlName'), maskitoRef)}\n        />\n    );\n};\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/examples/3-react-hook-form/with-maskito-register.ts",
    "content": "import type {RefCallback} from 'react';\n// @ts-ignore\nimport type {UseFormRegisterReturn} from 'react-hook-form';\n\nconst noop = async (): Promise<void> => {};\n\nexport const withMaskitoRegister = (\n    registerResult: UseFormRegisterReturn,\n    maskitoRef: RefCallback<HTMLElement | null>,\n): UseFormRegisterReturn & {onInput: UseFormRegisterReturn['onChange']} => {\n    const ref: RefCallback<HTMLElement | null> = (node): void => {\n        registerResult.ref(node);\n        maskitoRef(node);\n    };\n\n    return {\n        ...registerResult,\n        ref,\n        onInput: registerResult.onChange,\n        onChange: noop,\n    };\n};\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/examples/best-bad-practice.md",
    "content": "```ts\n// Best Practice ✅\nuseMaskito({\n  options: maskitoOptions,\n  elementPredicate: predicate,\n});\n\n// Anti-Pattern ❌\nuseMaskito({\n  options: {mask: /^.*$/},\n  elementPredicate: () => e.querySelector('input#my-input'),\n});\n```\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/examples/controlled-input.md",
    "content": "```tsx\nimport {useState} from 'react';\nimport {useMaskito} from '@maskito/react';\n\nconst digitsOnlyMask: MaskitoOptions = {\n  mask: /^\\d+$/,\n};\n\nfunction App() {\n  const inputRef = useMaskito({options: digitsOnlyMask});\n  const [value, setValue] = useState('');\n\n  // Use `onInput` handler to build controlled input\n  return (\n    <input\n      ref={inputRef}\n      value={value}\n      onInput={(e) => setValue(e.currentTarget.value)}\n    />\n  );\n}\n```\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/examples/merge-ref.md",
    "content": "```tsx\nimport {useMaskito} from '@maskito/react';\n\nconst options: MaskitoOptions = {\n  mask: /^\\d+$/,\n};\n\nfunction App() {\n  const anyExternalRef = useRef(null);\n  const maskitoRef = useMaskito({options});\n\n  return (\n    <input\n      ref={(node) => {\n        maskitoRef(node);\n        anyExternalRef.current = node;\n      }}\n    />\n  );\n}\n```\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/react-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath} from '@demo/constants';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\nimport {TuiTabs} from '@taiga-ui/kit';\n\nimport {ReactExample1} from './examples/1-use-maskito-basic-usage/example.component';\nimport {ReactExample2} from './examples/2-element-predicate/example.component';\n\n@Component({\n    selector: 'react-doc-page',\n    imports: [\n        ReactExample1,\n        ReactExample2,\n        RouterLink,\n        TuiAddonDoc,\n        TuiLink,\n        TuiNotification,\n        TuiTabs,\n    ],\n    templateUrl: './react-doc.template.html',\n    styleUrl: './react-doc.style.less',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class ReactDocPageComponent {\n    protected readonly coreConceptsOverviewDocPage = `/${DemoPath.CoreConceptsOverview}`;\n    protected readonly useMaskitoBasicUsage = import(\n        './examples/1-use-maskito-basic-usage/useMaskitoBasicUsage.tsx?raw',\n        {with: {loader: 'text'}}\n    );\n\n    protected readonly controlledInputDemo = import('./examples/controlled-input.md');\n    protected readonly mergeRefDemo = import('./examples/merge-ref.md');\n\n    protected readonly elementPredicateExample: Record<string, TuiRawLoaderContent> = {\n        'index.tsx': import('./examples/2-element-predicate/index.tsx?raw', {\n            with: {loader: 'text'},\n        }),\n        'awesome-input.tsx': import(\n            './examples/2-element-predicate/awesomeInput.tsx?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly reactHookFormExample: Record<string, TuiRawLoaderContent> = {\n        'index.tsx': import('./examples/3-react-hook-form/index.tsx?raw', {\n            with: {loader: 'text'},\n        }),\n        'with-maskito-register.ts': import(\n            './examples/3-react-hook-form/with-maskito-register.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly bestBadPractice = import('./examples/best-bad-practice.md');\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/react-doc.style.less",
    "content": ".no-tabs {\n    padding-block-start: 0;\n\n    ::ng-deep .t-example {\n        margin-block-start: 0;\n    }\n}\n\nsection {\n    margin-block-start: 3.5rem;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/react/react-doc.template.html",
    "content": "<tui-doc-page\n    header=\"React\"\n    path=\"react\"\n>\n    <p class=\"tui-space_top-0\">\n        <code>&#64;maskito/react</code>\n        is a light-weighted library to use\n        <strong>Maskito</strong>\n        in an React-way.\n    </p>\n\n    <tui-notification\n        appearance=\"warning\"\n        size=\"m\"\n    >\n        <div>\n            <strong>Prerequisites</strong>\n            <p class=\"tui-space_bottom-0\">\n                To get the most out of this guide, you should review the topic\n                <a\n                    tuiLink\n                    [routerLink]=\"coreConceptsOverviewDocPage\"\n                >\n                    \"Core Concepts\"\n                </a>\n                first.\n            </p>\n        </div>\n    </tui-notification>\n\n    <section id=\"getting-started\">\n        <h2>Getting Started</h2>\n\n        <p>Install libraries</p>\n\n        <tui-doc-code code=\"npm install &#64;maskito/{core,react}\" />\n\n        <p>\n            and use\n            <strong>Maskito</strong>\n        </p>\n\n        <tui-doc-code [code]=\"useMaskitoBasicUsage\" />\n\n        <p>See the result of above code example in action:</p>\n\n        <tui-doc-example class=\"no-tabs\">\n            <react-example-1 />\n        </tui-doc-example>\n    </section>\n\n    <section id=\"controlled-input\">\n        <h2>Controlled masked input</h2>\n\n        <p>\n            <strong>Maskito</strong>\n            core is developed as framework-agnostic library. It does not depend on any JS-framework's peculiarities. It\n            uses only native browser API. That is why we\n            <u>strongly recommends</u>\n            use native\n            <code>onInput</code>\n            instead of React-specific\n            <code>onChange</code>\n            event handler. Do not worry, both events works similarly! Read more about it in the\n            <a\n                href=\"https://react.dev/reference/react-dom/components/input#props\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n                tuiLink\n            >\n                official React documentation.\n            </a>\n        </p>\n\n        <tui-notification\n            size=\"m\"\n            class=\"tui-space_bottom-4\"\n        >\n            React-specific\n            <code>onChange</code>\n            is supported by\n            <strong>Maskito</strong>\n            too but usage of\n            <code>onInput</code>\n            handler is a more robust solution!\n        </tui-notification>\n\n        <tui-doc-code [code]=\"controlledInputDemo\" />\n    </section>\n\n    <section id=\"merge-refs\">\n        <h2>Merge Maskito ref with the third-party ref</h2>\n\n        <p>Do you need to use multiple hooks that return refs which both should be attached to the masked textfield?</p>\n\n        <p>\n            <strong>\n                Use\n                <a\n                    href=\"https://react.dev/reference/react-dom/components/common#ref-callback\"\n                    rel=\"noreferrer\"\n                    target=\"_blank\"\n                    tuiLink\n                >\n                    ref callback\n                </a>\n                !\n            </strong>\n        </p>\n\n        <tui-doc-code [code]=\"mergeRefDemo\" />\n    </section>\n\n    <section id=\"element-predicate\">\n        <h2>Query nested input element</h2>\n\n        <p>\n            Pass a predicate to\n            <code>elementPredicate</code>\n            to find input element for you, if you do not have a direct access to it. For example, you use component from\n            some UI Kit library.\n        </p>\n\n        <tui-notification size=\"m\">\n            <div>\n                By default\n                <strong>Maskito</strong>\n                will try to find input/textarea by querying its host:\n                <code>host.querySelector('input,textarea')</code>\n                so that might be sufficient. Use custom predicate if you need custom logic.\n            </div>\n        </tui-notification>\n\n        <tui-doc-example\n            [content]=\"elementPredicateExample\"\n            [style.padding-block-start.px]=\"0\"\n        >\n            <react-example-2 />\n        </tui-doc-example>\n    </section>\n\n    <section id=\"form-library-integration\">\n        <h2>Integration with third-party library for forms</h2>\n\n        <p>\n            There is not silver bullet how to integrate\n            <strong>Maskito</strong>\n            with\n            <u>any</u>\n            library for form-building. Explore all examples above – the provided knowledge about element predicate,\n            ref merging and\n            <code>OnInput</code>\n            event will help you a lot to achieve it.\n        </p>\n\n        <p>\n            This example demonstrates how to use\n            <strong>Maskito</strong>\n            with popular library\n            <a\n                href=\"https://react-hook-form.com\"\n                target=\"_blank\"\n                tuiLink\n            >\n                react-hook-form\n            </a>\n            .\n        </p>\n\n        <tui-tabs-with-more\n            #tabs\n            class=\"tabs\"\n        >\n            <button\n                *tuiItem\n                tuiTab\n                type=\"button\"\n            >\n                <tui-doc-tab src=\"assets/icons/react.svg\">index.ts</tui-doc-tab>\n            </button>\n            <button\n                *tuiItem\n                tuiTab\n                type=\"button\"\n            >\n                <tui-doc-tab src=\"assets/icons/typescript.svg\">with-maskito-register.ts</tui-doc-tab>\n            </button>\n        </tui-tabs-with-more>\n\n        @switch (tabs.activeItemIndex) {\n            @case (0) {\n                <tui-doc-code [code]=\"reactHookFormExample['index.tsx']!\" />\n            }\n            @case (1) {\n                <tui-doc-code [code]=\"reactHookFormExample['with-maskito-register.ts']!\" />\n            }\n        }\n    </section>\n\n    <section>\n        <h2>Best practices & Anti-Patterns</h2>\n\n        <p>\n            Pass named variables to avoid unnecessary hook runs with\n            <strong>Maskito</strong>\n            recreation:\n        </p>\n\n        <tui-doc-code [code]=\"bestBadPractice\" />\n    </section>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/vue/examples/best-bad-practice.md",
    "content": "```html\n<!-- Best Practice ✅-->\n<input v-maskito=\"options\" />\n\n<!-- Anti-Pattern ❌-->\n<input v-maskito=\"{ mask: /^\\d+$/ }\" />\n```\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/vue/examples/query-nested-input.md",
    "content": "```ts\nimport {createApp} from 'vue';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\nimport {maskito} from '@maskito/vue';\n\ncreateApp({\n  template: '<CustomInput v-model=\"value\" v-maskito=\"options\" />',\n  directives: {maskito},\n  data: () => ({\n    value: '123456',\n    options: {\n      ...maskitoNumberOptionsGenerator(),\n      elementPredicate: (host) => host.querySelector('input')!,\n    },\n  }),\n}).mount('#vue');\n```\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/vue/examples/use-maskito-basic-usage.md",
    "content": "```ts\nimport {createApp} from 'vue';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\nimport {maskito} from '@maskito/vue';\n\ncreateApp({\n  template: '<input v-model=\"value\" v-maskito=\"options\" />',\n  directives: {maskito},\n  data: () => ({\n    value: '123_456',\n    options: maskitoNumberOptionsGenerator({\n      thousandSeparator: '_',\n    }),\n  }),\n}).mount('#vue');\n```\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/vue/examples/vue-1/component.ts",
    "content": "import {afterNextRender, ChangeDetectionStrategy, Component} from '@angular/core';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\nimport {maskito} from '@maskito/vue';\n\n@Component({\n    selector: 'vue-example-1',\n    template: '<div id=\"vue\"></div>',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class VueExample1 {\n    protected readonly csrOnly = afterNextRender(async () =>\n        import('vue').then(({createApp}) => {\n            createApp({\n                template: '<input v-model=\"value\" v-maskito=\"options\" />',\n                directives: {maskito},\n                data: () => ({\n                    value: '123_456',\n                    options: maskitoNumberOptionsGenerator({thousandSeparator: '_'}),\n                }),\n            }).mount('#vue');\n        }),\n    );\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/vue/vue-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath} from '@demo/constants';\nimport {TuiAddonDoc} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\n\nimport {VueExample1} from './examples/vue-1/component';\n\n@Component({\n    selector: 'vue-doc-page',\n    imports: [RouterLink, TuiAddonDoc, TuiLink, TuiNotification, VueExample1],\n    templateUrl: './vue-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class VueDocPageComponent {\n    protected readonly coreConceptsOverviewDocPage = `/${DemoPath.CoreConceptsOverview}`;\n    protected readonly useMaskitoBasicUsage =\n        import('./examples/use-maskito-basic-usage.md');\n\n    protected readonly queryNestedInputDemo = import('./examples/query-nested-input.md');\n\n    protected readonly bestBadPractice = import('./examples/best-bad-practice.md');\n}\n"
  },
  {
    "path": "projects/demo/src/pages/frameworks/vue/vue-doc.template.html",
    "content": "<tui-doc-page\n    header=\"Vue\"\n    path=\"vue\"\n>\n    <p class=\"tui-space_top-0\">\n        <code>&#64;maskito/vue</code>\n        is a light-weighted library to use\n        <strong>Maskito</strong>\n        in as a Vue directive.\n    </p>\n\n    <tui-notification\n        appearance=\"warning\"\n        size=\"m\"\n        class=\"tui-space_top-6\"\n    >\n        <div>\n            <strong>Prerequisites</strong>\n            <p class=\"tui-space_bottom-0\">\n                To get the most out of this guide, you should review the topic\n                <a\n                    tuiLink\n                    [routerLink]=\"coreConceptsOverviewDocPage\"\n                >\n                    \"Core Concepts\"\n                </a>\n                first.\n            </p>\n        </div>\n    </tui-notification>\n\n    <section class=\"tui-space_top-12\">\n        <h2>Getting Started</h2>\n\n        <p>Install libraries</p>\n\n        <tui-doc-code code=\"npm install &#64;maskito/{core,vue}\" />\n\n        <p>\n            and use\n            <strong>Maskito</strong>\n        </p>\n\n        <tui-doc-code [code]=\"useMaskitoBasicUsage\" />\n    </section>\n\n    <tui-doc-example\n        id=\"example\"\n        heading=\"Above code example in practice\"\n    >\n        @defer {\n            <vue-example-1 />\n        } @placeholder {\n            <input value=\"123_456\" />\n        }\n    </tui-doc-example>\n\n    <section class=\"tui-space_top-12\">\n        <h2>Query nested input element</h2>\n\n        <p>\n            Pass a predicate to\n            <strong>elementPredicate</strong>\n            to find input element for you, if you do not have a direct access to it. For example, you use component from\n            some UI Kit library.\n        </p>\n\n        <tui-notification\n            size=\"m\"\n            class=\"tui-space_bottom-4\"\n        >\n            <div>\n                By default\n                <strong>Maskito</strong>\n                will try to find input/textarea by querying its host:\n                <code>host.querySelector('input,textarea')</code>\n                so that might be sufficient. Use custom predicate if you need custom logic.\n            </div>\n        </tui-notification>\n\n        <tui-doc-code [code]=\"queryNestedInputDemo\" />\n    </section>\n\n    <section class=\"tui-space_top-12\">\n        <h2>Best practices & Anti-Patterns</h2>\n\n        <p>Avoid inlining options object, otherwise Maskito will be recreated on every update:</p>\n\n        <tui-doc-code [code]=\"bestBadPractice\" />\n    </section>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date/date-mask-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormControl, ReactiveFormsModule} from '@angular/forms';\nimport {DocExamplePrimaryTab} from '@demo/constants';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {MaskitoOptions} from '@maskito/core';\nimport {\n    type MaskitoDateMode,\n    maskitoDateOptionsGenerator,\n    type MaskitoDateParams,\n} from '@maskito/kit';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport {DateMaskDocExample1} from './examples/1-localization/component';\nimport {DateMaskDocExample2} from './examples/2-min-max/component';\n\n@Component({\n    selector: 'date-mask-doc',\n    imports: [\n        DateMaskDocExample1,\n        DateMaskDocExample2,\n        MaskitoDirective,\n        ReactiveFormsModule,\n        TuiAddonDoc,\n        TuiInputModule,\n        TuiLink,\n        TuiNotification,\n        TuiTextfieldControllerModule,\n    ],\n    templateUrl: './date-mask-doc.template.html',\n    styleUrl: './date-mask-doc.style.less',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class DateMaskDocComponent implements Required<MaskitoDateParams> {\n    protected apiPageControl = new FormControl('');\n\n    protected readonly maskitoParseStringifyDateDemo =\n        import('./examples/maskito-parse-stringify-date-demo.md');\n\n    protected readonly dateLocalization: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/1-localization/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly dateMinMax: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/2-min-max/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly modeOptions = [\n        'dd/mm/yyyy',\n        'mm/dd/yyyy',\n        'yyyy/mm/dd',\n        'dd/mm',\n        'mm/dd',\n        'mm/yy',\n        'mm/yyyy',\n        'yyyy/mm',\n        'yyyy',\n    ] as const satisfies readonly MaskitoDateMode[];\n\n    protected readonly separatorOptions = ['.', '/', '-'] as const;\n    protected readonly minMaxOptions = [\n        '0001-01-01',\n        '9999-12-31',\n        '2000-01-01',\n        '2025-05-10',\n    ] as const;\n\n    protected minStr: string = this.minMaxOptions[0];\n    protected maxStr: string = this.minMaxOptions[1];\n    public mode: MaskitoDateMode = this.modeOptions[0];\n    public separator: string = this.separatorOptions[0];\n    public min = new Date(this.minStr);\n    public max = new Date(this.maxStr);\n    public maskitoOptions: MaskitoOptions = maskitoDateOptionsGenerator(this);\n\n    protected updateDate(): void {\n        this.min = new Date(this.minStr);\n        this.max = new Date(this.maxStr);\n        this.updateOptions();\n    }\n\n    protected updateOptions(): void {\n        this.maskitoOptions = maskitoDateOptionsGenerator(this);\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date/date-mask-doc.style.less",
    "content": ".input-date {\n    max-inline-size: 25rem;\n\n    &:not(:last-child) {\n        margin-block-end: 1rem;\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date/date-mask-doc.template.html",
    "content": "<tui-doc-page\n    header=\"Date\"\n    package=\"KIT\"\n>\n    <ng-template pageTab>\n        Use\n        <code>maskitoDateOptionsGenerator</code>\n        to create a mask for date input.\n\n        <tui-notification\n            size=\"m\"\n            class=\"tui-space_top-4\"\n        >\n            <div>\n                Despite the name of the mask, element's raw value is still string.\n\n                <p>\n                    Use\n                    <code>maskitoParseDate</code>\n                    to get date from masked string.\n                </p>\n                <p>\n                    Use\n                    <code>maskitoStringifyDate</code>\n                    to get the masked string from date.\n                </p>\n\n                <tui-doc-code [code]=\"maskitoParseStringifyDateDemo\" />\n            </div>\n        </tui-notification>\n\n        <tui-doc-example\n            id=\"date-localization\"\n            heading=\"Date localization\"\n            [content]=\"dateLocalization\"\n            [description]=\"dateLocalizationDescription\"\n        >\n            <ng-template #dateLocalizationDescription>\n                Use\n                <code>mode</code>\n                and\n                <code>separator</code>\n                properties to get a mask with a locale specific representation of dates.\n            </ng-template>\n\n            <date-mask-doc-example-1 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"min-max\"\n            heading=\"Min/Max\"\n            [content]=\"dateMinMax\"\n            [description]=\"minMaxDescription\"\n        >\n            <ng-template #minMaxDescription>\n                Properties\n                <code>min</code>\n                and\n                <code>max</code>\n                allow you to set the earliest and the latest available dates. They accept native\n                <a\n                    href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date\"\n                    rel=\"noreferrer\"\n                    target=\"_blank\"\n                    tuiLink\n                >\n                    Date\n                </a>\n                .\n            </ng-template>\n\n            <date-mask-doc-example-2 />\n        </tui-doc-example>\n    </ng-template>\n\n    <ng-template pageTab>\n        <tui-doc-demo [control]=\"apiPageControl\">\n            <ng-template>\n                <tui-input\n                    tuiTextfieldCustomContent=\"@tui.calendar\"\n                    class=\"input-date\"\n                    [formControl]=\"apiPageControl\"\n                >\n                    Enter date\n                    <input\n                        inputmode=\"numeric\"\n                        tuiTextfieldLegacy\n                        [maskito]=\"maskitoOptions\"\n                    />\n                </tui-input>\n            </ng-template>\n        </tui-doc-demo>\n        <tui-doc-documentation>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"mode\"\n                documentationPropertyType=\"MaskitoDateMode\"\n                [documentationPropertyValues]=\"modeOptions\"\n                [(documentationPropertyValue)]=\"mode\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Date format mode\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"separator\"\n                documentationPropertyType=\"string\"\n                [documentationPropertyValues]=\"separatorOptions\"\n                [(documentationPropertyValue)]=\"separator\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Symbol for separating date-segments (days, months, years)\n\n                <p class=\"tui-space_bottom-0\">\n                    <strong>Default:</strong>\n                    <code>.</code>\n                    (dot)\n                </p>\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"min\"\n                documentationPropertyType=\"Date\"\n                [documentationPropertyValues]=\"minMaxOptions\"\n                [(documentationPropertyValue)]=\"minStr\"\n                (documentationPropertyValueChange)=\"updateDate()\"\n            >\n                Earliest date\n\n                <p class=\"tui-space_bottom-0\">\n                    <strong>Default:</strong>\n                    <code>new Date('0001-01-01')</code>\n                </p>\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"max\"\n                documentationPropertyType=\"Date\"\n                [documentationPropertyValues]=\"minMaxOptions\"\n                [(documentationPropertyValue)]=\"maxStr\"\n                (documentationPropertyValueChange)=\"updateDate()\"\n            >\n                Latest date\n\n                <p class=\"tui-space_bottom-0\">\n                    <strong>Default:</strong>\n                    <code>new Date('9999-12-31')</code>\n                </p>\n            </ng-template>\n        </tui-doc-documentation>\n    </ng-template>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date/examples/1-localization/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'date-mask-doc-example-1',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.calendar\"\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldFiller]=\"filler\"\n            [(ngModel)]=\"value\"\n        >\n            Localization\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DateMaskDocExample1 {\n    protected value = '2005/10/21';\n    protected readonly filler = 'yyyy/mm/dd';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date/examples/1-localization/mask.ts",
    "content": "import {maskitoDateOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoDateOptionsGenerator({mode: 'yyyy/mm/dd', separator: '/'});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date/examples/2-min-max/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'date-mask-doc-example-2',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.calendar\"\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldFiller]=\"filler\"\n            [(ngModel)]=\"value\"\n        >\n            Date\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DateMaskDocExample2 {\n    protected value = '20.01.2023';\n    protected readonly filler = 'dd.mm.yyyy';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date/examples/2-min-max/mask.ts",
    "content": "import {maskitoDateOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoDateOptionsGenerator({\n    mode: 'dd/mm/yyyy',\n    min: new Date(2000, 0, 1),\n    max: new Date(2025, 4, 10),\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date/examples/maskito-parse-stringify-date-demo.md",
    "content": "```ts\nimport {maskitoParseDate, maskitoStringifyDate, MaskitoDateParams} from '@maskito/kit';\n\nconst params: MaskitoDateParams = {\n  mode: 'dd/mm/yyyy',\n  separator: '/', // default is '.'\n};\n\nmaskitoParseDate('05/02/2004', params); // returns Date object\n\nmaskitoStringifyDate(new Date('2004-02-05'), params); // '05/02/2004'\n```\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-range/date-range-mask-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormControl, ReactiveFormsModule} from '@angular/forms';\nimport {DocExamplePrimaryTab} from '@demo/constants';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {MaskitoOptions} from '@maskito/core';\nimport {\n    type MaskitoDateMode,\n    maskitoDateRangeOptionsGenerator,\n    type MaskitoDateSegments,\n} from '@maskito/kit';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {tuiPure} from '@taiga-ui/cdk';\nimport {TuiLink} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport {DateRangeMaskDocExample1} from './examples/1-date-localization/component';\nimport {DateRangeMaskDocExample2} from './examples/2-min-max/component';\nimport {DateRangeMaskDocExample3} from './examples/3-min-max-length/component';\nimport {DateRangeMaskDocExample4} from './examples/4-range-separator/component';\n\ntype GeneratorOptions = Required<\n    NonNullable<Parameters<typeof maskitoDateRangeOptionsGenerator>[0]>\n>;\n\n@Component({\n    selector: 'date-range-mask-doc',\n    imports: [\n        DateRangeMaskDocExample1,\n        DateRangeMaskDocExample2,\n        DateRangeMaskDocExample3,\n        DateRangeMaskDocExample4,\n        MaskitoDirective,\n        ReactiveFormsModule,\n        TuiAddonDoc,\n        TuiInputModule,\n        TuiLink,\n        TuiTextfieldControllerModule,\n    ],\n    templateUrl: './date-range-mask-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class DateRangeMaskDocComponent implements GeneratorOptions {\n    protected readonly dateLocalizationExample1: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/1-date-localization/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly minMaxExample2: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/2-min-max/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly minMaxLengthExample3: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/3-min-max-length/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly customRangeExample4: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/4-range-separator/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected apiPageControl = new FormControl('');\n\n    protected readonly modeOptions = [\n        'dd/mm/yyyy',\n        'mm/dd/yyyy',\n        'yyyy/mm/dd',\n        'mm/yy',\n        'mm/yyyy',\n        'yyyy/mm',\n        'yyyy',\n    ] as const satisfies readonly MaskitoDateMode[];\n\n    protected readonly minMaxOptions = [\n        '0001-01-01',\n        '9999-12-31',\n        '2000-01-01',\n        '2025-05-10',\n    ] as const;\n\n    protected readonly minLengthOptions: Array<Partial<MaskitoDateSegments<number>>> = [\n        {day: 3},\n        {day: 15},\n        {month: 1},\n        {month: 1, day: 1},\n    ];\n\n    protected readonly maxLengthOptions: Array<Partial<MaskitoDateSegments<number>>> = [\n        {day: 5},\n        {month: 1},\n        {year: 1},\n    ];\n\n    protected minStr: string = this.minMaxOptions[0];\n    protected maxStr: string = this.minMaxOptions[1];\n    public mode: MaskitoDateMode = this.modeOptions[0];\n    public min = new Date(this.minStr);\n    public max = new Date(this.maxStr);\n    public minLength: Partial<MaskitoDateSegments<number>> = {};\n    public maxLength: Partial<MaskitoDateSegments<number>> = {};\n    public dateSeparator = '.';\n    public rangeSeparator = ' – ';\n    public maskitoOptions: MaskitoOptions = maskitoDateRangeOptionsGenerator(this);\n\n    @tuiPure\n    protected getPlaceholder(\n        mode: MaskitoDateMode,\n        dateSeparator: string,\n        rangeSeparator: string,\n    ): string {\n        const datePlaceholder = mode.replaceAll('/', dateSeparator);\n\n        return `${datePlaceholder}${rangeSeparator}${datePlaceholder}`;\n    }\n\n    protected updateOptions(): void {\n        this.maskitoOptions = maskitoDateRangeOptionsGenerator(this);\n    }\n\n    protected updateDate(): void {\n        this.min = new Date(this.minStr);\n        this.max = new Date(this.maxStr);\n        this.updateOptions();\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-range/date-range-mask-doc.template.html",
    "content": "<tui-doc-page\n    header=\"DateRange\"\n    package=\"KIT\"\n>\n    <ng-template pageTab>\n        Use\n        <code>maskitoDateRangeOptionsGenerator</code>\n        to create a mask to input a range of dates.\n\n        <tui-doc-example\n            id=\"date-localization\"\n            heading=\"Date localization\"\n            [content]=\"dateLocalizationExample1\"\n            [description]=\"dateLocalizationDescription\"\n        >\n            <ng-template #dateLocalizationDescription>\n                Use\n                <code>mode</code>\n                and\n                <code>dateSeparator</code>\n                parameters to get a mask with a locale specific representation of dates.\n            </ng-template>\n            <date-range-mask-doc-example-1 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"min-max\"\n            heading=\"Min and max dates\"\n            [content]=\"minMaxExample2\"\n            [description]=\"minMaxDescription\"\n        >\n            <ng-template #minMaxDescription>\n                Parameters\n                <code>min</code>\n                and\n                <code>max</code>\n                allow you to set the earliest and the latest available dates. They accept native\n                <a\n                    href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date\"\n                    rel=\"noreferrer\"\n                    target=\"_blank\"\n                    tuiLink\n                >\n                    Date\n                </a>\n                .\n            </ng-template>\n            <date-range-mask-doc-example-2 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"min-max-length\"\n            heading=\"Min and max length of range\"\n            [content]=\"minMaxLengthExample3\"\n            [description]=\"minMaxLengthDescription\"\n        >\n            <ng-template #minMaxLengthDescription>\n                Use\n                <code>minLength</code>\n                and\n                <code>maxLength</code>\n                parameters to set minimal and maximal length of the date range.\n            </ng-template>\n            <date-range-mask-doc-example-3 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"custom-range-separator\"\n            heading=\"Custom range separator\"\n            [content]=\"customRangeExample4\"\n            [description]=\"customRangeSeparatorDescription\"\n        >\n            <ng-template #customRangeSeparatorDescription>\n                Use\n                <code>rangeSeparator</code>\n                parameter to customize separator between dates of the date range.\n            </ng-template>\n            <date-range-mask-doc-example-4 />\n        </tui-doc-example>\n    </ng-template>\n\n    <ng-template pageTab>\n        <tui-doc-demo [control]=\"apiPageControl\">\n            <ng-template>\n                <tui-input\n                    tuiTextfieldCustomContent=\"@tui.calendar\"\n                    [formControl]=\"apiPageControl\"\n                    [tuiTextfieldFiller]=\"getPlaceholder(mode, dateSeparator, rangeSeparator)\"\n                >\n                    Enter dates\n                    <input\n                        inputmode=\"numeric\"\n                        tuiTextfieldLegacy\n                        [maskito]=\"maskitoOptions\"\n                    />\n                </tui-input>\n            </ng-template>\n        </tui-doc-demo>\n        <tui-doc-documentation>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"mode\"\n                documentationPropertyType=\"MaskitoDateMode\"\n                [documentationPropertyValues]=\"modeOptions\"\n                [(documentationPropertyValue)]=\"mode\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Date format mode\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"dateSeparator\"\n                documentationPropertyType=\"string\"\n                [(documentationPropertyValue)]=\"dateSeparator\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Separator between date segments (days, months and years).\n\n                <p>\n                    <strong>Default:</strong>\n                    <code>.</code>\n                    (dot).\n                </p>\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"rangeSeparator\"\n                documentationPropertyType=\"string\"\n                [(documentationPropertyValue)]=\"rangeSeparator\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Separator between dates of the date range.\n                <p>\n                    <strong>Default:</strong>\n                    <code> – </code>\n                </p>\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"min\"\n                documentationPropertyType=\"Date\"\n                [documentationPropertyValues]=\"minMaxOptions\"\n                [(documentationPropertyValue)]=\"minStr\"\n                (documentationPropertyValueChange)=\"updateDate()\"\n            >\n                Earliest date\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"max\"\n                documentationPropertyType=\"Date\"\n                [documentationPropertyValues]=\"minMaxOptions\"\n                [(documentationPropertyValue)]=\"maxStr\"\n                (documentationPropertyValueChange)=\"updateDate()\"\n            >\n                Latest date\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"minLength\"\n                documentationPropertyType=\"MaskitoDateSegments<number>\"\n                [documentationPropertyValues]=\"minLengthOptions\"\n                [(documentationPropertyValue)]=\"minLength\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Minimal length of the range\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"maxLength\"\n                documentationPropertyType=\"MaskitoDateSegments<number>\"\n                [documentationPropertyValues]=\"maxLengthOptions\"\n                [(documentationPropertyValue)]=\"maxLength\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Maximal length of the range\n            </ng-template>\n        </tui-doc-documentation>\n    </ng-template>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-range/examples/1-date-localization/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiHint} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'date-range-mask-doc-example-1',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiHint,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.calendar\"\n            [style.max-width.rem]=\"30\"\n            [tuiHintContent]=\"hint\"\n            [tuiTextfieldFiller]=\"filler\"\n            [(ngModel)]=\"value\"\n        >\n            US format\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DateRangeMaskDocExample1 {\n    private readonly usDateFormatter = new Intl.DateTimeFormat('en-US', {\n        month: 'long',\n        day: 'numeric',\n        year: 'numeric',\n    });\n\n    protected value = '09/20/2020 – 02/06/2023';\n    protected readonly filler = 'mm/dd/yyyy – mm/dd/yyyy';\n    protected readonly mask = mask;\n\n    protected get hint(): string {\n        return this.value.length < this.filler.length\n            ? 'Complete the date range!'\n            : this.value\n                  .split(' – ')\n                  .map((date) => this.usDateFormatter.format(new Date(date)))\n                  .join(' – ');\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-range/examples/1-date-localization/mask.ts",
    "content": "import {maskitoDateRangeOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoDateRangeOptionsGenerator({\n    mode: 'mm/dd/yyyy',\n    dateSeparator: '/',\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-range/examples/2-min-max/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'date-range-mask-doc-example-2',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.calendar\"\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldFiller]=\"filler\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DateRangeMaskDocExample2 {\n    protected value = '19.11.1711 – 15.04.1765';\n    protected readonly filler = 'dd.mm.yyyy – dd.mm.yyyy';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-range/examples/2-min-max/mask.ts",
    "content": "import {maskitoDateRangeOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoDateRangeOptionsGenerator({\n    mode: 'dd/mm/yyyy',\n    min: new Date('1711-11-19'),\n    max: new Date('1765-04-15'),\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-range/examples/3-min-max-length/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiHint} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'date-range-mask-doc-example-3',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiHint,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.calendar\"\n            [style.max-width.rem]=\"30\"\n            [tuiHintContent]=\"hint\"\n            [tuiTextfieldFiller]=\"filler\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DateRangeMaskDocExample3 {\n    protected value = '01.01.2023 – 05.01.2023';\n    protected readonly filler = 'dd.mm.yyyy – dd.mm.yyyy';\n    protected readonly mask = mask;\n    protected readonly hint =\n        'The right date must be at least 3 days after the left one.\\nAlso, the difference between the dates must not exceed 1 month.';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-range/examples/3-min-max-length/mask.ts",
    "content": "import {maskitoDateRangeOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoDateRangeOptionsGenerator({\n    mode: 'dd/mm/yyyy',\n    minLength: {day: 3},\n    maxLength: {month: 1},\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-range/examples/4-range-separator/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'date-range-mask-doc-example-4',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.calendar\"\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldFiller]=\"filler\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DateRangeMaskDocExample4 {\n    protected value = '01.01.2023 ~ 05.01.2023';\n    protected readonly filler = 'dd.mm.yyyy ~ dd.mm.yyyy';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-range/examples/4-range-separator/mask.ts",
    "content": "import {maskitoDateRangeOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoDateRangeOptionsGenerator({\n    mode: 'dd/mm/yyyy',\n    rangeSeparator: ' ~ ',\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-time/date-time-mask-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormControl, ReactiveFormsModule} from '@angular/forms';\nimport {DocExamplePrimaryTab} from '@demo/constants';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {MaskitoOptions} from '@maskito/core';\nimport {\n    type MaskitoDateMode,\n    maskitoDateTimeOptionsGenerator,\n    type MaskitoDateTimeParams,\n    type MaskitoTimeMode,\n} from '@maskito/kit';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {tuiPure} from '@taiga-ui/cdk';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport {DateTimeMaskDocExample1} from './examples/1-date-time-localization/component';\nimport {DateTimeMaskDocExample2} from './examples/2-date-time-separator/component';\nimport {DateTimeMaskDocExample3} from './examples/3-min-max/component';\nimport {DateTimeMaskDocExample4} from './examples/4-time-step/component';\nimport {DateTimeMaskDocExample5} from './examples/5-am-pm/component';\n\n@Component({\n    selector: 'date-time-mask-doc',\n    imports: [\n        DateTimeMaskDocExample1,\n        DateTimeMaskDocExample2,\n        DateTimeMaskDocExample3,\n        DateTimeMaskDocExample4,\n        DateTimeMaskDocExample5,\n        MaskitoDirective,\n        ReactiveFormsModule,\n        TuiAddonDoc,\n        TuiInputModule,\n        TuiLink,\n        TuiNotification,\n        TuiTextfieldControllerModule,\n    ],\n    templateUrl: './date-time-mask-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class DateTimeMaskDocComponent implements Required<MaskitoDateTimeParams> {\n    protected readonly maskitoParseStringifyDateTimeDemo =\n        import('./examples/maskito-parse-stringify-date-time-demo.md');\n\n    protected readonly dateTimeLocalizationExample: Record<string, TuiRawLoaderContent> =\n        {\n            [DocExamplePrimaryTab.MaskitoOptions]: import(\n                './examples/1-date-time-localization/mask.ts?raw',\n                {with: {loader: 'text'}}\n            ),\n        };\n\n    protected readonly dateTimeSeparatorExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/2-date-time-separator/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly dateTimeMinMaxExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/3-min-max/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly dateTimeTimeStepExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/4-time-step/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly amPmExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/5-am-pm/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected apiPageControl = new FormControl('');\n\n    protected readonly dateModeOptions = [\n        'dd/mm/yyyy',\n        'mm/dd/yyyy',\n        'yyyy/mm/dd',\n    ] as const satisfies readonly MaskitoDateMode[];\n\n    protected readonly timeModeOptions = [\n        'HH:MM',\n        'HH:MM AA',\n        'HH:MM:SS',\n        'HH:MM:SS AA',\n        'HH:MM:SS.MSS',\n        'HH:MM:SS.MSS AA',\n    ] as const satisfies readonly MaskitoTimeMode[];\n\n    protected readonly minMaxOptions = [\n        '0001-01-01T00:00:00',\n        '9999-12-31T23:59:59',\n        '2000-01-01T12:30',\n        '2025-05-10T18:30',\n    ] as const;\n\n    protected minStr: string = this.minMaxOptions[0];\n    protected maxStr: string = this.minMaxOptions[1];\n    public dateMode: MaskitoDateMode = this.dateModeOptions[0];\n    public timeMode: MaskitoTimeMode = this.timeModeOptions[0];\n    public dateTimeSeparator = ', ';\n    public dateSeparator = '.';\n    public min = new Date(this.minStr);\n    public max = new Date(this.maxStr);\n    public timeStep = 0;\n    public maskitoOptions: MaskitoOptions = maskitoDateTimeOptionsGenerator(this);\n\n    @tuiPure\n    protected getPlaceholder(\n        dateMode: MaskitoDateMode,\n        timeMode: MaskitoTimeMode,\n        separator: string,\n        dateTimeSeparator: string,\n    ): string {\n        return `${dateMode.replaceAll('/', separator)}${dateTimeSeparator}${timeMode}`;\n    }\n\n    protected updateOptions(): void {\n        this.maskitoOptions = maskitoDateTimeOptionsGenerator(this);\n    }\n\n    protected updateDate(): void {\n        this.min = new Date(this.minStr);\n        this.max = new Date(this.maxStr);\n        this.updateOptions();\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-time/date-time-mask-doc.template.html",
    "content": "<tui-doc-page\n    header=\"DateTime\"\n    package=\"KIT\"\n>\n    <ng-template pageTab>\n        Use\n        <code>maskitoDateTimeOptionsGenerator</code>\n        to create a mask to input both date and time.\n\n        <tui-notification\n            size=\"m\"\n            class=\"tui-space_top-4\"\n        >\n            <div>\n                Despite the name of the mask, element's raw value is still string.\n\n                <p>\n                    Use\n                    <code>maskitoParseDateTime</code>\n                    to get date from masked string.\n                </p>\n                <p>\n                    Use\n                    <code>maskitoStringifyDateTime</code>\n                    to get the masked string from date.\n                </p>\n\n                <tui-doc-code [code]=\"maskitoParseStringifyDateTimeDemo\" />\n            </div>\n        </tui-notification>\n\n        <tui-doc-example\n            id=\"localization\"\n            heading=\"Localization\"\n            [content]=\"dateTimeLocalizationExample\"\n            [description]=\"localizationDescription\"\n        >\n            <ng-template #localizationDescription>\n                Use\n                <code>dateMode</code>\n                ,\n                <code>timeMode</code>\n                and\n                <code>dateSeparator</code>\n                parameters to get a mask with a locale specific representation of dates.\n            </ng-template>\n            <date-time-mask-doc-example-1 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"date-time-separator\"\n            heading=\"Custom separator between date and time\"\n            [content]=\"dateTimeSeparatorExample\"\n            [description]=\"dateTimeSeparatorDescription\"\n        >\n            <ng-template #dateTimeSeparatorDescription>\n                Use\n                <code>dateTimeSeparator</code>\n                parameter to configure separator between date and time strings.\n            </ng-template>\n            <date-time-mask-doc-example-2 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"min-max\"\n            heading=\"Min and max\"\n            [content]=\"dateTimeMinMaxExample\"\n            [description]=\"minMaxDescription\"\n        >\n            <ng-template #minMaxDescription>\n                Parameters\n                <code>min</code>\n                and\n                <code>max</code>\n                allow to set the earliest and the latest available dates. They accept native\n                <a\n                    href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date\"\n                    rel=\"noreferrer\"\n                    target=\"_blank\"\n                    tuiLink\n                >\n                    Date\n                </a>\n                .\n            </ng-template>\n            <date-time-mask-doc-example-3 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"time-step\"\n            heading=\"Time segments stepping via arrows\"\n            [content]=\"dateTimeTimeStepExample\"\n            [description]=\"timeStepDescription\"\n        >\n            <ng-template #timeStepDescription>\n                <p class=\"tui-space_top-0 tui-space_bottom-1\">\n                    Property\n                    <code>timeStep</code>\n                    allows you to increment / decrement time segments by pressing\n                    <code>ArrowUp</code>\n                    /\n                    <code>ArrowDown</code>\n                    .\n                </p>\n\n                <p class=\"tui-space_top-0\">\n                    Use\n                    <code>step === 0</code>\n                    (default value) to disable this feature.\n                </p>\n            </ng-template>\n            <date-time-mask-doc-example-4 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"am-pm\"\n            heading=\"AM / PM\"\n            [content]=\"amPmExample\"\n            [description]=\"amPmDescription\"\n        >\n            <ng-template #amPmDescription>\n                Any\n                <code>timeMode</code>\n                ending with\n                <code>AA</code>\n                is 12-hour time format with meridiem part.\n            </ng-template>\n            <date-time-mask-doc-example-5 />\n        </tui-doc-example>\n    </ng-template>\n\n    <ng-template pageTab>\n        <tui-doc-demo [control]=\"apiPageControl\">\n            <ng-template>\n                <tui-input\n                    tuiTextfieldCustomContent=\"@tui.calendar\"\n                    [formControl]=\"apiPageControl\"\n                    [tuiTextfieldFiller]=\"getPlaceholder(dateMode, timeMode, dateSeparator, dateTimeSeparator)\"\n                >\n                    Enter date and time\n                    <input\n                        inputmode=\"numeric\"\n                        tuiTextfieldLegacy\n                        [maskito]=\"maskitoOptions\"\n                    />\n                </tui-input>\n            </ng-template>\n        </tui-doc-demo>\n        <tui-doc-documentation>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"dateMode\"\n                documentationPropertyType=\"MaskitoDateMode\"\n                [documentationPropertyValues]=\"dateModeOptions\"\n                [(documentationPropertyValue)]=\"dateMode\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Date format mode\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"timeMode\"\n                documentationPropertyType=\"MaskitoTimeMode\"\n                [documentationPropertyValues]=\"timeModeOptions\"\n                [(documentationPropertyValue)]=\"timeMode\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Time format mode\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"dateSeparator\"\n                documentationPropertyType=\"string\"\n                [(documentationPropertyValue)]=\"dateSeparator\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Date separator\n\n                <p>\n                    <strong>Default:</strong>\n                    <code>.</code>\n                    (dot).\n                </p>\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"dateTimeSeparator\"\n                documentationPropertyType=\"string\"\n                [(documentationPropertyValue)]=\"dateTimeSeparator\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Separator between date and time\n\n                <p>\n                    <strong>Default:</strong>\n                    <code>,&nbsp;</code>\n                    (comma and space)\n                </p>\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"timeStep\"\n                documentationPropertyType=\"number\"\n                [(documentationPropertyValue)]=\"timeStep\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                The value by which the keyboard arrows increment/decrement time segments\n\n                <p>\n                    <strong>Default:</strong>\n                    <code>0</code>\n                    (disable stepping)\n                </p>\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"min\"\n                documentationPropertyType=\"Date\"\n                [documentationPropertyValues]=\"minMaxOptions\"\n                [(documentationPropertyValue)]=\"minStr\"\n                (documentationPropertyValueChange)=\"updateDate()\"\n            >\n                Earliest date\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"max\"\n                documentationPropertyType=\"Date\"\n                [documentationPropertyValues]=\"minMaxOptions\"\n                [(documentationPropertyValue)]=\"maxStr\"\n                (documentationPropertyValueChange)=\"updateDate()\"\n            >\n                Latest date\n            </ng-template>\n        </tui-doc-documentation>\n    </ng-template>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-time/examples/1-date-time-localization/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'date-time-mask-doc-example-1',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.calendar\"\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldFiller]=\"filler\"\n            [(ngModel)]=\"value\"\n        >\n            Localization\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DateTimeMaskDocExample1 {\n    protected value = '09/20/2020, 15:30';\n    protected readonly filler = 'mm/dd/yyyy, hh:mm';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-time/examples/1-date-time-localization/mask.ts",
    "content": "import {maskitoDateTimeOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoDateTimeOptionsGenerator({\n    dateMode: 'mm/dd/yyyy',\n    timeMode: 'HH:MM',\n    dateSeparator: '/',\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-time/examples/2-date-time-separator/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'date-time-mask-doc-example-2',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.calendar\"\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldFiller]=\"filler\"\n            [(ngModel)]=\"value\"\n        >\n            Custom date and time separator\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DateTimeMaskDocExample2 {\n    protected value = '05.02.2004; 10:10';\n    protected readonly filler = 'dd.mm.yyyy; hh:mm';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-time/examples/2-date-time-separator/mask.ts",
    "content": "import {maskitoDateTimeOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoDateTimeOptionsGenerator({\n    dateMode: 'dd/mm/yyyy',\n    timeMode: 'HH:MM',\n    dateTimeSeparator: '; ',\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-time/examples/3-min-max/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'date-time-mask-doc-example-3',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.calendar\"\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldFiller]=\"filler\"\n            [(ngModel)]=\"value\"\n        >\n            Min-max\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DateTimeMaskDocExample3 {\n    protected value = '09-01-2018, 15:30';\n    protected readonly filler = 'dd-mm-yyyy, hh:mm';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-time/examples/3-min-max/mask.ts",
    "content": "import {maskitoDateTimeOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoDateTimeOptionsGenerator({\n    dateMode: 'dd/mm/yyyy',\n    timeMode: 'HH:MM',\n    dateSeparator: '-',\n    min: new Date(2010, 1, 15, 12, 30, 0),\n    max: new Date(2020, 8, 15, 18, 30, 0),\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-time/examples/4-time-step/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'date-time-mask-doc-example-4',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.calendar\"\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldFiller]=\"filler\"\n            [(ngModel)]=\"value\"\n        >\n            Time Stepping\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DateTimeMaskDocExample4 {\n    protected value = '09.01.2018, 15:30';\n    protected readonly filler = 'dd.mm.yyyy, hh:mm';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-time/examples/4-time-step/mask.ts",
    "content": "import {maskitoDateTimeOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoDateTimeOptionsGenerator({\n    dateMode: 'dd/mm/yyyy',\n    timeMode: 'HH:MM',\n    timeStep: 1,\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-time/examples/5-am-pm/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'date-time-mask-doc-example-5',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.calendar\"\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldFiller]=\"filler\"\n            [(ngModel)]=\"value\"\n        >\n            With 12-hour time format\n            <input\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DateTimeMaskDocExample5 {\n    protected value = '20/09/2020, 03:30 PM';\n    protected readonly filler = 'mm/dd/yyyy, hh:mm aa';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-time/examples/5-am-pm/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {\n    type MaskitoDateMode,\n    maskitoDateTimeOptionsGenerator,\n    maskitoSelectionChangeHandler,\n    type MaskitoTimeMode,\n} from '@maskito/kit';\n\nconst dateTimeSeparator = ', ';\nconst dateMode: MaskitoDateMode = 'dd/mm/yyyy';\nconst timeMode: MaskitoTimeMode = 'HH:MM AA';\n\nconst dateTimeOptions = maskitoDateTimeOptionsGenerator({\n    dateMode,\n    timeMode,\n    dateTimeSeparator,\n    dateSeparator: '/',\n});\n\nexport default {\n    ...dateTimeOptions,\n    plugins: [\n        ...dateTimeOptions.plugins,\n        maskitoSelectionChangeHandler((element) => {\n            element.inputMode =\n                element.selectionStart! >= `${dateMode}${dateTimeSeparator}HH:MM`.length\n                    ? 'text'\n                    : 'numeric';\n        }),\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/kit/date-time/examples/maskito-parse-stringify-date-time-demo.md",
    "content": "```ts\nimport {maskitoParseDateTime, maskitoStringifyDateTime, MaskitoDateTimeParams} from '@maskito/kit';\n\nconst params: MaskitoDateTimeParams = {\n  dateMode: 'dd/mm/yyyy',\n  timeMode: 'HH:MM',\n  dateSeparator: ', ',\n};\n\nmaskitoParseDateTime('07.11.2022, 13:17', params); // returns Date object\nmaskitoStringifyDateTime(new Date(2022, 10, 7, 13, 17), params); // '07.11.2022, 13:17'\n```\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/1-high-precision/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'number-mask-doc-example-1',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"30\"\n            [(ngModel)]=\"value\"\n        >\n            <strong>&pi;</strong>\n            -value\n            <input\n                inputmode=\"decimal\"\n                placeholder=\"3,141...\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NumberMaskDocExample1 {\n    protected value = '';\n    protected maskitoOptions = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/1-high-precision/mask.ts",
    "content": "import {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoNumberOptionsGenerator({\n    maximumFractionDigits: 8,\n    min: 0,\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/2-separators/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'number-mask-doc-example-2',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"30\"\n            [(ngModel)]=\"value\"\n        >\n            Type number like a German\n            <input\n                inputmode=\"decimal\"\n                placeholder=\"1.000,42\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NumberMaskDocExample2 {\n    protected maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/2-separators/mask.ts",
    "content": "import {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoNumberOptionsGenerator({\n    decimalSeparator: ',',\n    thousandSeparator: '.',\n    maximumFractionDigits: 2,\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/3-postfix/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask, {postfix} from './mask';\n\n@Component({\n    selector: 'number-mask-doc-example-3',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NumberMaskDocExample3 {\n    protected value = `97${postfix}`;\n    protected readonly maskitoOptions = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/3-postfix/mask.ts",
    "content": "import {type MaskitoOptions, maskitoUpdateElement} from '@maskito/core';\nimport {\n    maskitoCaretGuard,\n    maskitoEventHandler,\n    maskitoNumberOptionsGenerator,\n} from '@maskito/kit';\n\nexport const postfix = '%';\nconst {plugins, ...numberOptions} = maskitoNumberOptionsGenerator({\n    postfix,\n    min: 0,\n    max: 100,\n    maximumFractionDigits: 2,\n});\n\nexport default {\n    ...numberOptions,\n    plugins: [\n        ...plugins,\n        // Forbids caret to be placed after postfix\n        maskitoCaretGuard((value) => [0, value.length - 1]),\n        maskitoEventHandler('blur', (element) => {\n            if (element.value === postfix) {\n                maskitoUpdateElement(element, `0${postfix}`);\n            }\n        }),\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/4-decimal-zero-padding/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'number-mask-doc-example-4',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"30\"\n            [(ngModel)]=\"value\"\n        >\n            Cost\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NumberMaskDocExample4 {\n    protected value = '$100.00';\n    protected maskitoOptions = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/4-decimal-zero-padding/mask.ts",
    "content": "import {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoNumberOptionsGenerator({\n    minimumFractionDigits: 2,\n    maximumFractionDigits: 2,\n    decimalSeparator: '.',\n    min: 0,\n    prefix: '$',\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/5-custom-minus-sign/components.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'number-mask-doc-example-5',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            <input\n                tuiTextfieldLegacy\n                [maskito]=\"options\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NumberMaskDocExample5 {\n    protected value = '-42';\n    protected readonly options = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/5-custom-minus-sign/mask.ts",
    "content": "import {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoNumberOptionsGenerator({\n    minusSign: '-',\n    thousandSeparator: '',\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/6-minus-before-prefix/components.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'number-mask-doc-example-6',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            <input\n                tuiTextfieldLegacy\n                [maskito]=\"options\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NumberMaskDocExample6 {\n    protected value = '-$777';\n    protected readonly options = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/6-minus-before-prefix/mask.ts",
    "content": "import {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoNumberOptionsGenerator({\n    minusSign: '-',\n    prefix: '$',\n    negativePattern: 'minusFirst',\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/7-dynamic-decimal-zero-padding/component.ts",
    "content": "/**\n * This example demonstrates Angular way.\n * But this behaviour can be achieved via vanilla JavaScript too\n * (it just requires more code).\n */\nimport {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {MaskitoOptions} from '@maskito/core';\nimport {tuiPure} from '@taiga-ui/cdk';\nimport {TuiLabel} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport {getMaskitoOptions} from './mask';\n\n@Component({\n    selector: 'number-mask-doc-example-7',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiLabel,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <label tuiLabel>\n            Enable decimal zero padding by typing dot\n            <tui-input\n                [style.max-width.rem]=\"30\"\n                [tuiTextfieldLabelOutside]=\"true\"\n                [(ngModel)]=\"value\"\n            >\n                <input\n                    inputmode=\"decimal\"\n                    tuiTextfieldLegacy\n                    [maskito]=\"getMaskOptions(decimalZeroPadding)\"\n                    (beforeinput.capture)=\"handleBeforeInput($event)\"\n                />\n            </tui-input>\n        </label>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NumberMaskDocExample7 {\n    protected value = '42';\n    protected decimalZeroPadding = this.value.includes('.');\n\n    @tuiPure // Decorator for memoization\n    protected getMaskOptions(decimalZeroPadding: boolean): MaskitoOptions {\n        return getMaskitoOptions(decimalZeroPadding);\n    }\n\n    protected handleBeforeInput(event: Event): void {\n        const {inputType, target, data} = event as InputEvent;\n\n        if (inputType.includes('delete')) {\n            const element = target as HTMLInputElement;\n            const [from, to] = this.getNotEmptySelection(\n                [element.selectionStart ?? 0, element.selectionEnd ?? 0],\n                inputType.includes('Forward'),\n            );\n            const dotWasRemoved = this.value.slice(from, to).includes('.');\n\n            this.decimalZeroPadding = this.decimalZeroPadding && !dotWasRemoved;\n        } else {\n            this.decimalZeroPadding = ['.', ',', 'б', 'ю'].some(\n                (sep) => data?.includes(sep) || this.value.includes(sep),\n            );\n        }\n    }\n\n    private getNotEmptySelection(\n        [from, to]: [number, number],\n        isForward: boolean,\n    ): [number, number] {\n        if (from !== to) {\n            return [from, to];\n        }\n\n        return isForward ? [from, to + 1] : [Math.max(from - 1, 0), to];\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/7-dynamic-decimal-zero-padding/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nexport function getMaskitoOptions(decimalZeroPadding: boolean): MaskitoOptions {\n    return maskitoNumberOptionsGenerator({\n        minimumFractionDigits: decimalZeroPadding ? 2 : 0,\n        maximumFractionDigits: 2,\n        decimalSeparator: '.',\n        min: 0,\n    });\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/8-thousand-separator-pattern/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'number-mask-doc-example-8',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"30\"\n            [(ngModel)]=\"value\"\n        >\n            Japanese yen\n            <input\n                inputmode=\"decimal\"\n                placeholder=\"¥1,2345,6789\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NumberMaskDocExample8 {\n    protected maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/8-thousand-separator-pattern/mask.ts",
    "content": "import {maskitoNumberOptionsGenerator, type MaskitoNumberParams} from '@maskito/kit';\n\nconst japaneseYenGrouping: MaskitoNumberParams['thousandSeparatorPattern'] = (digits) =>\n    digits.match(/\\d{1,4}(?=(?:\\d{4})*$)/g) ?? [];\n\nexport default maskitoNumberOptionsGenerator({\n    prefix: '¥',\n    thousandSeparator: ',',\n    thousandSeparatorPattern: japaneseYenGrouping,\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/9-thousand-separator-pattern-intl/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'number-mask-doc-example-9',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"30\"\n            [(ngModel)]=\"value\"\n        >\n            Indian numbering system\n            <input\n                inputmode=\"decimal\"\n                placeholder=\"₹12,34,567\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NumberMaskDocExample9 {\n    protected maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/examples/9-thousand-separator-pattern-intl/mask.ts",
    "content": "import {maskitoNumberOptionsGenerator, type MaskitoNumberParams} from '@maskito/kit';\n\nexport const intlPattern = (\n    locale: string,\n): MaskitoNumberParams['thousandSeparatorPattern'] => {\n    const formatter = new Intl.NumberFormat(locale, {maximumFractionDigits: 0});\n\n    return (digits: string): readonly string[] => {\n        if (!digits) {\n            return [];\n        }\n\n        let pos = 0;\n\n        return formatter\n            .formatToParts(BigInt(`1${'0'.repeat(digits.length - 1)}`))\n            .filter((part) => part.type === 'integer')\n            .map((part) => {\n                const group = digits.slice(pos, pos + part.value.length);\n\n                pos += part.value.length;\n\n                return group;\n            });\n    };\n};\n\nexport default maskitoNumberOptionsGenerator({\n    prefix: '₹',\n    thousandSeparator: ',',\n    thousandSeparatorPattern: intlPattern('en-IN'),\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/helpers/parse-number-as-bigint-type.md",
    "content": "```ts\nimport {\n  maskitoParseNumber,\n  type MaskitoNumberParams, // type for 2nd argument of maskitoParseNumber\n  maskitoNumberOptionsGenerator,\n} from '@maskito/kit';\n\nconst params: MaskitoNumberParams = {\n  thousandSeparator: '_',\n};\n\nmaskitoNumberOptionsGenerator(params); // MaskitoOptions\n\nconst value: bigint | null = maskitoParseNumber('1_234_567_890_123_456_789', {\n  ...params,\n  bigint: true,\n}); // 1234567890123456789n\n\nvalue > Number.MAX_SAFE_INTEGER; // true\ntypeof value === 'bigint'; // true\n\n// \"Empty\" values\nmaskitoParseNumber('', {bigint: true}); // null\nmaskitoParseNumber('-', {bigint: true}); // null\n```\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/helpers/parse-number-as-number-type.md",
    "content": "```ts\nimport {\n  maskitoParseNumber,\n  type MaskitoNumberParams, // type for 2nd argument of maskitoParseNumber\n  maskitoNumberOptionsGenerator,\n} from '@maskito/kit';\n\nconst params: MaskitoNumberParams = {\n  decimalSeparator: ',', // default is '.'\n};\n\nmaskitoNumberOptionsGenerator(params); // MaskitoOptions\n\nconst value: number = maskitoParseNumber('10 000,42', params); // 10000.42\n\ntypeof value === 'number'; // true\n\n// \"Empty\" values\nmaskitoParseNumber(''); // NaN\nmaskitoParseNumber('-'); // NaN\n```\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/helpers/parse-number-invalid-usage.md",
    "content": "```ts\nmaskitoParseNumber('-42'); // -42 ✅\nmaskitoParseNumber('> -42'); // 42 ❌\nmaskitoParseNumber('> -42', {prefix: '> '}); // -42 ✅\n```\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/helpers/stringify-number.md",
    "content": "```ts\nimport {\n  maskitoStringifyNumber,\n  type MaskitoNumberParams, // type for 2nd argument of maskitoStringifyNumber\n  maskitoNumberOptionsGenerator,\n} from '@maskito/kit';\n\nconst params: MaskitoNumberParams = {\n  thousandSeparator: '_',\n  prefix: '$',\n};\n\nmaskitoNumberOptionsGenerator(params); // MaskitoOptions\n\nmaskitoStringifyNumber(null); // ''\nmaskitoStringifyNumber(NaN); // ''\nmaskitoStringifyNumber(1234, params); // '$1_234'\nmaskitoStringifyNumber(BigInt('1234'), params); // '$1_234'\n```\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/number-mask-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormControl, ReactiveFormsModule} from '@angular/forms';\nimport {DocExamplePrimaryTab} from '@demo/constants';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {MaskitoOptions} from '@maskito/core';\nimport {\n    maskitoAddOnFocusPlugin,\n    maskitoCaretGuard,\n    maskitoNumberOptionsGenerator,\n    type MaskitoNumberParams,\n    maskitoRemoveOnBlurPlugin,\n} from '@maskito/kit';\nimport {CHAR_MINUS} from '@maskito/kit/src/lib/constants';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport {NumberMaskDocExample1} from './examples/1-high-precision/component';\nimport {NumberMaskDocExample2} from './examples/2-separators/component';\nimport {NumberMaskDocExample3} from './examples/3-postfix/component';\nimport {NumberMaskDocExample4} from './examples/4-decimal-zero-padding/component';\nimport {NumberMaskDocExample5} from './examples/5-custom-minus-sign/components';\nimport {NumberMaskDocExample6} from './examples/6-minus-before-prefix/components';\nimport {NumberMaskDocExample7} from './examples/7-dynamic-decimal-zero-padding/component';\nimport {NumberMaskDocExample8} from './examples/8-thousand-separator-pattern/component';\nimport {NumberMaskDocExample9} from './examples/9-thousand-separator-pattern-intl/component';\n\ntype GeneratorParams = Omit<\n    Required<MaskitoNumberParams>,\n    'minusPseudoSigns' | 'thousandSeparatorPattern'\n> &\n    Pick<MaskitoNumberParams, 'thousandSeparatorPattern'>;\n\n@Component({\n    selector: 'number-mask-doc',\n    imports: [\n        MaskitoDirective,\n        NumberMaskDocExample1,\n\n        NumberMaskDocExample2,\n        NumberMaskDocExample3,\n        NumberMaskDocExample4,\n        NumberMaskDocExample5,\n        NumberMaskDocExample6,\n        NumberMaskDocExample7,\n        NumberMaskDocExample8,\n        NumberMaskDocExample9,\n        ReactiveFormsModule,\n        TuiAddonDoc,\n        TuiInputModule,\n        TuiLink,\n        TuiNotification,\n    ],\n    templateUrl: './number-mask-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class NumberMaskDocComponent implements GeneratorParams {\n    protected readonly parseNumberAsNumberTypeDemo =\n        import('./helpers/parse-number-as-number-type.md');\n\n    protected readonly parseNumberAsBigIntTypeDemo =\n        import('./helpers/parse-number-as-bigint-type.md');\n\n    protected readonly parseNumberInvalidUsageDemo =\n        import('./helpers/parse-number-invalid-usage.md');\n\n    protected readonly stringifyNumberDemo = import('./helpers/stringify-number.md');\n\n    protected readonly highPrecisionExample1: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/1-high-precision/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly separatorsExample2: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/2-separators/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly postfixExample3: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/3-postfix/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly decimalZeroPaddingExample4: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/4-decimal-zero-padding/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly customMinusSignExample5: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/5-custom-minus-sign/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly minusBeforePrefixExample6: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/6-minus-before-prefix/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly thousandSeparatorPatternExample8: Record<\n        string,\n        TuiRawLoaderContent\n    > = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/8-thousand-separator-pattern/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly thousandSeparatorPatternIntlExample9: Record<\n        string,\n        TuiRawLoaderContent\n    > = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/9-thousand-separator-pattern-intl/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly dynamicDecimalZeroPaddingExample7: Record<\n        string,\n        TuiRawLoaderContent\n    > = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/7-dynamic-decimal-zero-padding/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n        [DocExamplePrimaryTab.Angular]: import(\n            './examples/7-dynamic-decimal-zero-padding/component.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected apiPageControl = new FormControl('');\n\n    protected readonly decimalPseudoSeparatorsOptions = [\n        ['.', ',', 'б', 'ю'],\n        ['.'],\n        [','],\n    ];\n\n    protected readonly maximumFractionDigitsOptions = [0, 1, 2, 5, 10, Infinity];\n\n    protected readonly negativePatternOptions = [\n        'prefixFirst',\n        'minusFirst',\n    ] as const satisfies ReadonlyArray<Required<MaskitoNumberParams>['negativePattern']>;\n\n    protected readonly minOptions: ReadonlyArray<bigint | number> = [\n        -Infinity,\n        BigInt(`-${'987654321'.repeat(3)}`),\n        Number.MIN_SAFE_INTEGER,\n        -123,\n        -100,\n        0,\n        0.1,\n        5,\n    ];\n\n    protected readonly maxOptions: ReadonlyArray<bigint | number> = [\n        Infinity,\n        BigInt('987654321'.repeat(3)),\n        Number.MAX_SAFE_INTEGER,\n        777,\n        3,\n        0,\n        -0.1,\n        -5,\n    ];\n\n    public max = Infinity;\n    public min = -Infinity;\n    public decimalSeparator = '.';\n    public decimalPseudoSeparators = this.decimalPseudoSeparatorsOptions[0]!;\n    public thousandSeparator = ' ';\n    public prefix = '';\n    public postfix = '';\n    public minusSign = CHAR_MINUS;\n    public minimumFractionDigits = 0;\n    public maximumFractionDigits = 0;\n    public negativePattern: Required<MaskitoNumberParams>['negativePattern'] =\n        this.negativePatternOptions[0];\n\n    public thousandSeparatorPattern: MaskitoNumberParams['thousandSeparatorPattern'];\n\n    public maskitoOptions = this.calculateMask(this);\n\n    protected updateOptions(): void {\n        this.maskitoOptions = this.calculateMask(this);\n    }\n\n    private calculateMask(params: GeneratorParams): MaskitoOptions {\n        const {prefix, postfix, negativePattern, minusSign} = params;\n        const {plugins, ...numberOptions} = maskitoNumberOptionsGenerator(params);\n\n        return {\n            ...numberOptions,\n            plugins: [\n                ...plugins,\n                maskitoAddOnFocusPlugin(`${prefix}${postfix}`),\n                maskitoRemoveOnBlurPlugin(`${prefix}${postfix}`),\n                maskitoCaretGuard((value) => [\n                    negativePattern === 'minusFirst' && value.includes(minusSign)\n                        ? minusSign.length + prefix.length\n                        : prefix.length,\n                    value.length - postfix.length,\n                ]),\n            ],\n        };\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/number/number-mask-doc.template.html",
    "content": "<tui-doc-page\n    header=\"Number\"\n    package=\"KIT\"\n>\n    <ng-template pageTab>\n        Use\n        <code>maskitoNumberOptionsGenerator</code>\n        to create a mask for entering a formatted number.\n\n        <tui-doc-example\n            id=\"high-precision\"\n            heading=\"High precision\"\n            [content]=\"highPrecisionExample1\"\n            [description]=\"maximumFractionDigitsDescription\"\n        >\n            <ng-template #maximumFractionDigitsDescription>\n                Use\n                <code>maximumFractionDigits</code>\n                parameter to configure the number of digits after decimal separator.\n            </ng-template>\n            <number-mask-doc-example-1 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"separators\"\n            heading=\"Separators\"\n            [content]=\"separatorsExample2\"\n            [description]=\"separatorsDescription\"\n        >\n            <ng-template #separatorsDescription>\n                Use\n                <code>decimalSeparator</code>\n                and\n                <code>thousandSeparator</code>\n                to get mask with locale specific representation of numbers.\n            </ng-template>\n            <tui-notification\n                size=\"m\"\n                class=\"tui-space_bottom-4\"\n                [style.max-width.rem]=\"30\"\n            >\n                In Germany people use comma as decimal separator and dot for thousands\n            </tui-notification>\n            <number-mask-doc-example-2 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"postfix\"\n            heading=\"Postfix\"\n            [content]=\"postfixExample3\"\n            [description]=\"postfixDescription\"\n        >\n            <ng-template #postfixDescription>\n                <div>\n                    Use\n                    <code>postfix</code>\n                    parameter to set non-removable text after the number.\n                </div>\n                <div>\n                    Additionally you can use\n                    <code>maskitoCaretGuard</code>\n                    to clamp caret inside allowable range.\n                </div>\n                <div class=\"tui-space_top-4\">\n                    This example also shows how to restrict the greatest permitted value via\n                    <code>max</code>\n                    parameter.\n                </div>\n            </ng-template>\n            <number-mask-doc-example-3 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"decimal-zero-padding\"\n            heading=\"Decimal zero padding\"\n            [content]=\"decimalZeroPaddingExample4\"\n            [description]=\"decimalZeroPaddingDescription\"\n        >\n            <ng-template #decimalZeroPaddingDescription>\n                <p class=\"tui-space_top-0\">\n                    Use\n                    <code>minimumFractionDigits</code>\n                    to always show trailing zeroes.\n                </p>\n                <p>\n                    Non removable dollar sign is achieved by using\n                    <code>prefix</code>\n                    parameter.\n                </p>\n            </ng-template>\n            <number-mask-doc-example-4 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"minus-sign\"\n            heading=\"Minus sign\"\n            [content]=\"customMinusSignExample5\"\n            [description]=\"customMinusSignDescription\"\n        >\n            <ng-template #customMinusSignDescription>\n                <p>\n                    Use\n                    <code>minusSign</code>\n                    parameter to configure the character which indicates that a number is negative.\n                </p>\n                <p>\n                    In this example\n                    <a\n                        href=\"https://symbl.cc/en/2010\"\n                        rel=\"noreferrer\"\n                        target=\"_blank\"\n                        tuiLink\n                    >\n                        hyphen\n                    </a>\n                    is used as\n                    <code>minusSign</code>\n                </p>\n            </ng-template>\n            <number-mask-doc-example-5 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"minus-before-prefix\"\n            heading=\"Minus before prefix\"\n            [content]=\"minusBeforePrefixExample6\"\n            [description]=\"minusBeforePrefixDescription\"\n        >\n            <ng-template #minusBeforePrefixDescription>\n                Use\n                <code>negativePattern</code>\n                property to configure order of prefix and minus sign (by default, prefix is always placed before minus).\n            </ng-template>\n            <number-mask-doc-example-6 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"dynamic-decimal-zero-padding\"\n            heading=\"Dynamic decimal zero padding\"\n            [content]=\"dynamicDecimalZeroPaddingExample7\"\n            [description]=\"dynamicDecimalZeroPaddingDescription\"\n        >\n            <ng-template #dynamicDecimalZeroPaddingDescription>\n                <div>You can change options on the fly to build complex logic.</div>\n                <div>\n                    This example shows how to initially disable decimal zero padding and enable it only after user\n                    inserts decimal separator.\n                </div>\n            </ng-template>\n            <number-mask-doc-example-7 />\n        </tui-doc-example>\n        <tui-doc-example\n            id=\"thousand-separator-pattern\"\n            heading=\"Thousand separator pattern\"\n            [content]=\"thousandSeparatorPatternExample8\"\n            [description]=\"thousandSeparatorPatternDescription\"\n        >\n            <ng-template #thousandSeparatorPatternDescription>\n                <p>\n                    Use\n                    <code>thousandSeparatorPattern</code>\n                    to customize digit grouping. Provide a function that receives raw integer digits as a string and\n                    returns them split into groups, left-to-right.\n                </p>\n                <p>\n                    This example implements 4-digit grouping for Japanese yen — the traditional\n                    <code>万</code>\n                    (10 000) counting system: ¥1,2345,6789.\n                </p>\n            </ng-template>\n            <tui-notification\n                size=\"m\"\n                class=\"tui-space_bottom-4\"\n                [style.max-width.rem]=\"30\"\n            >\n                Japanese numbering groups digits in sets of 4 from right: ¥1,2345,6789\n            </tui-notification>\n            <number-mask-doc-example-8 />\n        </tui-doc-example>\n        <tui-doc-example\n            id=\"thousand-separator-pattern-intl\"\n            heading=\"Thousand separator pattern uses Intl.NumberFormat\"\n            [content]=\"thousandSeparatorPatternIntlExample9\"\n            [description]=\"thousandSeparatorPatternIntlDescription\"\n        >\n            <ng-template #thousandSeparatorPatternIntlDescription>\n                Use\n                <code>Intl.NumberFormat.formatToParts</code>\n                to derive the grouping from a browser locale automatically. Pass the result to\n                <code>thousandSeparatorPattern</code>\n                parameter.\n            </ng-template>\n            <tui-notification\n                size=\"m\"\n                class=\"tui-space_bottom-4\"\n                [style.max-width.rem]=\"30\"\n            >\n                Indian numbering system groups digits as 2+3 from right: 12,34,567\n            </tui-notification>\n            <number-mask-doc-example-9 />\n        </tui-doc-example>\n    </ng-template>\n\n    <ng-template pageTab>\n        <tui-doc-demo [control]=\"apiPageControl\">\n            <ng-template>\n                <tui-input\n                    tuiHintContent=\"Only digits (+ decimal separator) are allowed\"\n                    [formControl]=\"apiPageControl\"\n                >\n                    Enter a number\n                    <input\n                        inputmode=\"decimal\"\n                        tuiTextfieldLegacy\n                        [maskito]=\"maskitoOptions\"\n                    />\n                </tui-input>\n            </ng-template>\n        </tui-doc-demo>\n        <tui-doc-documentation>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"decimalSeparator\"\n                documentationPropertyType=\"string\"\n                [(documentationPropertyValue)]=\"decimalSeparator\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Symbol for separating fraction.\n\n                <p>\n                    <strong>Default:</strong>\n                    point.\n                </p>\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"decimalPseudoSeparators\"\n                documentationPropertyType=\"string[]\"\n                [documentationPropertyValues]=\"decimalPseudoSeparatorsOptions\"\n                [(documentationPropertyValue)]=\"decimalPseudoSeparators\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Symbols to be replaced with\n                <code>decimalSeparator</code>\n                .\n\n                <p>\n                    <strong>Default:</strong>\n                    <code>['.', 'ю', 'б']</code>\n                    .\n                </p>\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"thousandSeparator\"\n                documentationPropertyType=\"string\"\n                [(documentationPropertyValue)]=\"thousandSeparator\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Symbol for separating thousands.\n\n                <p>\n                    <strong>Default:</strong>\n                    non-breaking space.\n                </p>\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"thousandSeparatorPattern\"\n                documentationPropertyType=\"(digits: string) => readonly string[]\"\n                [(documentationPropertyValue)]=\"thousandSeparatorPattern\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                A function that defines how integer digits are split into groups. Receives raw integer digits as a\n                string; returns them as an ordered array of groups (left-to-right).\n\n                <p>\n                    <strong>Default:</strong>\n                    standard 3-digit Western grouping driven by\n                    <code>thousandSeparator</code>\n                    .\n                </p>\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"minimumFractionDigits\"\n                documentationPropertyType=\"number\"\n                [(documentationPropertyValue)]=\"minimumFractionDigits\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                <p>The minimum number of fraction digits to use.</p>\n\n                <p>\n                    A value with a smaller number of fraction digits than this number will be right-padded with zeros\n                    (to the specified length).\n                </p>\n\n                <p>\n                    <strong>Default:</strong>\n                    <code>0</code>\n                    .\n                </p>\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"maximumFractionDigits\"\n                documentationPropertyType=\"number\"\n                [documentationPropertyValues]=\"maximumFractionDigitsOptions\"\n                [(documentationPropertyValue)]=\"maximumFractionDigits\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                <p>\n                    The maximum number of digits after\n                    <code>decimalSeparator</code>\n                    .\n                </p>\n\n                <p>\n                    Use\n                    <code>Infinity</code>\n                    for an untouched decimal part.\n                </p>\n\n                <p>\n                    <strong>Default:</strong>\n                    <code>0</code>\n                    (decimal part is forbidden).\n                </p>\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"min\"\n                documentationPropertyType=\"number | bigint\"\n                [documentationPropertyValues]=\"minOptions\"\n                [(documentationPropertyValue)]=\"min\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                The lowest permitted value.\n\n                <p>\n                    <strong>Default:</strong>\n                    <code>-Infinity</code>\n                    .\n                </p>\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"max\"\n                documentationPropertyType=\"number | bigint\"\n                [documentationPropertyValues]=\"maxOptions\"\n                [(documentationPropertyValue)]=\"max\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                The greatest permitted value.\n\n                <p>\n                    <strong>Default:</strong>\n                    <code>Infinity</code>\n                    .\n                </p>\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"prefix\"\n                documentationPropertyType=\"string\"\n                [(documentationPropertyValue)]=\"prefix\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                A prefix symbol, like currency.\n\n                <p>\n                    <strong>Default:</strong>\n                    empty string (no prefix).\n                </p>\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"postfix\"\n                documentationPropertyType=\"string\"\n                [(documentationPropertyValue)]=\"postfix\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                A postfix symbol, like currency.\n\n                <p>\n                    <strong>Default:</strong>\n                    empty string (no postfix).\n                </p>\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"minusSign\"\n                documentationPropertyType=\"string\"\n                [(documentationPropertyValue)]=\"minusSign\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                A minus symbol.\n\n                <p>\n                    <strong>Default:</strong>\n                    <a\n                        href=\"https://symbl.cc/en/2212\"\n                        rel=\"noreferrer\"\n                        target=\"_blank\"\n                        tuiLink\n                    >\n                        <code>\\u2212</code>\n                    </a>\n                </p>\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"negativePattern\"\n                documentationPropertyType=\"'minusFirst' | 'prefixFirst'\"\n                [documentationPropertyValues]=\"negativePatternOptions\"\n                [(documentationPropertyValue)]=\"negativePattern\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Order of prefix and minus sign\n\n                <p>\n                    <strong>Default:</strong>\n                    <code>'prefixFirst'</code>\n                </p>\n            </ng-template>\n        </tui-doc-documentation>\n    </ng-template>\n\n    <ng-template pageTab=\"Helpers\">\n        <div>\n            Despite the name of the mask, the element's raw value remains of type\n            <code>string</code>\n            !\n        </div>\n        <p>\n            Use the helpers below to perform\n            <code>string</code>\n            ⇄\n            <code>number</code>\n            conversions seamlessly.\n        </p>\n\n        <section class=\"tui-space_top-10\">\n            <h2>maskitoParseNumber</h2>\n\n            <ul class=\"tui-list\">\n                <li class=\"tui-list__item\">\n                    By default, returns\n                    <code>number</code>\n                    type value\n\n                    <tui-doc-code\n                        class=\"tui-space_top-4\"\n                        [code]=\"parseNumberAsNumberTypeDemo\"\n                    />\n                </li>\n\n                <li class=\"tui-list__item\">\n                    Also, supports\n                    <code>bigint</code>\n                    mode\n\n                    <tui-doc-code\n                        class=\"tui-space_top-4\"\n                        [code]=\"parseNumberAsBigIntTypeDemo\"\n                    />\n                </li>\n            </ul>\n\n            <tui-notification\n                appearance=\"warning\"\n                size=\"m\"\n                class=\"tui-space_top-4\"\n            >\n                Always pass the second argument to the utility if your number format parameters differ from the default\n                ones to ensure correct parsing and avoid unexpected results!\n\n                <tui-doc-code [code]=\"parseNumberInvalidUsageDemo\" />\n            </tui-notification>\n        </section>\n\n        <section class=\"tui-space_top-10\">\n            <h2>maskitoStringifyNumber</h2>\n\n            Works with both\n            <code>number</code>\n            and\n            <code>bigint</code>\n            types.\n\n            <tui-doc-code\n                class=\"tui-space_top-4\"\n                [code]=\"stringifyNumberDemo\"\n            />\n        </section>\n    </ng-template>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/kit/plugins/examples/1-selection-handler/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiIcon, TuiTextfield} from '@taiga-ui/core';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'kit-plugins-doc-example-1',\n    imports: [FormsModule, MaskitoDirective, TuiIcon, TuiTextfield],\n    template: `\n        <tui-textfield\n            filler=\"HH:MM AA\"\n            [style.max-width.rem]=\"20\"\n        >\n            <label tuiLabel>Enter 12-hour time format</label>\n\n            <input\n                tuiTextfield\n                [maskito]=\"maskitoOptions\"\n                [(ngModel)]=\"value\"\n            />\n\n            <tui-icon icon=\"@tui.clock\" />\n        </tui-textfield>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class KitPluginsDocExample1 {\n    protected value = '05:00 PM';\n    protected maskitoOptions = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/plugins/examples/1-selection-handler/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {\n    maskitoSelectionChangeHandler,\n    type MaskitoTimeMode,\n    maskitoTimeOptionsGenerator,\n} from '@maskito/kit';\n\nconst mode: MaskitoTimeMode = 'HH:MM AA';\n\nconst timeOptions = maskitoTimeOptionsGenerator({mode});\n\nexport default {\n    ...timeOptions,\n    plugins: [\n        ...timeOptions.plugins,\n        maskitoSelectionChangeHandler((element) => {\n            element.inputMode =\n                element.selectionStart! >= mode.indexOf(' AA') ? 'text' : 'numeric';\n        }),\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/kit/plugins/examples/2-caret-guard/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiTextfield} from '@taiga-ui/core';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'kit-plugins-doc-example-2',\n    imports: [FormsModule, MaskitoDirective, TuiTextfield],\n    template: `\n        <tui-textfield\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldCleaner]=\"false\"\n        >\n            <input\n                inputmode=\"numeric\"\n                tuiTextfield\n                [maskito]=\"maskitoOptions\"\n                [(ngModel)]=\"value\"\n            />\n        </tui-textfield>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class KitPluginsDocExample2 {\n    protected value = '$100 per day';\n    protected maskitoOptions = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/plugins/examples/2-caret-guard/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {maskitoCaretGuard, maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nconst prefix = '$';\nconst postfix = ' per day';\n\nconst numberOptions = maskitoNumberOptionsGenerator({\n    prefix,\n    postfix,\n    min: 0,\n});\n\nexport default {\n    ...numberOptions,\n    plugins: [\n        ...numberOptions.plugins,\n        maskitoCaretGuard((value) => [prefix.length, value.length - postfix.length]),\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/kit/plugins/examples/3-event-handlers/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiIcon, TuiTextfield} from '@taiga-ui/core';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'kit-plugins-doc-example-3',\n    imports: [FormsModule, MaskitoDirective, TuiIcon, TuiTextfield],\n    template: `\n        <tui-textfield\n            filler=\"+1 (___) ___-____\"\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldCleaner]=\"false\"\n        >\n            <input\n                placeholder=\"Enter mobile phone\"\n                tuiTextfield\n                [maskito]=\"maskitoOptions\"\n                [(ngModel)]=\"value\"\n            />\n\n            <tui-icon icon=\"@tui.phone\" />\n        </tui-textfield>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class KitPluginsDocExample3 {\n    protected value = '';\n    protected maskitoOptions = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/plugins/examples/3-event-handlers/mask.ts",
    "content": "import {type MaskitoOptions, maskitoUpdateElement} from '@maskito/core';\nimport {\n    maskitoAddOnFocusPlugin,\n    maskitoEventHandler,\n    maskitoPrefixPostprocessorGenerator,\n} from '@maskito/kit';\n// import {maskitoRemoveOnBlurPlugin} from '@maskito/kit';\n\nconst countryPrefix = '+1 ';\n\nexport default {\n    plugins: [\n        maskitoAddOnFocusPlugin(countryPrefix),\n        /**\n         * You can also just use `maskitoRemoveOnBlurPlugin(countryPrefix)`\n         * instead of plugin below.\n         */\n        maskitoEventHandler('blur', (element) => {\n            if (element.value === countryPrefix) {\n                maskitoUpdateElement(element, '');\n            }\n        }),\n    ],\n    postprocessors: [maskitoPrefixPostprocessorGenerator(countryPrefix)],\n    mask: [\n        '+',\n        '1',\n        ' ',\n        '(',\n        /\\d/,\n        /\\d/,\n        /\\d/,\n        ')',\n        ' ',\n        /\\d/,\n        /\\d/,\n        /\\d/,\n        '-',\n        /\\d/,\n        /\\d/,\n        /\\d/,\n        /\\d/,\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/kit/plugins/examples/4-reject/animation.css",
    "content": ":root {\n    --red: 244, 87, 37;\n}\n\n@keyframes reject-0 {\n    from {\n        box-shadow: 0 0 rgba(var(--red), 1);\n    }\n\n    to {\n        box-shadow: 0 0 1rem rgba(var(--red), 0.12);\n    }\n}\n\n@keyframes reject-1 {\n    from {\n        box-shadow: 0 0 rgba(var(--red), 1);\n    }\n\n    to {\n        box-shadow: 0 0 1rem rgba(var(--red), 0.12);\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/plugins/examples/4-reject/component.ts",
    "content": "import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'kit-plugins-doc-example-4',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"20\"\n            [(ngModel)]=\"value\"\n        >\n            CVC\n            <input\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    styleUrl: './animation.css',\n    encapsulation: ViewEncapsulation.None,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class KitPluginsDocExample4 {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/plugins/examples/4-reject/index.md",
    "content": "```ts\nimport './animation.css';\n\nimport {Maskito} from '@maskito/core';\n\nimport maskitoOptions from './mask';\n\nconst element = document.querySelector('input')!;\nconst maskedInput = new Maskito(element, maskitoOptions);\n\nconsole.info('Call this function when the element is detached from DOM', maskedInput.destroy);\n```\n"
  },
  {
    "path": "projects/demo/src/pages/kit/plugins/examples/4-reject/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {maskitoRejectEvent} from '@maskito/kit';\n\nconst maskitoOptions: MaskitoOptions = {\n    mask: /^\\d{0,3}$/,\n    plugins: [\n        maskitoRejectEvent,\n        (element) => {\n            element.style.animation = '0.3s 1';\n\n            let reject = -1;\n            const listener = (): void => {\n                reject += 1;\n                element.style.animationName = `reject-${reject % 2}`;\n            };\n\n            element.addEventListener('maskitoReject', listener);\n\n            return () => element.removeEventListener('maskitoReject', listener);\n        },\n    ],\n};\n\nexport default maskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/kit/plugins/kit-plugins-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component, inject} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath, DocExamplePrimaryTab} from '@demo/constants';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TUI_IS_MOBILE} from '@taiga-ui/cdk';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\n\nimport {KitPluginsDocExample1} from './examples/1-selection-handler/component';\nimport {KitPluginsDocExample2} from './examples/2-caret-guard/component';\nimport {KitPluginsDocExample3} from './examples/3-event-handlers/component';\nimport {KitPluginsDocExample4} from './examples/4-reject/component';\n\n@Component({\n    selector: 'kit-plugins-doc',\n    imports: [\n        KitPluginsDocExample1,\n        KitPluginsDocExample2,\n        KitPluginsDocExample3,\n        KitPluginsDocExample4,\n        RouterLink,\n        TuiAddonDoc,\n        TuiLink,\n        TuiNotification,\n    ],\n    templateUrl: './kit-plugins-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class KitPluginsDocComponent {\n    protected readonly pluginsDocPage = `/${DemoPath.Plugins}`;\n    protected readonly isMobile = inject(TUI_IS_MOBILE);\n\n    protected readonly selectionChangeHandlerExample: Record<\n        string,\n        TuiRawLoaderContent\n    > = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/1-selection-handler/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly caretGuardExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/2-caret-guard/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly eventHandlersExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/3-event-handlers/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly rejectExample: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/4-reject/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n        'animation.css': import('./examples/4-reject/animation.css'),\n        [DocExamplePrimaryTab.JavaScript]: import('./examples/4-reject/index.md'),\n    };\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/plugins/kit-plugins-doc.template.html",
    "content": "<tui-doc-page\n    header=\"Plugins\"\n    package=\"KIT\"\n    path=\"kit/src/lib/plugins\"\n>\n    The page contains list of officially supported plugins by\n    <strong>Maskito</strong>\n    team.\n\n    <tui-notification\n        size=\"m\"\n        class=\"tui-space_top-4\"\n    >\n        If you wish to develop your own plugin, read\n        <a\n            tuiLink\n            [routerLink]=\"pluginsDocPage\"\n        >\n            documentation page about plugins\n        </a>\n        .\n    </tui-notification>\n\n    <tui-doc-example\n        id=\"selection-change-handler\"\n        heading=\"Selection Change Handler\"\n        [content]=\"selectionChangeHandlerExample\"\n        [description]=\"selectionChangeHandlerDescription\"\n    >\n        <ng-template #selectionChangeHandlerDescription>\n            Plugin\n            <code>maskitoSelectionChangeHandler</code>\n            accepts callback and invokes it on every change of caret position.\n\n            <p>\n                This examples demonstrates how dynamically switch native mobile keyboard to enter different parts of\n                time string:\n                <code>numeric</code>\n                - to enter digit time segments,\n                <code>text</code>\n                – to enter meridiem part (AM / PM).\n            </p>\n        </ng-template>\n        @if (!isMobile) {\n            <tui-notification\n                appearance=\"warning\"\n                size=\"m\"\n                class=\"tui-space_bottom-4\"\n                [style.max-width.rem]=\"20\"\n            >\n                Use real mobile device to see how it works!\n            </tui-notification>\n        }\n\n        <kit-plugins-doc-example-1 />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"caret-huard\"\n        heading=\"Caret Guard\"\n        [content]=\"caretGuardExample\"\n        [description]=\"caretGuardDescription\"\n    >\n        <ng-template #caretGuardDescription>\n            Plugin\n            <code>maskitoCaretGuard</code>\n            is specific instance of\n            <code>maskitoSelectionChangeHandler</code>\n            - it also accepts callback which is triggered on every caret position change. It is used to limit the\n            boundaries for caret position.\n\n            <p>\n                Callback should return array with 2 numbers: the first one – caret cannot be placed\n                <strong>before</strong>\n                this index, the last one – caret cannot be placed\n                <strong>after</strong>\n                this index.\n            </p>\n\n            <p>It can be especially useful for textfields with non-editable affixes.</p>\n        </ng-template>\n        <kit-plugins-doc-example-2 />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"event-handlers\"\n        heading=\"Event handlers\"\n        [content]=\"eventHandlersExample\"\n        [description]=\"eventHandlersDescription\"\n    >\n        <ng-template #eventHandlersDescription>\n            Add/remove non-editable prefix on focus/blur is so common task that we even created\n            <code>maskitoAddOnFocusPlugin</code>\n            /\n            <code>maskitoRemoveOnBlurPlugin</code>\n            plugins.\n\n            <p>\n                If you need more complex logic for these (or other) events – use\n                <code>maskitoEventHandler</code>\n                .\n            </p>\n        </ng-template>\n        <kit-plugins-doc-example-3 />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"reject\"\n        heading=\"Visualize rejected characters\"\n        [content]=\"rejectExample\"\n        [description]=\"rejectDescription\"\n    >\n        <ng-template #rejectDescription>\n            Plugin\n            <code>maskitoRejectEvent</code>\n            dispatches custom event\n            <code>maskitoReject</code>\n            when a character that the user has entered is rejected by the mask. You can use it to visualize rejection.\n        </ng-template>\n        <kit-plugins-doc-example-4 />\n    </tui-doc-example>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/examples/1-modes/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'time-mask-doc-example-1',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldFiller=\"hh:mm:ss\"\n            tuiTextfieldIcon=\"@tui.clock\"\n            [style.max-width.rem]=\"20\"\n            [(ngModel)]=\"value\"\n        >\n            Enter 24-hour time format\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TimeMaskDocExample1 {\n    protected readonly mask = mask;\n    protected value = '23:59:59';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/examples/1-modes/mask.ts",
    "content": "import {maskitoTimeOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoTimeOptionsGenerator({mode: 'HH:MM:SS'});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/examples/2-am-pm/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'time-mask-doc-example-2',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldFiller=\"HH:MM AA\"\n            tuiTextfieldIcon=\"@tui.clock\"\n            [style.max-width.rem]=\"20\"\n            [(ngModel)]=\"value\"\n        >\n            Enter 12-hour time format\n            <input\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TimeMaskDocExample2 {\n    protected readonly mask = mask;\n    protected value = '03:30 PM';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/examples/2-am-pm/mask.ts",
    "content": "import {type MaskitoOptions, maskitoUpdateElement} from '@maskito/core';\nimport {\n    maskitoEventHandler,\n    maskitoSelectionChangeHandler,\n    maskitoTimeOptionsGenerator,\n} from '@maskito/kit';\n\nconst timeOptions = maskitoTimeOptionsGenerator({mode: 'HH:MM AA'});\n\nexport default {\n    ...timeOptions,\n    plugins: [\n        ...timeOptions.plugins,\n        maskitoSelectionChangeHandler((element) => {\n            element.inputMode =\n                element.selectionStart! >= 'HH:MM'.length ? 'text' : 'numeric';\n        }),\n        maskitoEventHandler('blur', (element) => {\n            if (element.value.length >= 'HH:MM'.length && !element.value.endsWith('M')) {\n                maskitoUpdateElement(element, `${element.value} AM`);\n            }\n        }),\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/examples/3-step/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'time-mask-doc-example-3',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldFiller=\"hh:mm:ss\"\n            tuiTextfieldIcon=\"@tui.clock\"\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldLabelOutside]=\"true\"\n            [(ngModel)]=\"value\"\n        >\n            <input\n                inputmode=\"decimal\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TimeMaskDocExample3 {\n    protected value = '11:59:59';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/examples/3-step/mask.ts",
    "content": "import {type MaskitoOptions, maskitoUpdateElement} from '@maskito/core';\nimport {maskitoEventHandler, maskitoTimeOptionsGenerator} from '@maskito/kit';\n\nconst timeOptions = maskitoTimeOptionsGenerator({\n    mode: 'HH:MM:SS',\n    step: 1,\n});\n\nexport default {\n    ...timeOptions,\n    plugins: [\n        ...timeOptions.plugins,\n        maskitoEventHandler('blur', (element) => {\n            const [hh = '', mm = '', ss = ''] = element.value.split(':');\n\n            maskitoUpdateElement(\n                element,\n                [hh, mm, ss].map((segment) => segment.padEnd(2, '0')).join(':'),\n            );\n        }),\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/examples/4-affixes/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'time-mask-doc-example-4',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldIcon=\"@tui.timer\"\n            [style.max-width.rem]=\"20\"\n            [(ngModel)]=\"value\"\n        >\n            Timer (minutes)\n            <input\n                inputmode=\"numeric\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TimeMaskDocExample4 {\n    protected value = '05:00 left';\n    protected readonly maskitoOptions = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/examples/4-affixes/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {\n    maskitoAddOnFocusPlugin,\n    maskitoCaretGuard,\n    maskitoRemoveOnBlurPlugin,\n    maskitoTimeOptionsGenerator,\n} from '@maskito/kit';\n\nexport const postfix = ' left';\nconst {plugins, ...timeOptions} = maskitoTimeOptionsGenerator({\n    postfix,\n    mode: 'MM:SS',\n});\n\nexport default {\n    ...timeOptions,\n    plugins: [\n        ...plugins,\n        maskitoRemoveOnBlurPlugin(postfix),\n        maskitoAddOnFocusPlugin(postfix),\n        // Forbids caret to be placed after postfix\n        maskitoCaretGuard((value) => [0, value.length - postfix.length]),\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/examples/5-time-segments-min-max/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiTextfield} from '@taiga-ui/core';\nimport {TuiSegmented} from '@taiga-ui/kit';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'time-mask-doc-example-5',\n    imports: [FormsModule, MaskitoDirective, TuiSegmented, TuiTextfield],\n    template: `\n        <tui-textfield\n            filler=\"HH:MM\"\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldCleaner]=\"false\"\n        >\n            <input\n                inputmode=\"decimal\"\n                tuiTextfield\n                [maskito]=\"mask\"\n                [(ngModel)]=\"value\"\n            />\n\n            <tui-segmented>\n                <button type=\"button\">AM</button>\n                <button type=\"button\">PM</button>\n            </tui-segmented>\n        </tui-textfield>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TimeMaskDocExample5 {\n    protected value = '03:30';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/examples/5-time-segments-min-max/mask.ts",
    "content": "import {maskitoTimeOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoTimeOptionsGenerator({\n    mode: 'HH:MM',\n    timeSegmentMaxValues: {hours: 12},\n    timeSegmentMinValues: {hours: 1},\n});\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/examples/maskito-parse-stringify-time-demo.md",
    "content": "```ts\nimport {maskitoParseTime, maskitoStringifyTime, MaskitoTimeParams} from '@maskito/kit';\n\nconst params: MaskitoTimeParams = {mode: 'HH:MM:SS.MSS'};\n\nmaskitoParseTime('23:59:59.999', params); // 86399999\nmaskitoParseTime('12:3', params); // 43380000 (parsed like '12:30:00.000')\n\nmaskitoStringifyTime(86399999, params); // '23:59:59.999'\n```\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/time-mask-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormControl, ReactiveFormsModule} from '@angular/forms';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath, DocExamplePrimaryTab} from '@demo/constants';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {MaskitoOptions} from '@maskito/core';\nimport {\n    type MaskitoTimeMode,\n    maskitoTimeOptionsGenerator,\n    type MaskitoTimeParams,\n    type MaskitoTimeSegments,\n} from '@maskito/kit';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport {TimeMaskDocExample1} from './examples/1-modes/component';\nimport {TimeMaskDocExample2} from './examples/2-am-pm/component';\nimport {TimeMaskDocExample3} from './examples/3-step/component';\nimport {TimeMaskDocExample4} from './examples/4-affixes/component';\nimport {TimeMaskDocExample5} from './examples/5-time-segments-min-max/component';\n\n@Component({\n    selector: 'time-mask-doc',\n    imports: [\n        MaskitoDirective,\n        ReactiveFormsModule,\n        RouterLink,\n        TimeMaskDocExample1,\n        TimeMaskDocExample2,\n        TimeMaskDocExample3,\n        TimeMaskDocExample4,\n        TimeMaskDocExample5,\n        TuiAddonDoc,\n        TuiInputModule,\n        TuiLink,\n        TuiNotification,\n        TuiTextfieldControllerModule,\n    ],\n    templateUrl: './time-mask-doc.template.html',\n    styleUrl: './time-mask-doc.style.less',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class TimeMaskDocComponent implements Required<MaskitoTimeParams> {\n    protected pages = DemoPath;\n\n    protected readonly maskitoParseStringifyTimeDemo =\n        import('./examples/maskito-parse-stringify-time-demo.md');\n\n    protected readonly modeExample1: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/1-modes/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly amPmExample2: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/2-am-pm/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly stepExample3: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/3-step/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly affixesExample4: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/4-affixes/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly timeSegmentsMinMaxExample5: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/5-time-segments-min-max/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected apiPageControl = new FormControl('');\n\n    protected readonly modeOptions = [\n        'HH:MM',\n        'HH:MM AA',\n        'HH:MM:SS',\n        'HH:MM:SS AA',\n        'HH:MM:SS.MSS',\n        'HH:MM:SS.MSS AA',\n        'HH',\n        'HH AA',\n        'MM:SS.MSS',\n        'SS.MSS',\n        'MM:SS',\n    ] as const satisfies readonly MaskitoTimeMode[];\n\n    protected readonly timeSegmentMaxValuesOptions = [\n        {},\n        {hours: 23, minutes: 59, seconds: 59, milliseconds: 999},\n        {hours: 11},\n        {hours: 5, minutes: 5, seconds: 5, milliseconds: 5},\n    ] as const satisfies ReadonlyArray<Partial<MaskitoTimeSegments<number>>>;\n\n    protected readonly timeSegmentMinValuesOptions = [\n        {},\n        {hours: 1},\n    ] as const satisfies ReadonlyArray<Partial<MaskitoTimeSegments<number>>>;\n\n    public mode: MaskitoTimeMode = this.modeOptions[0];\n    public timeSegmentMinValues = this.timeSegmentMinValuesOptions[0];\n    public timeSegmentMaxValues = this.timeSegmentMaxValuesOptions[0];\n    public prefix = '';\n    public postfix = '';\n\n    public step = 0;\n    public maskitoOptions: MaskitoOptions = maskitoTimeOptionsGenerator(this);\n\n    protected updateOptions(): void {\n        this.maskitoOptions = maskitoTimeOptionsGenerator(this);\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/time-mask-doc.style.less",
    "content": ".input-time {\n    max-inline-size: 25rem;\n\n    &:not(:last-child) {\n        margin-block-end: 1rem;\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/kit/time/time-mask-doc.template.html",
    "content": "<tui-doc-page\n    header=\"Time\"\n    package=\"KIT\"\n>\n    <ng-template pageTab>\n        Use\n        <code>maskitoTimeOptionsGenerator</code>\n        to create a mask for time input.\n\n        <tui-notification\n            size=\"m\"\n            class=\"tui-space_top-4\"\n        >\n            <div>\n                Despite the name of the mask, element's raw value is still string.\n\n                <p>\n                    Use\n                    <code>maskitoParseTime</code>\n                    to get milliseconds from masked string.\n                </p>\n                <p>\n                    Use\n                    <code>maskitoStringifyTime</code>\n                    to get the masked string from milliseconds.\n                </p>\n\n                <tui-doc-code [code]=\"maskitoParseStringifyTimeDemo\" />\n            </div>\n        </tui-notification>\n\n        <tui-doc-example\n            id=\"mode\"\n            heading=\"Mode\"\n            [content]=\"modeExample1\"\n            [description]=\"modeDescription\"\n        >\n            <ng-template #modeDescription>\n                <p class=\"tui-space_top-0 tui-space_bottom-1\">\n                    Use\n                    <code>mode</code>\n                    property to set time format. See the full list of available mode on\n                    <a\n                        routerLink=\"/{{ pages.Time }}/API\"\n                        tuiLink\n                    >\n                        API page\n                    </a>\n                    of the documentation.\n                </p>\n            </ng-template>\n            <time-mask-doc-example-1 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"am-pm\"\n            heading=\"AM / PM\"\n            [content]=\"amPmExample2\"\n            [description]=\"amPmDescription\"\n        >\n            <ng-template #amPmDescription>\n                Any\n                <code>mode</code>\n                ending with\n                <code>AA</code>\n                is 12-hour time format with meridiem part.\n            </ng-template>\n            <time-mask-doc-example-2 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"step\"\n            heading=\"Arrows stepping\"\n            [content]=\"stepExample3\"\n            [description]=\"stepDescription\"\n        >\n            <ng-template #stepDescription>\n                <p class=\"tui-space_top-0 tui-space_bottom-1\">\n                    Property\n                    <code>step</code>\n                    allows you to increment/decrement time segments by pressing\n                    <code>ArrowUp</code>\n                    /\n                    <code>ArrowDown</code>\n                    .\n                </p>\n\n                <p class=\"tui-space_top-0\">\n                    Use\n                    <code>step === 0</code>\n                    (default value) to disable this feature.\n                </p>\n            </ng-template>\n            <time-mask-doc-example-3 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"affixes\"\n            heading=\"Affixes\"\n            [content]=\"affixesExample4\"\n            [description]=\"affixesDescription\"\n        >\n            <ng-template #affixesDescription>\n                Use\n                <code>prefix</code>\n                /\n                <code>postfix</code>\n                parameters to set non-removable text before / after the time.\n            </ng-template>\n            <time-mask-doc-example-4 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"time-segment-min-max\"\n            heading=\"Min / max value for every time segment\"\n            [content]=\"timeSegmentsMinMaxExample5\"\n            [description]=\"timeSegmentMinMaxDescription\"\n        >\n            <ng-template #timeSegmentMinMaxDescription>\n                <p class=\"tui-space_top-0 tui-space_bottom-1\">\n                    Property\n                    <code>timeSegmentMinValues</code>\n                    /\n                    <code>timeSegmentMaxValues</code>\n                    allows you to set min/max value for every time segment.\n                </p>\n\n                <p class=\"tui-space_top-0\">\n                    <strong>Time segments</strong>\n                    are units of the time which form time string. For example,\n                    <code>HH:MM</code>\n                    consists of two time segments: hours and minutes.\n                </p>\n            </ng-template>\n            <time-mask-doc-example-5 />\n        </tui-doc-example>\n    </ng-template>\n\n    <ng-template pageTab>\n        <tui-doc-demo [control]=\"apiPageControl\">\n            <ng-template>\n                <tui-input\n                    tuiTextfieldCustomContent=\"@tui.clock\"\n                    class=\"input-time\"\n                    [formControl]=\"apiPageControl\"\n                    [tuiTextfieldFiller]=\"prefix || postfix ? '' : mode.toLowerCase()\"\n                >\n                    Enter time\n                    <input\n                        inputmode=\"numeric\"\n                        tuiTextfieldLegacy\n                        [maskito]=\"maskitoOptions\"\n                    />\n                </tui-input>\n            </ng-template>\n        </tui-doc-demo>\n        <tui-doc-documentation>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"mode\"\n                documentationPropertyType=\"MaskitoTimeMode\"\n                [documentationPropertyValues]=\"modeOptions\"\n                [(documentationPropertyValue)]=\"mode\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Time format mode\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"timeSegmentMinValues\"\n                documentationPropertyType=\"MaskitoTimeSegments<number>\"\n                [documentationPropertyValues]=\"timeSegmentMinValuesOptions\"\n                [(documentationPropertyValue)]=\"timeSegmentMinValues\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Minimum value for each time segment\n\n                <p>\n                    <strong>Default:</strong>\n                    <br />\n                    <code>&#123;hours: 0&#125;</code>\n                     / \n                    <code>&#123;hours: 1&#125;</code>\n                    for\n                    <code>mode</code>\n                    without / with meridiem period\n                </p>\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"timeSegmentMaxValues\"\n                documentationPropertyType=\"MaskitoTimeSegments<number>\"\n                [documentationPropertyValues]=\"timeSegmentMaxValuesOptions\"\n                [(documentationPropertyValue)]=\"timeSegmentMaxValues\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Maximum value for each time segment\n\n                <p>\n                    <strong>Default:</strong>\n                    <br />\n                    <code>&#123;hours: 24&#125;</code>\n                     / \n                    <code>&#123;hours: 12&#125;</code>\n                    for\n                    <code>mode</code>\n                    without / with meridiem period\n                </p>\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"step\"\n                documentationPropertyType=\"number\"\n                [(documentationPropertyValue)]=\"step\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                The value by which the keyboard arrows increment/decrement time segments\n\n                <p>\n                    <strong>Default:</strong>\n                    <code>0</code>\n                    (disable stepping)\n                </p>\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"prefix\"\n                documentationPropertyType=\"string\"\n                [(documentationPropertyValue)]=\"prefix\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Uneditable text\n                <strong>before</strong>\n                time\n\n                <p>\n                    <strong>Default:</strong>\n                    empty string (no prefix).\n                </p>\n            </ng-template>\n\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"postfix\"\n                documentationPropertyType=\"string\"\n                [(documentationPropertyValue)]=\"postfix\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Uneditable text\n                <strong>after</strong>\n                time\n\n                <p>\n                    <strong>Default:</strong>\n                    empty string (no postfix).\n                </p>\n            </ng-template>\n        </tui-doc-documentation>\n    </ng-template>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/pages.ts",
    "content": "import {DemoPath} from '@demo/constants';\nimport type {TuiDocRoutePages} from '@taiga-ui/addon-doc';\n\nexport const DEMO_PAGES: TuiDocRoutePages = [\n    {\n        section: 'Getting started',\n        title: 'What is Maskito?',\n        route: DemoPath.WhatIsMaskito,\n        keywords: 'getting, started, what, is, maskito',\n    },\n    {\n        section: 'Getting started',\n        title: 'Maskito libraries',\n        route: DemoPath.MaskitoLibraries,\n        keywords: 'install, package, packages, maskito, npm, setup, explore, ecosystem',\n    },\n    {\n        section: 'Core concepts',\n        title: 'Overview',\n        route: DemoPath.CoreConceptsOverview,\n        keywords: 'core, concepts, overview',\n    },\n    {\n        section: 'Core concepts',\n        title: 'Mask expression',\n        route: DemoPath.MaskExpression,\n        keywords: 'core, concepts, mask, expression, reg, exp, fixed',\n    },\n    {\n        section: 'Core concepts',\n        title: 'Element state',\n        route: DemoPath.ElementState,\n        keywords: 'core, concepts, element, state',\n    },\n    {\n        section: 'Core concepts',\n        title: 'Processors',\n        route: DemoPath.Processors,\n        keywords:\n            'core, concepts, preprocessor, postprocessor, processor, element, state, elementState',\n    },\n    {\n        section: 'Core concepts',\n        title: 'Plugins',\n        route: DemoPath.Plugins,\n        keywords: 'core, concepts, extension, event, focus, blur',\n    },\n    {\n        section: 'Core concepts',\n        title: 'Overwrite mode',\n        route: DemoPath.OverwriteMode,\n        keywords: 'core, concepts, overwrite, mode, shift, replace',\n    },\n    {\n        section: 'Core concepts',\n        title: 'Transformer',\n        route: DemoPath.Transformer,\n        keywords: 'core, concepts, programmatic, patch, set, update, value',\n    },\n    {\n        section: 'Frameworks',\n        title: 'Angular',\n        route: DemoPath.Angular,\n        keywords: 'ng, angular, framework, addon',\n    },\n    {\n        section: 'Frameworks',\n        title: 'React',\n        route: DemoPath.React,\n        keywords: 'react, framework, addon',\n    },\n    {\n        section: 'Frameworks',\n        title: 'Vue',\n        route: DemoPath.Vue,\n        keywords: 'vue, framework, addon',\n    },\n    {\n        section: 'Kit',\n        title: 'Number',\n        route: DemoPath.Number,\n        keywords: 'digit, number, money, mask, kit, generator, big, int, integer, bigint',\n    },\n    {\n        section: 'Kit',\n        title: 'Time',\n        route: DemoPath.Time,\n        keywords: 'time, hour, minute, second, mask, kit, generator',\n    },\n    {\n        section: 'Kit',\n        title: 'Date',\n        route: DemoPath.Date,\n        keywords: 'date, day, month, year, mask, kit, generator',\n    },\n    {\n        section: 'Kit',\n        title: 'DateRange',\n        route: DemoPath.DateRange,\n        keywords: 'date, day, month, year, mask, range, kit, generator',\n    },\n    {\n        section: 'Kit',\n        title: 'DateTime',\n        route: DemoPath.DateTime,\n        keywords:\n            'date, day, month, year, mask, time, date-time, hour, minute, second, kit, generator',\n    },\n    {\n        section: 'Kit',\n        title: 'List of Plugins',\n        route: DemoPath.KitPlugins,\n        keywords: 'reject, caret, guard, event, handler, focus, blur, selection',\n    },\n    {\n        section: 'Addons',\n        title: '@maskito/phone',\n        route: DemoPath.PhonePackage,\n        keywords: 'phone, libphonenumber, international, generator',\n    },\n    {\n        section: 'Recipes',\n        title: 'Card',\n        route: DemoPath.Card,\n        keywords: 'card, credit, cvv, debit, mask, recipe',\n    },\n    {\n        section: 'Recipes',\n        title: 'Phone',\n        route: DemoPath.Phone,\n        keywords: 'phone, mobile, tel, telephone, mask, recipe',\n    },\n    {\n        section: 'Recipes',\n        title: 'Textarea',\n        route: DemoPath.Textarea,\n        keywords: 'textarea, latin, mask, recipe',\n    },\n    {\n        section: 'Recipes',\n        title: 'ContentEditable',\n        route: DemoPath.ContentEditable,\n        keywords: 'content, editable, contenteditable, contentEditable, mask, recipe',\n    },\n    {\n        section: 'Recipes',\n        title: 'With prefix',\n        route: DemoPath.Prefix,\n        keywords: 'prefix, before, recipe',\n    },\n    {\n        section: 'Recipes',\n        title: 'With postfix',\n        route: DemoPath.Postfix,\n        keywords: 'postfix, after, percent, am, pm, recipe',\n    },\n    {\n        section: 'Recipes',\n        title: 'With placeholder',\n        route: DemoPath.Placeholder,\n        keywords: 'guide, placeholder, fill, recipe',\n    },\n    {\n        section: 'Recipes',\n        title: 'Network address',\n        route: DemoPath.NetworkAddress,\n        keywords: 'ipv6, ipv4, ip, mac, address, network, recipe',\n    },\n    {\n        section: 'Other',\n        title: 'Browser support',\n        route: DemoPath.BrowserSupport,\n        keywords: 'chrome, safari, ie, edge, firefox, browser, support',\n    },\n    {\n        section: 'Other',\n        title: 'Supported <input /> types',\n        route: DemoPath.SupportedInputTypes,\n        keywords:\n            'input, type, text, password, search, tel, url, email, number, date, month',\n    },\n    {\n        section: 'Other',\n        title: 'Maskito in Real World Form',\n        route: DemoPath.RealWorldForm,\n        keywords: 'browser, autofill, showcase, in, action, demo',\n    },\n    {\n        section: 'Other',\n        title: 'Changelog',\n        route: 'https://github.com/taiga-family/maskito/blob/main/CHANGELOG.md',\n        target: '_blank',\n        keywords: 'release, change, changelog, archive, history',\n    },\n];\n"
  },
  {
    "path": "projects/demo/src/pages/phone/examples/1-basic/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'phone-doc-example-1',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.phone\"\n            [style.max-width.rem]=\"30\"\n            [(ngModel)]=\"value\"\n        >\n            Basic\n            <input\n                autocomplete=\"tel\"\n                inputmode=\"tel\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PhoneMaskDocExample1 {\n    protected value = '+7 771 931-1111';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/phone/examples/1-basic/mask.ts",
    "content": "import {maskitoPhoneOptionsGenerator} from '@maskito/phone';\nimport metadata from 'libphonenumber-js/min/metadata';\n\nexport default maskitoPhoneOptionsGenerator({countryIsoCode: 'KZ', metadata});\n"
  },
  {
    "path": "projects/demo/src/pages/phone/examples/2-validation/component.ts",
    "content": "import {AsyncPipe} from '@angular/common';\nimport {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {\n    type AbstractControl,\n    FormControl,\n    ReactiveFormsModule,\n    type ValidationErrors,\n    type ValidatorFn,\n} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiValidationError} from '@taiga-ui/cdk';\nimport {TuiError} from '@taiga-ui/core';\nimport {TuiFieldErrorPipe} from '@taiga-ui/kit';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\nimport {type CountryCode, isValidPhoneNumber} from 'libphonenumber-js/max';\n\nimport mask from './mask';\n\nfunction phoneValidator(countryCode: CountryCode): ValidatorFn {\n    return (control: AbstractControl): ValidationErrors | null => {\n        const valid = isValidPhoneNumber(control.value, countryCode);\n\n        return valid ? null : new TuiValidationError('Invalid number');\n    };\n}\n\n@Component({\n    selector: 'phone-doc-example-2',\n    imports: [\n        AsyncPipe,\n        MaskitoDirective,\n        ReactiveFormsModule,\n        TuiError,\n        TuiFieldErrorPipe,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.phone\"\n            [formControl]=\"control\"\n            [style.max-width.rem]=\"30\"\n        >\n            Basic\n            <input\n                autocomplete=\"tel\"\n                inputmode=\"tel\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n        <tui-error\n            [error]=\"[] | tuiFieldError | async\"\n            [formControl]=\"control\"\n        />\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PhoneMaskDocExample2 {\n    protected readonly control = new FormControl('+36 20 123-3122', phoneValidator('HU'));\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/phone/examples/2-validation/mask.ts",
    "content": "import {maskitoPhoneOptionsGenerator} from '@maskito/phone';\nimport metadata from 'libphonenumber-js/mobile/metadata';\n\nexport default maskitoPhoneOptionsGenerator({countryIsoCode: 'HU', metadata});\n"
  },
  {
    "path": "projects/demo/src/pages/phone/examples/3-non-strict/component.ts",
    "content": "import {ChangeDetectionStrategy, Component, inject} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {maskitoGetCountryFromNumber} from '@maskito/phone';\nimport {isSafari} from '@ng-web-apis/platform';\nimport {TUI_IS_IOS, tuiInjectElement} from '@taiga-ui/cdk';\nimport {TuiFlagPipe} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\nimport metadata from 'libphonenumber-js/min/metadata';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'phone-doc-example-3',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiFlagPipe,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldCustomContent]=\"countryIsoCode ? flag : '@tui.phone'\"\n            [(ngModel)]=\"value\"\n        >\n            Non-strict\n            <input\n                autocomplete=\"tel\"\n                inputmode=\"tel\"\n                tuiTextfieldLegacy\n                [attr.pattern]=\"pattern\"\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n\n        <ng-template #flag>\n            <img\n                width=\"28\"\n                [attr.alt]=\"countryIsoCode\"\n                [src]=\"countryIsoCode | tuiFlag\"\n                [style.border-radius.%]=\"50\"\n            />\n        </ng-template>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PhoneMaskDocExample3 {\n    /**\n     * https://github.com/taiga-family/maskito/pull/2165\n     * TODO: delete after bumping Safari support to 18+\n     */\n    protected readonly pattern =\n        isSafari(tuiInjectElement()) || inject(TUI_IS_IOS) ? '+[0-9-]{1,20}' : '';\n\n    protected value = '';\n    protected readonly mask = mask;\n\n    protected get countryIsoCode(): string {\n        return maskitoGetCountryFromNumber(this.value, metadata) ?? '';\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/phone/examples/3-non-strict/mask.ts",
    "content": "import {maskitoPhoneOptionsGenerator} from '@maskito/phone';\nimport metadata from 'libphonenumber-js/min/metadata';\n\nexport default maskitoPhoneOptionsGenerator({\n    metadata,\n    strict: false,\n    countryIsoCode: 'RU',\n});\n"
  },
  {
    "path": "projects/demo/src/pages/phone/examples/4-lazy-metadata/component.ts",
    "content": "import {ChangeDetectionStrategy, Component, type OnInit} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {MASKITO_DEFAULT_OPTIONS} from '@maskito/core';\nimport {maskitoPhoneOptionsGenerator} from '@maskito/phone';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\n@Component({\n    selector: 'phone-doc-example-4',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.phone\"\n            [style.max-width.rem]=\"30\"\n            [(ngModel)]=\"value\"\n        >\n            Lazy metadata\n            <input\n                autocomplete=\"tel\"\n                inputmode=\"tel\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PhoneMaskDocExample4 implements OnInit {\n    protected value = '+7 920 123-4567';\n    protected mask = MASKITO_DEFAULT_OPTIONS;\n\n    public ngOnInit(): void {\n        import('libphonenumber-js/min/metadata').then(({default: metadata}) => () => {\n            this.mask = maskitoPhoneOptionsGenerator({countryIsoCode: 'RU', metadata});\n        });\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/phone/examples/4-lazy-metadata/simple.md",
    "content": "```js\nimport {Maskito, MASKITO_DEFAULT_OPTIONS} from '@maskito/core';\nimport {maskitoPhoneOptionsGenerator} from '@maskito/phone';\n\nconst element = document.querySelector('input,textarea');\n\nlet maskedInput;\n\n(async function initMask() {\n  const maskitoOptions = maskitoPhoneOptionsGenerator({\n    countryIsoCode: 'RU',\n    metadata: await import('libphonenumber-js/min/metadata').then((m) => m.default),\n  });\n\n  maskedInput = new Maskito(element, maskitoOptions);\n})();\n\n// Call this function when the element is detached from DOM\nmaskedInput.destroy();\n```\n"
  },
  {
    "path": "projects/demo/src/pages/phone/examples/5-focus-blur-events/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiFlagPipe} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'phone-doc-example-5',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiFlagPipe,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            #textfield\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldCustomContent]=\"flag\"\n            [(ngModel)]=\"value\"\n        >\n            {{\n                textfield.focused ? 'Blur me to remove prefix' : 'Focus me to see prefix'\n            }}\n            <input\n                autocomplete=\"tel\"\n                inputmode=\"tel\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            />\n\n            <ng-template #flag>\n                <img\n                    alt=\"Turkish flag\"\n                    width=\"28\"\n                    [src]=\"'TR' | tuiFlag\"\n                    [style.border-radius.%]=\"50\"\n                />\n            </ng-template>\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PhoneMaskDocExample5 {\n    protected value = '';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/phone/examples/5-focus-blur-events/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {maskitoAddOnFocusPlugin, maskitoRemoveOnBlurPlugin} from '@maskito/kit';\nimport {maskitoPhoneOptionsGenerator} from '@maskito/phone';\nimport {getCountryCallingCode} from 'libphonenumber-js/core';\nimport metadata from 'libphonenumber-js/min/metadata';\n\nconst countryIsoCode = 'TR';\nconst code = getCountryCallingCode(countryIsoCode, metadata);\nconst prefix = `+${code} `;\n\nconst phoneOptions = maskitoPhoneOptionsGenerator({\n    metadata,\n    countryIsoCode,\n    strict: true,\n});\n\nexport default {\n    ...phoneOptions,\n    plugins: [\n        ...phoneOptions.plugins,\n        maskitoAddOnFocusPlugin(prefix),\n        maskitoRemoveOnBlurPlugin(prefix),\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/phone/examples/6-national-format/component.ts",
    "content": "import {ChangeDetectionStrategy, Component, inject} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {isSafari} from '@ng-web-apis/platform';\nimport {TUI_IS_IOS, tuiInjectElement} from '@taiga-ui/cdk';\nimport {TuiFlagPipe} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'phone-doc-example-6',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiFlagPipe,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"30\"\n            [tuiTextfieldCustomContent]=\"flag\"\n            [(ngModel)]=\"value\"\n        >\n            National Format (US)\n            <input\n                autocomplete=\"tel\"\n                inputmode=\"tel\"\n                tuiTextfieldLegacy\n                [attr.pattern]=\"pattern\"\n                [maskito]=\"mask\"\n            />\n        </tui-input>\n\n        <ng-template #flag>\n            <img\n                alt=\"US\"\n                width=\"28\"\n                [src]=\"'US' | tuiFlag\"\n                [style.border-radius.%]=\"50\"\n            />\n        </ng-template>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PhoneMaskDocExample6 {\n    protected value = '';\n    protected readonly mask = mask;\n\n    /**\n     * Pattern for iOS Safari to allow phone number input.\n     * National format doesn't include '+', so pattern is different.\n     * TODO: delete after bumping Safari support to 18+\n     */\n    protected readonly pattern =\n        isSafari(tuiInjectElement()) || inject(TUI_IS_IOS) ? '[0-9()-]{1,20}' : '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/phone/examples/6-national-format/mask.ts",
    "content": "import {maskitoPhoneOptionsGenerator} from '@maskito/phone';\nimport metadata from 'libphonenumber-js/min/metadata';\n\n/**\n * National format phone mask for United States.\n * Displays phone numbers in national format: (XXX) XXX-XXXX\n * without the country code prefix.\n */\nexport default maskitoPhoneOptionsGenerator({\n    countryIsoCode: 'US',\n    metadata,\n    format: 'NATIONAL',\n});\n"
  },
  {
    "path": "projects/demo/src/pages/phone/phone-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component, inject} from '@angular/core';\nimport {FormControl, ReactiveFormsModule} from '@angular/forms';\nimport {DocExamplePrimaryTab} from '@demo/constants';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {MaskitoOptions} from '@maskito/core';\nimport {maskitoAddOnFocusPlugin, maskitoRemoveOnBlurPlugin} from '@maskito/kit';\nimport {maskitoPhoneOptionsGenerator, type MaskitoPhoneParams} from '@maskito/phone';\nimport {isSafari} from '@ng-web-apis/platform';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {CHAR_PLUS, TUI_IS_IOS, tuiInjectElement} from '@taiga-ui/cdk';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\nimport type {MetadataJson} from 'libphonenumber-js';\nimport {\n    type CountryCode,\n    getCountries,\n    getCountryCallingCode,\n} from 'libphonenumber-js/core';\nimport maxMetadata from 'libphonenumber-js/max/metadata';\nimport minMetadata from 'libphonenumber-js/min/metadata';\nimport mobileMetadata from 'libphonenumber-js/mobile/metadata';\n\nimport {PhoneMaskDocExample1} from './examples/1-basic/component';\nimport {PhoneMaskDocExample2} from './examples/2-validation/component';\nimport {PhoneMaskDocExample3} from './examples/3-non-strict/component';\nimport {PhoneMaskDocExample4} from './examples/4-lazy-metadata/component';\nimport {PhoneMaskDocExample5} from './examples/5-focus-blur-events/component';\nimport {PhoneMaskDocExample6} from './examples/6-national-format/component';\n\nconst metadataSets = {\n    min: minMetadata,\n    max: maxMetadata,\n    mobile: mobileMetadata,\n} as const satisfies Record<string, MetadataJson>;\n\ntype GeneratorOptions = Required<Parameters<typeof maskitoPhoneOptionsGenerator>[0]>;\n\ntype MetadataName = keyof typeof metadataSets;\n\n@Component({\n    selector: 'phone-doc',\n    imports: [\n        MaskitoDirective,\n        PhoneMaskDocExample1,\n        PhoneMaskDocExample2,\n        PhoneMaskDocExample3,\n        PhoneMaskDocExample4,\n        PhoneMaskDocExample5,\n        PhoneMaskDocExample6,\n        ReactiveFormsModule,\n        TuiAddonDoc,\n        TuiInputModule,\n        TuiLink,\n        TuiNotification,\n        TuiTextfieldControllerModule,\n    ],\n    templateUrl: './phone-doc.template.html',\n    styleUrl: './phone-doc.style.less',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class PhoneDocComponent implements GeneratorOptions {\n    private readonly isApple = isSafari(tuiInjectElement()) || inject(TUI_IS_IOS);\n\n    protected apiPageControl = new FormControl('');\n\n    protected readonly basic: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/1-basic/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly validation: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/2-validation/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n        [DocExamplePrimaryTab.Angular]: import(\n            './examples/2-validation/component.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly nonStrict: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/3-non-strict/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n        [DocExamplePrimaryTab.Angular]: import(\n            './examples/3-non-strict/component.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly lazyMetadata: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.Angular]: import(\n            './examples/4-lazy-metadata/component.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n        [DocExamplePrimaryTab.JavaScript]: import('./examples/4-lazy-metadata/simple.md'),\n    };\n\n    protected readonly focusBlurEvents: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/5-focus-blur-events/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly nationalFormat: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/6-national-format/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    public strict = true;\n    public countryIsoCode: CountryCode = 'RU';\n    public separator = '-';\n    public format: NonNullable<MaskitoPhoneParams['format']> = 'INTERNATIONAL';\n    public metadataVariants = Object.keys(metadataSets) as readonly MetadataName[];\n    public selectedMetadata = this.metadataVariants[0]!;\n    public countryCodeVariants = getCountries(this.metadata);\n    public separatorVariants = ['-', ' '];\n    public formatVariants: Array<NonNullable<MaskitoPhoneParams['format']>> = [\n        'INTERNATIONAL',\n        'NATIONAL',\n    ];\n\n    public maskitoOptions = this.computeOptions();\n\n    public get metadata(): MetadataJson {\n        return metadataSets[this.selectedMetadata];\n    }\n\n    /**\n     * Pattern for iOS Safari to allow phone number input.\n     * Different patterns for international vs national format.\n     * TODO: delete after bumping Safari support to 18+\n     */\n    protected get pattern(): string {\n        if (!this.isApple) {\n            return '';\n        }\n\n        return this.format === 'NATIONAL' ? '[0-9()-]{1,20}' : '+[0-9-]{1,20}';\n    }\n\n    protected updateOptions(): void {\n        this.maskitoOptions = this.computeOptions();\n    }\n\n    private computeOptions(): Required<MaskitoOptions> {\n        const options = maskitoPhoneOptionsGenerator(this);\n        const code = getCountryCallingCode(this.countryIsoCode, this.metadata);\n        const prefix = `${CHAR_PLUS}${code} `;\n\n        return this.strict && this.format === 'INTERNATIONAL'\n            ? {\n                  ...options,\n                  plugins: [\n                      ...options.plugins,\n                      maskitoRemoveOnBlurPlugin(prefix),\n                      maskitoAddOnFocusPlugin(prefix),\n                  ],\n              }\n            : options;\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/phone/phone-doc.style.less",
    "content": ".phone {\n    max-inline-size: 25rem;\n\n    &:not(:last-child) {\n        margin-block-end: 1rem;\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/phone/phone-doc.template.html",
    "content": "<tui-doc-page\n    header=\"Phone\"\n    package=\"PHONE\"\n>\n    <ng-template pageTab>\n        <p>\n            This mask is based on the\n            <a\n                href=\"https://www.npmjs.com/package/libphonenumber-js\"\n                tuiLink\n            >\n                libphonenumber-js\n            </a>\n            package.\n        </p>\n        Use\n        <code>maskitoPhoneOptionsGenerator</code>\n        to create a mask for phone input.\n\n        <tui-doc-example\n            id=\"basic\"\n            description=\"Kazakhstan phone example\"\n            heading=\"basic\"\n            [content]=\"basic\"\n        >\n            <phone-doc-example-1 />\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"validation\"\n            heading=\"validation\"\n            [content]=\"validation\"\n            [description]=\"description\"\n        >\n            <phone-doc-example-2 />\n\n            <ng-template #description>\n                <p class=\"tui-space_top-0\">\n                    For validating phone number you can use\n                    <code>isValidPhoneNumber</code>\n                    ,\n                    <code>isPossiblePhoneNumber</code>\n                    functions from\n                    <a\n                        href=\"https://www.npmjs.com/package/libphonenumber-js\"\n                        tuiLink\n                    >\n                        libphonenumber-js\n                    </a>\n                    package.\n                    <a\n                        href=\"https://www.npmjs.com/package/libphonenumber-js\"\n                        tuiLink\n                    >\n                        Read more\n                    </a>\n                </p>\n\n                <p>Below is an example of a Hungarian phone mask with an angular validator.</p>\n            </ng-template>\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"non-strict\"\n            heading=\"Non-strict mask\"\n            [content]=\"nonStrict\"\n            [description]=\"nonStrictDescription\"\n        >\n            <phone-doc-example-3 />\n\n            <ng-template #nonStrictDescription>\n                <p>\n                    Setting the\n                    <code>strict</code>\n                    option to\n                    <code>false</code>\n                    enables non-strict mask mode and allow user to type any country phone number.\n                </p>\n                <p>\n                    The\n                    <code>countryIsoCode</code>\n                    option is optional in that case, but if you specify it, the mask will try to add that country's\n                    calling code when you try to insert a phone number without a calling code.\n                </p>\n            </ng-template>\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"lazy-metadata\"\n            heading=\"Lazy metadata\"\n            [content]=\"lazyMetadata\"\n            [description]=\"lazyDescription\"\n        >\n            <phone-doc-example-4 />\n\n            <ng-template #lazyDescription>\n                <p>You can load metadata lazily, below is an example of how to do it in Angular.</p>\n                <p>\n                    You can also\n                    <a\n                        href=\"https://gitlab.com/catamphetamine/libphonenumber-js#customizing-metadata\"\n                        tuiLink\n                    >\n                        customize the metadata\n                    </a>\n                    to reduce metadata size. See instructions\n                    <a\n                        href=\"https://gitlab.com/catamphetamine/libphonenumber-metadata-generator\"\n                        tuiLink\n                    >\n                        here\n                    </a>\n                </p>\n            </ng-template>\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"focus-blur\"\n            heading=\"Focus & Blur events\"\n            [content]=\"focusBlurEvents\"\n            [description]=\"focusBlurEventDescription\"\n        >\n            <phone-doc-example-5 />\n\n            <ng-template #focusBlurEventDescription>\n                Use\n                <code>maskitoAddOnFocusPlugin</code>\n                /\n                <code>maskitoRemoveOnBlurPlugin</code>\n                to mutate textfield's value on focus/blur events.\n            </ng-template>\n        </tui-doc-example>\n\n        <tui-doc-example\n            id=\"national-format\"\n            heading=\"National format\"\n            [content]=\"nationalFormat\"\n            [description]=\"nationalFormatDescription\"\n        >\n            <phone-doc-example-6 />\n\n            <ng-template #nationalFormatDescription>\n                <p>\n                    Setting the\n                    <code>format</code>\n                    option to\n                    <code>'NATIONAL'</code>\n                    displays phone numbers in the country's national format without the country code prefix.\n                </p>\n                <p>\n                    For example, US numbers will be formatted as\n                    <code>(212) 343-3355</code>\n                    instead of\n                    <code>+1 212 343-3355</code>\n                    .\n                </p>\n                <tui-notification size=\"m\">\n                    National format only works with defined\n                    <code>countryIsoCode</code>\n                    property!\n                </tui-notification>\n            </ng-template>\n        </tui-doc-example>\n    </ng-template>\n\n    <ng-template pageTab>\n        <tui-doc-demo [control]=\"apiPageControl\">\n            <ng-template>\n                <tui-input\n                    tuiTextfieldCustomContent=\"@tui.phone\"\n                    class=\"phone\"\n                    [formControl]=\"apiPageControl\"\n                >\n                    Enter phone\n                    <input\n                        autocomplete=\"tel\"\n                        inputmode=\"tel\"\n                        tuiTextfieldLegacy\n                        [attr.pattern]=\"pattern\"\n                        [maskito]=\"maskitoOptions\"\n                    />\n                </tui-input>\n            </ng-template>\n        </tui-doc-demo>\n        <tui-doc-documentation>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"metadata\"\n                documentationPropertyType=\"MetadataJson\"\n                [documentationPropertyValues]=\"metadataVariants\"\n                [(documentationPropertyValue)]=\"selectedMetadata\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                List of phone number parsing and formatting rules for all countries.\n\n                <p>\n                    The complete list of those rules is huge, so\n                    <code>libphonenumber-js</code>\n                    provides a way to optimize bundle size by choosing between\n                    <code>max</code>\n                    ,\n                    <code>min</code>\n                    ,\n                    <code>mobile</code>\n                    metadata.\n                </p>\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"countryIsoCode\"\n                documentationPropertyType=\"string\"\n                [documentationPropertyValues]=\"countryCodeVariants\"\n                [(documentationPropertyValue)]=\"countryIsoCode\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Country ISO-code\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"strict\"\n                documentationPropertyType=\"boolean\"\n                [(documentationPropertyValue)]=\"strict\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                If true, it allows to enter only phone number of selected country (see countryIsoCode property). If\n                false, all country phone number is allowed.\n                <p>\n                    <strong>Default:</strong>\n                    <code>true</code>\n                </p>\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"separator\"\n                documentationPropertyType=\"string\"\n                [documentationPropertyValues]=\"separatorVariants\"\n                [(documentationPropertyValue)]=\"separator\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Separator between groups of numbers in a phone number (excluding country code and area code).\n                <p>\n                    <strong>Default:</strong>\n                    <code>-</code>\n                </p>\n            </ng-template>\n            <ng-template\n                documentationPropertyMode=\"input\"\n                documentationPropertyName=\"format\"\n                documentationPropertyType=\"'NATIONAL' | 'INTERNATIONAL'\"\n                [documentationPropertyValues]=\"formatVariants\"\n                [(documentationPropertyValue)]=\"format\"\n                (documentationPropertyValueChange)=\"updateOptions()\"\n            >\n                Phone number format.\n                <code>'INTERNATIONAL'</code>\n                includes the country code prefix (e.g.,\n                <code>+1 212 343-3355</code>\n                ).\n                <code>'NATIONAL'</code>\n                uses country-specific formatting without the country code (e.g.,\n                <code>(212) 343-3355</code>\n                for US).\n                <p>\n                    <strong>Note:</strong>\n                    <code>'NATIONAL'</code>\n                    format only works with\n                    <code>strict: true</code>\n                    mode.\n                </p>\n                <p>\n                    <strong>Default:</strong>\n                    <code>'INTERNATIONAL'</code>\n                </p>\n            </ng-template>\n        </tui-doc-documentation>\n    </ng-template>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/card/card-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath} from '@demo/constants';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink} from '@taiga-ui/core';\n\nimport {CardDocExample1} from './examples/1-basic/component';\n\n@Component({\n    selector: 'card-doc',\n    imports: [CardDocExample1, RouterLink, TuiAddonDoc, TuiLink],\n    templateUrl: './card-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class CardDocComponent {\n    protected readonly maskExpressionDocPage = `/${DemoPath.MaskExpression}`;\n    protected readonly dateMaskDocPage = `/${DemoPath.Date}`;\n\n    protected readonly cardExample1: Record<string, TuiRawLoaderContent> = {\n        TypeScript: import('./examples/1-basic/component.ts?raw', {\n            with: {loader: 'text'},\n        }),\n        HTML: import('./examples/1-basic/template.html'),\n    };\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/card/card-doc.template.html",
    "content": "<tui-doc-page\n    header=\"Card\"\n    package=\"Recipes\"\n>\n    <section>\n        <p class=\"tui-space_top-0\">\n            Creating mask for credit card input requires basic understanding of the following topics:\n        </p>\n\n        <ul class=\"tui-list\">\n            <li class=\"tui-list__item\">\n                <a\n                    tuiLink\n                    [routerLink]=\"maskExpressionDocPage\"\n                >\n                    Pattern mask expression\n                </a>\n            </li>\n            <li class=\"tui-list__item\">\n                How to use\n                <a\n                    tuiLink\n                    [routerLink]=\"dateMaskDocPage\"\n                >\n                    Date\n                </a>\n                mask from\n                <code>&#64;maskito/kit</code>\n            </li>\n        </ul>\n    </section>\n\n    <tui-doc-example\n        id=\"card\"\n        [content]=\"cardExample1\"\n        [style.padding.px]=\"0\"\n    >\n        <card-doc-example-1 />\n    </tui-doc-example>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/card/examples/1-basic/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {MaskitoOptions} from '@maskito/core';\nimport {maskitoDateOptionsGenerator} from '@maskito/kit';\nimport {TuiGroup} from '@taiga-ui/core';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\n@Component({\n    selector: 'card-doc-example-1',\n    imports: [MaskitoDirective, ReactiveFormsModule, TuiGroup, TuiInputModule],\n    templateUrl: './template.html',\n    styleUrl: './style.less',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CardDocExample1 {\n    protected readonly cardMask: MaskitoOptions = {\n        mask: [\n            ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n            ' ',\n            ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n            ' ',\n            ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n            ' ',\n            ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n            ' ',\n            ...Array.from<RegExp>({length: 3}).fill(/\\d/),\n        ],\n    };\n\n    protected readonly expiredMask = maskitoDateOptionsGenerator({\n        mode: 'mm/yy',\n        separator: '/',\n    });\n\n    protected readonly cvvMask: MaskitoOptions = {\n        mask: Array.from<RegExp>({length: 3}).fill(/\\d/),\n    };\n\n    protected readonly form = new FormGroup({\n        cardNumber: new FormControl(''),\n        expire: new FormControl(''),\n        cvv: new FormControl(''),\n    });\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/card/examples/1-basic/style.less",
    "content": ".wrapper {\n    display: flex;\n    max-inline-size: 30rem;\n}\n\n.number {\n    flex: 1 1 11rem;\n}\n\n.cvv {\n    flex: 1 0 4rem;\n}\n\n.expired {\n    flex: 1 0 5rem;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/card/examples/1-basic/template.html",
    "content": "<form\n    autocomplete=\"on\"\n    tuiGroup\n    class=\"wrapper\"\n    [formGroup]=\"form\"\n>\n    <tui-input\n        formControlName=\"cardNumber\"\n        class=\"number\"\n    >\n        Card number\n        <input\n            autocomplete=\"cc-number\"\n            inputmode=\"numeric\"\n            placeholder=\"0000 0000 0000 0000\"\n            tuiTextfieldLegacy\n            [maskito]=\"cardMask\"\n        />\n    </tui-input>\n    <tui-input\n        formControlName=\"expire\"\n        class=\"expired\"\n    >\n        EXP\n        <input\n            autocomplete=\"cc-exp\"\n            inputmode=\"numeric\"\n            placeholder=\"mm/yy\"\n            tuiTextfieldLegacy\n            [maskito]=\"expiredMask\"\n        />\n    </tui-input>\n    <tui-input\n        formControlName=\"cvv\"\n        class=\"cvv\"\n    >\n        CVV\n        <input\n            autocomplete=\"cc-csc\"\n            inputmode=\"numeric\"\n            placeholder=\"000\"\n            tuiTextfieldLegacy\n            [maskito]=\"cvvMask\"\n        />\n    </tui-input>\n</form>\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/content-editable/content-editable-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath, DocExamplePrimaryTab} from '@demo/constants';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\n\nimport {ContentEditableDocExample1} from './examples/1-time/component';\nimport {ContentEditableDocExample2} from './examples/2-multi-line/component';\n\n@Component({\n    selector: 'content-editable-doc',\n    imports: [\n        ContentEditableDocExample1,\n        ContentEditableDocExample2,\n        RouterLink,\n        TuiAddonDoc,\n        TuiLink,\n        TuiNotification,\n    ],\n    templateUrl: './content-editable-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class ContentEditableDocComponent {\n    protected readonly coreConceptsOverviewDocPage = `/${DemoPath.CoreConceptsOverview}`;\n    protected readonly timeMaskDocPage = `/${DemoPath.Time}`;\n    protected readonly angularDocPage = `/${DemoPath.Angular}`;\n    protected readonly reactDocPage = `/${DemoPath.React}`;\n    protected readonly vueDocPage = `/${DemoPath.Vue}`;\n    protected readonly maskitoWithContentEditableDemo =\n        import('./examples/maskito-with-content-editable.md');\n\n    protected readonly contentEditableExample1: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/1-time/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n        [DocExamplePrimaryTab.JavaScript]: import('./examples/vanilla-js-tab.md'),\n        [DocExamplePrimaryTab.Angular]: import('./examples/1-time/component.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly contentEditableExample2: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/2-multi-line/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n        [DocExamplePrimaryTab.JavaScript]: import('./examples/vanilla-js-tab.md'),\n        [DocExamplePrimaryTab.Angular]: import(\n            './examples/2-multi-line/component.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/content-editable/content-editable-doc.template.html",
    "content": "<tui-doc-page\n    header=\"ContentEditable\"\n    package=\"Recipes\"\n>\n    <section>\n        <p class=\"tui-space_top-0 tui-space_bottom-4\">\n            You can use\n            <strong>Maskito</strong>\n            with\n            <a\n                href=\"https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/contentEditable\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n                tuiLink\n            >\n                <code>contentEditable</code>\n            </a>\n            too.\n        </p>\n        <p>\n            Just wrap the element with\n            <code>maskitoAdaptContentEditable</code>\n            utility and use\n            <strong>Maskito</strong>\n            in the same way as\n            <code>HTMLInputElement</code>\n            /\n            <code>HTMLTextAreaElement</code>\n            .\n        </p>\n\n        <tui-notification\n            appearance=\"success\"\n            size=\"m\"\n            class=\"tui-space_bottom-4\"\n        >\n            <div>\n                No need to use\n                <code>maskitoAdaptContentEditable</code>\n                if you use\n                <a\n                    tuiLink\n                    [routerLink]=\"angularDocPage\"\n                >\n                    <code>&#64;maskito/angular</code>\n                </a>\n                ,\n                <a\n                    tuiLink\n                    [routerLink]=\"reactDocPage\"\n                >\n                    <code>&#64;maskito/react</code>\n                </a>\n                or\n                <a\n                    tuiLink\n                    [routerLink]=\"vueDocPage\"\n                >\n                    <code>&#64;maskito/vue</code>\n                </a>\n                with the default element predicate (it will be wrapped automatically).\n            </div>\n        </tui-notification>\n\n        <tui-doc-code [code]=\"maskitoWithContentEditableDemo\" />\n\n        <p class=\"tui-space_bottom-0\">\n            Learn more in the\n            <a\n                tuiLink\n                [routerLink]=\"coreConceptsOverviewDocPage\"\n            >\n                \"Core Concepts\"\n            </a>\n            section.\n        </p>\n    </section>\n\n    <tui-doc-example\n        id=\"time\"\n        [content]=\"contentEditableExample1\"\n        [heading]=\"heading1\"\n    >\n        <ng-template #heading1>\n            With built-in\n            <a\n                tuiLink\n                [routerLink]=\"timeMaskDocPage\"\n            >\n                <code>Time</code>\n            </a>\n            mask\n        </ng-template>\n        <content-editable-doc-example-1 />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"multi-line\"\n        heading=\"Multi-line support\"\n        [content]=\"contentEditableExample2\"\n        [description]=\"description2\"\n    >\n        <ng-template #description2>\n            Use\n            <code>white-space: pre</code>\n            for multi-line mode\n        </ng-template>\n        <content-editable-doc-example-2 />\n    </tui-doc-example>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/content-editable/examples/1-time/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {MaskitoDirective} from '@maskito/angular';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'content-editable-doc-example-1',\n    imports: [MaskitoDirective],\n    template: `\n        Meeting time:\n        <span\n            contenteditable=\"true\"\n            [maskito]=\"mask\"\n            [textContent]=\"initialValue\"\n        ></span>\n    `,\n    styles: [\n        ':host {font-size: 1.75rem}',\n        '[contenteditable] {border: 3px dashed lightgrey}',\n    ],\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ContentEditableDocExample1 {\n    protected initialValue = '12:00';\n    protected readonly mask = mask;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/content-editable/examples/1-time/mask.ts",
    "content": "import {maskitoTimeOptionsGenerator} from '@maskito/kit';\n\nexport default maskitoTimeOptionsGenerator({mode: 'HH:MM'});\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/content-editable/examples/2-multi-line/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {MaskitoDirective} from '@maskito/angular';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'content-editable-doc-example-2',\n    imports: [MaskitoDirective],\n    template: `\n        <i>Enter message:</i>\n        <p\n            contenteditable=\"true\"\n            [innerHTML]=\"initialText\"\n            [maskito]=\"mask\"\n        ></p>\n    `,\n    styles: [\n        `\n            [contenteditable] {\n                white-space: pre;\n                border: 3px dashed lightgrey;\n                max-width: 30rem;\n                padding: 1rem;\n            }\n        `,\n    ],\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ContentEditableDocExample2 {\n    protected readonly mask = mask;\n    protected initialText = `Hello, world!\nHow are you today?\nRead description of this example!`;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/content-editable/examples/2-multi-line/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nexport default {mask: /^[a-z\\s.,/!?]+$/i} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/content-editable/examples/maskito-with-content-editable.md",
    "content": "```ts\nimport {Maskito, maskitoAdaptContentEditable, MaskitoOptions} from '@maskito/core';\n\nconst maskitoOptions: MaskitoOptions = {\n  mask: /^\\d+$/,\n};\n\nconst element = document.querySelector<HTMLElement>('[contenteditable]')!;\n\nconst maskedInput = new Maskito(\n  maskitoAdaptContentEditable(element), // <-- This is the only difference\n  maskitoOptions,\n);\n```\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/content-editable/examples/vanilla-js-tab.md",
    "content": "```ts\nimport {Maskito, maskitoAdaptContentEditable} from '@maskito/core';\n\nimport maskitoOptions from './mask';\n\nconst element = document.querySelector<HTMLElement>('[contenteditable]')!;\n\nconst maskedInput = new Maskito(maskitoAdaptContentEditable(element), maskitoOptions);\n\nconsole.info('Call this function when the element is detached from DOM', maskedInput.destroy);\n```\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/network-address/examples/1-ipv6/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'network-address-doc-example-1',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"25\"\n            [(ngModel)]=\"value\"\n        >\n            Enter IPv6 address\n            <input\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NetworkAddressDocExample1 {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/network-address/examples/1-ipv6/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nconst HEX_GROUP = Array.from({length: 4}, () => /[0-9A-F]/i);\n\nexport default {\n    mask: Array.from({length: 8}, (_, i) => (i ? [':', ...HEX_GROUP] : HEX_GROUP)).flat(),\n    postprocessors: [({value, selection}) => ({value: value.toLowerCase(), selection})],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/network-address/examples/2-ipv4/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'network-address-doc-example-2',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"25\"\n            [(ngModel)]=\"value\"\n        >\n            Enter IPv4 address\n            <input\n                inputmode=\"numeric\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NetworkAddressDocExample2 {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/network-address/examples/2-ipv4/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nconst MAX_OCTET_VALUE = 255;\nconst MAX_OCTET_LENGTH = 3;\nconst MAX_OCTETS = 4;\nconst SEPARATOR = '.';\nconst DIGIT = /\\d/;\n\nexport default {\n    mask: ({value}) => {\n        const octets = value\n            .split(new RegExp(`(\\\\${SEPARATOR}|${DIGIT.source}{${MAX_OCTET_LENGTH}})`))\n            .filter((x) => x && x !== SEPARATOR);\n\n        return octets\n            .map((octet, i) => {\n                const length = Math.max(1, Math.min(MAX_OCTET_LENGTH, octet.length));\n                const group = Array.from({length}, () => DIGIT);\n                const last = i === octets.length - 1;\n\n                return last ? group : [...group, SEPARATOR];\n            })\n            .concat([octets.length ? [SEPARATOR, DIGIT] : [DIGIT]])\n            .slice(0, MAX_OCTETS)\n            .flat();\n    },\n    postprocessors: [\n        ({value, selection}) => ({\n            value: value\n                .split(SEPARATOR)\n                .map((v) => (Number(v) > MAX_OCTET_VALUE ? String(MAX_OCTET_VALUE) : v))\n                .join(SEPARATOR),\n            selection,\n        }),\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/network-address/examples/3-mac/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'network-address-doc-example-3',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"25\"\n            [(ngModel)]=\"value\"\n        >\n            Enter MAC address\n            <input\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NetworkAddressDocExample3 {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/network-address/examples/3-mac/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nconst HEX_GROUP = Array.from({length: 2}, () => /[0-9A-F]/i);\n\nexport default {\n    mask: Array.from({length: 6}, (_, i) => (i ? [':', ...HEX_GROUP] : HEX_GROUP)).flat(),\n    postprocessors: [({value, selection}) => ({value: value.toUpperCase(), selection})],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/network-address/network-address-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {DocExamplePrimaryTab} from '@demo/constants';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\n\nimport {NetworkAddressDocExample1} from './examples/1-ipv6/component';\nimport {NetworkAddressDocExample2} from './examples/2-ipv4/component';\nimport {NetworkAddressDocExample3} from './examples/3-mac/component';\n\n@Component({\n    selector: 'network-address-doc',\n    imports: [\n        NetworkAddressDocExample1,\n        NetworkAddressDocExample2,\n        NetworkAddressDocExample3,\n        TuiAddonDoc,\n    ],\n    templateUrl: './network-address-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class NetworkAddressDocComponent {\n    protected readonly ipv6Example1: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/1-ipv6/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly ipv4Example2: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/2-ipv4/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly macExample3: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/3-mac/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/network-address/network-address-doc.template.html",
    "content": "<tui-doc-page\n    header=\"Network address\"\n    package=\"Recipes\"\n>\n    <p class=\"tui-space_top-0\">\n        Use mask expression to create inputs for network addresses such as IPv6, IPv4, and MAC.\n    </p>\n\n    <tui-doc-example\n        id=\"ipv6\"\n        heading=\"IPv6\"\n        [content]=\"ipv6Example1\"\n        [description]=\"ipv6Description\"\n    >\n        <ng-template #ipv6Description>\n            This example demonstrates how to create an IPv6 address input via pattern mask expression with fixed colon\n            separators. A\n            <strong>postprocessor</strong>\n            converts all characters to lowercase.\n        </ng-template>\n        <network-address-doc-example-1 />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"ipv4\"\n        heading=\"IPv4\"\n        [content]=\"ipv4Example2\"\n        [description]=\"ipv4Description\"\n    >\n        <ng-template #ipv4Description>\n            This example demonstrates how to create an IPv4 address input via dynamic mask expression. It creates up to\n            4 octets separated by dots. Each octet allows up to 3 digits. A\n            <strong>postprocessor</strong>\n            clamps each octet value to a maximum of 255.\n        </ng-template>\n        <network-address-doc-example-2 />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"mac\"\n        heading=\"MAC\"\n        [content]=\"macExample3\"\n        [description]=\"macDescription\"\n    >\n        <ng-template #macDescription>\n            This example demonstrates how to create a MAC address input via pattern mask expression with fixed colon\n            separators. A\n            <strong>postprocessor</strong>\n            converts all characters to uppercase.\n        </ng-template>\n        <network-address-doc-example-3 />\n    </tui-doc-example>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/phone/examples/1-us-phone/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiFlagPipe} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'phone-doc-example-1',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiFlagPipe,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldCustomContent]=\"usFlag\"\n            [(ngModel)]=\"value\"\n        >\n            Enter a phone number\n            <input\n                inputmode=\"tel\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n\n            <ng-template #usFlag>\n                <img\n                    alt=\"Flag of the United States\"\n                    width=\"28\"\n                    [src]=\"'US' | tuiFlag\"\n                    [style.border-radius.%]=\"50\"\n                />\n            </ng-template>\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PhoneUSDocExample1 {\n    protected readonly maskitoOptions = mask;\n    protected value = '+1 (212) 555-2368';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/phone/examples/1-us-phone/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nexport default {\n    mask: [\n        '+',\n        '1',\n        ' ',\n        '(',\n        /\\d/,\n        /\\d/,\n        /\\d/,\n        ')',\n        ' ',\n        /\\d/,\n        /\\d/,\n        /\\d/,\n        '-',\n        /\\d/,\n        /\\d/,\n        /\\d/,\n        /\\d/,\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/phone/examples/2-kz-phone/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormControl, ReactiveFormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiFlagPipe, TuiTextfield} from '@taiga-ui/core';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'phone-doc-example-2',\n    imports: [MaskitoDirective, ReactiveFormsModule, TuiFlagPipe, TuiTextfield],\n    templateUrl: './template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PhoneKZDocExample2 {\n    protected readonly maskitoOptions = mask;\n    protected readonly control = new FormControl('');\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/phone/examples/2-kz-phone/mask.ts",
    "content": "import type {MaskitoOptions, MaskitoPreprocessor} from '@maskito/core';\nimport {\n    maskitoAddOnFocusPlugin,\n    maskitoCaretGuard,\n    maskitoPrefixPostprocessorGenerator,\n    maskitoRemoveOnBlurPlugin,\n} from '@maskito/kit';\n\nexport default {\n    mask: [\n        '+',\n        '7',\n        ' ',\n        '(',\n        /\\d/,\n        /\\d/,\n        /\\d/,\n        ')',\n        ' ',\n        /\\d/,\n        /\\d/,\n        /\\d/,\n        '-',\n        /\\d/,\n        /\\d/,\n        '-',\n        /\\d/,\n        /\\d/,\n    ],\n    postprocessors: [\n        // non-removable country prefix\n        maskitoPrefixPostprocessorGenerator('+7 '),\n    ],\n    preprocessors: [createCompletePhoneInsertionPreprocessor()],\n    plugins: [\n        maskitoAddOnFocusPlugin('+7 '),\n        maskitoRemoveOnBlurPlugin('+7 '),\n        // Forbids to put caret before non-removable country prefix\n        // But allows to select all value!\n        maskitoCaretGuard((value, [from, to]) => [\n            from === to ? '+7 '.length : 0,\n            value.length,\n        ]),\n    ],\n} satisfies MaskitoOptions;\n\n// Paste \"89123456789\" => \"+7 (912) 345-67-89\"\nfunction createCompletePhoneInsertionPreprocessor(): MaskitoPreprocessor {\n    const trimPrefix = (value: string): string => value.replace(/^\\+?7?\\s?8?\\s?/, '');\n    const countDigits = (value: string): number => value.replaceAll(/\\D/g, '').length;\n\n    return ({elementState, data}) => {\n        const {value, selection} = elementState;\n\n        return {\n            elementState: {\n                selection,\n                value: countDigits(value) > 11 ? trimPrefix(value) : value,\n            },\n            data: countDigits(data) >= 11 ? trimPrefix(data) : data,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/phone/examples/2-kz-phone/template.html",
    "content": "<tui-textfield [style.max-width.rem]=\"20\">\n    <label tuiLabel>Enter a phone number</label>\n    <input\n        tuiTextfield\n        [formControl]=\"control\"\n        [maskito]=\"maskitoOptions\"\n    />\n\n    <img\n        alt=\"Flag of Kazakhstan\"\n        width=\"28\"\n        [src]=\"'KZ' | tuiFlag\"\n        [style.border-radius.%]=\"50\"\n    />\n</tui-textfield>\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/phone/phone-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath, DocExamplePrimaryTab} from '@demo/constants';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink} from '@taiga-ui/core';\n\nimport {PhoneUSDocExample1} from './examples/1-us-phone/component';\nimport {PhoneKZDocExample2} from './examples/2-kz-phone/component';\n\n@Component({\n    selector: 'phone-doc',\n    imports: [PhoneKZDocExample2, PhoneUSDocExample1, RouterLink, TuiAddonDoc, TuiLink],\n    templateUrl: './phone-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class PhoneDocComponent {\n    protected readonly maskExpressionDocPage = `/${DemoPath.MaskExpression}`;\n    protected readonly prefixDocPage = `/${DemoPath.Prefix}`;\n\n    protected readonly usPhoneExample1: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/1-us-phone/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly kzPhoneExample2: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/2-kz-phone/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/phone/phone-doc.template.html",
    "content": "<tui-doc-page\n    header=\"Phone\"\n    package=\"Recipes\"\n>\n    <p class=\"tui-space_top-0\">\n        Creating mask for a phone number is simple. The only required knowledge is the\n        <strong>pattern mask expression</strong>\n        with\n        <strong>fixed characters</strong>\n        . Read more about it in\n        <a\n            tuiLink\n            [routerLink]=\"maskExpressionDocPage\"\n        >\n            \"Mask expression\"\n        </a>\n        section.\n    </p>\n\n    <p class=\"tui-space_bottom-0\">This page demonstrates some examples for different countries.</p>\n\n    <tui-doc-example\n        id=\"us\"\n        heading=\"United States\"\n        [content]=\"usPhoneExample1\"\n    >\n        <phone-doc-example-1 />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"kz\"\n        heading=\"Kazakhstan\"\n        [content]=\"kzPhoneExample2\"\n        [description]=\"kzMaskDescription\"\n    >\n        <ng-template #kzMaskDescription>\n            <p class=\"tui-space_top-0\">\n                The following example demonstrates a more complex mask. It shows how to make the country prefix\n                non-removable. It is achieved by built-in\n                <strong>postprocessor</strong>\n                from\n                <code>&#64;maskito/kit</code>\n                .\n            </p>\n            <p>\n                Read more about it in\n                <a\n                    fragment=\"by-postprocessor\"\n                    tuiLink\n                    [routerLink]=\"prefixDocPage\"\n                >\n                    \"With prefix\"\n                </a>\n                section.\n            </p>\n        </ng-template>\n        <phone-doc-example-2 />\n    </tui-doc-example>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/placeholder/examples/1-cvc-code/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'placeholder-doc-example-1',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.credit-card\"\n            [style.max-width.rem]=\"20\"\n            [(ngModel)]=\"value\"\n        >\n            Enter CVC code\n            <input\n                inputmode=\"numeric\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PlaceholderDocExample1 {\n    protected readonly maskitoOptions = mask;\n    protected value = 'xxx';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/placeholder/examples/1-cvc-code/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {maskitoWithPlaceholder} from '@maskito/kit';\n\nexport default {\n    ...maskitoWithPlaceholder('xxx'),\n    mask: /^\\d{0,3}$/,\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/placeholder/examples/2-phone/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiFlagPipe} from '@taiga-ui/core';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'placeholder-doc-example-2',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiFlagPipe,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"20\"\n            [tuiTextfieldCustomContent]=\"usFlag\"\n            [(ngModel)]=\"value\"\n        >\n            Enter US phone number\n            <input\n                inputmode=\"tel\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n\n            <ng-template #usFlag>\n                <img\n                    alt=\"Flag of the United States\"\n                    width=\"28\"\n                    [src]=\"'US' | tuiFlag\"\n                    [style.border-radius.%]=\"50\"\n                />\n            </ng-template>\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PlaceholderDocExample2 {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/placeholder/examples/2-phone/mask.ts",
    "content": "import {type MaskitoOptions, maskitoUpdateElement} from '@maskito/core';\nimport {\n    maskitoEventHandler,\n    maskitoPrefixPostprocessorGenerator,\n    maskitoWithPlaceholder,\n} from '@maskito/kit';\n\n/**\n * It is better to use en quad for placeholder characters\n * instead of simple space.\n * @see https://symbl.cc/en/2000\n */\nconst PLACEHOLDER = '+  (   ) ___-____';\nconst {\n    /**\n     * Use this utility to remove placeholder characters\n     * ___\n     * @example\n     * inputRef.addEventListener('blur', () => {\n     *     // removePlaceholder('+1 (212) 555-____') => '+1 (212) 555'\n     *     const cleanValue = removePlaceholder(this.value);\n     *\n     *     inputRef.value = cleanValue === '+1' ? '' : cleanValue;\n     * });\n     */\n    removePlaceholder,\n    plugins,\n    ...placeholderOptions\n} = maskitoWithPlaceholder(PLACEHOLDER);\n\nexport default {\n    preprocessors: placeholderOptions.preprocessors,\n    postprocessors: [\n        maskitoPrefixPostprocessorGenerator('+1'),\n        ...placeholderOptions.postprocessors,\n    ],\n    mask: [\n        '+',\n        '1',\n        ' ',\n        '(',\n        /\\d/,\n        /\\d/,\n        /\\d/,\n        ')',\n        ' ',\n        /\\d/,\n        /\\d/,\n        /\\d/,\n        '-',\n        /\\d/,\n        /\\d/,\n        /\\d/,\n        /\\d/,\n    ],\n    plugins: [\n        ...plugins,\n        maskitoEventHandler('focus', (element) => {\n            const initialValue = element.value || '+1 (';\n\n            maskitoUpdateElement(\n                element,\n                `${initialValue}${PLACEHOLDER.slice(initialValue.length)}`,\n            );\n        }),\n        maskitoEventHandler('blur', (element) => {\n            const cleanValue = removePlaceholder(element.value);\n\n            maskitoUpdateElement(element, cleanValue === '+1' ? '' : cleanValue);\n        }),\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/placeholder/examples/3-date/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'placeholder-doc-example-3',\n    imports: [\n        FormsModule,\n        MaskitoDirective,\n        TuiInputModule,\n        TuiTextfieldControllerModule,\n    ],\n    template: `\n        <tui-input\n            tuiTextfieldCustomContent=\"@tui.calendar\"\n            [style.max-width.rem]=\"20\"\n            [(ngModel)]=\"value\"\n        >\n            Enter date\n            <input\n                inputmode=\"numeric\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PlaceholderDocExample3 {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/placeholder/examples/3-date/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {maskitoDateOptionsGenerator, maskitoWithPlaceholder} from '@maskito/kit';\n\nexport const PLACEHOLDER = 'dd/mm/yyyy';\n\nconst dateOptions = maskitoDateOptionsGenerator({\n    mode: 'dd/mm/yyyy',\n    separator: '/',\n});\n\nconst {\n    plugins, // plugins keeps caret inside actual value and remove placeholder on blur\n    ...placeholderOptions\n    // pass 'true' as second argument to add plugin to hide placeholder when input is not focused\n} = maskitoWithPlaceholder(PLACEHOLDER, true);\n\nexport default {\n    ...dateOptions,\n    plugins: plugins.concat(dateOptions.plugins),\n    preprocessors: [\n        // Always put it BEFORE all other preprocessors\n        ...placeholderOptions.preprocessors,\n        ...dateOptions.preprocessors,\n    ],\n    postprocessors: [\n        ...dateOptions.postprocessors,\n        // Always put it AFTER all other postprocessors\n        ...placeholderOptions.postprocessors,\n    ],\n} satisfies Required<MaskitoOptions>;\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/placeholder/placeholder-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath, DocExamplePrimaryTab} from '@demo/constants';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink} from '@taiga-ui/core';\n\nimport {PlaceholderDocExample1} from './examples/1-cvc-code/component';\nimport {PlaceholderDocExample2} from './examples/2-phone/component';\nimport {PlaceholderDocExample3} from './examples/3-date/component';\n\n@Component({\n    selector: 'placeholder-doc',\n    imports: [\n        PlaceholderDocExample1,\n        PlaceholderDocExample2,\n        PlaceholderDocExample3,\n        RouterLink,\n        TuiAddonDoc,\n        TuiLink,\n    ],\n    templateUrl: './placeholder-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class PlaceholderDocComponent {\n    protected readonly maskExpressionDocPage = `/${DemoPath.MaskExpression}`;\n    protected readonly processorsDocPage = `/${DemoPath.Processors}`;\n    protected readonly pluginsDocPage = `/${DemoPath.Plugins}`;\n    protected readonly prefixDocPage = `/${DemoPath.Prefix}`;\n\n    protected readonly cvcExample1: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/1-cvc-code/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n\n    protected readonly phoneExample2: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/2-phone/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n\n    protected readonly dateExample3: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/3-date/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/placeholder/placeholder-doc.template.html",
    "content": "<tui-doc-page\n    header=\"With placeholder\"\n    package=\"Recipes\"\n>\n    <p class=\"tui-space_top-0\">\n        <code>maskitoWithPlaceholder</code>\n        helps to show placeholder mask characters. The placeholder character represents the fillable spot in the mask.\n    </p>\n\n    <tui-doc-example\n        id=\"cvc\"\n        heading=\"Card Verification Code\"\n        [content]=\"cvcExample1\"\n        [description]=\"cvcDescription\"\n    >\n        <ng-template #cvcDescription>\n            <p class=\"tui-space_top-0 tui-space_bottom-2\">\n                This example is the simplest demonstration how to create masked input with\n                <strong>placeholder</strong>\n                .\n            </p>\n            <p class=\"tui-space_top-0 tui-space_bottom-0\">\n                The only required prerequisite is basic understanding of\n                <a\n                    tuiLink\n                    [routerLink]=\"maskExpressionDocPage\"\n                >\n                    \"Mask expression\"\n                </a>\n                concept.\n            </p>\n        </ng-template>\n        <placeholder-doc-example-1 />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"phone\"\n        heading=\"Phone\"\n        [content]=\"phoneExample2\"\n        [description]=\"phoneDescription\"\n    >\n        <ng-template #phoneDescription>\n            <p class=\"tui-space_top-0 tui-space_bottom-2\">\n                The following example explains return type of\n                <code>maskitoWithPlaceholder</code>\n                utility — an object which partially implements\n                <code>MaskitoOptions</code>\n                interface. It contains its own\n                <a\n                    tuiLink\n                    [routerLink]=\"processorsDocPage\"\n                >\n                    processor and postprocessor\n                </a>\n                and\n                <a\n                    tuiLink\n                    [routerLink]=\"pluginsDocPage\"\n                >\n                    plugins\n                </a>\n                to keep caret from getting into placeholder part of the value.\n            </p>\n            <p class=\"tui-space_top-0 tui-space_bottom-2\"></p>\n            <p class=\"tui-space_top-0 tui-space_bottom-0\">\n                Also, this complex example uses built-in postprocessor\n                <a\n                    fragment=\"by-postprocessor\"\n                    tuiLink\n                    [routerLink]=\"prefixDocPage\"\n                >\n                    maskitoPrefixPostprocessorGenerator\n                </a>\n                from\n                <code>&#64;maskito/kit</code>\n                .\n            </p>\n        </ng-template>\n        <placeholder-doc-example-2 />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"date\"\n        heading=\"Date\"\n        [content]=\"dateExample3\"\n        [description]=\"dateExampleDescription\"\n    >\n        <ng-template #dateExampleDescription>\n            This last example demonstrates how to integrate\n            <code>maskitoWithPlaceholder</code>\n            with any built-in mask from\n            <code>&#64;maskito/kit</code>\n            .\n        </ng-template>\n        <placeholder-doc-example-3 />\n    </tui-doc-example>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/postfix/examples/1-pattern-mask/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'postfix-doc-example-1',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"20\"\n            [(ngModel)]=\"value\"\n        >\n            Enter percentage amount\n            <input\n                inputmode=\"tel\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PostfixDocExample1 {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/postfix/examples/1-pattern-mask/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nexport default {\n    mask: ({value}) => {\n        const digitsMask = Array.from(value.replaceAll('%', '')).map(() => /\\d/);\n\n        if (!digitsMask.length) {\n            return [/\\d/];\n        }\n\n        return [...digitsMask, '%'];\n    },\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/postfix/examples/2-postprocessor/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'postfix-doc-example-2',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"20\"\n            [(ngModel)]=\"value\"\n        >\n            Enter price\n            <input\n                inputmode=\"numeric\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PostfixDocExample2 {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/postfix/examples/2-postprocessor/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {\n    maskitoAddOnFocusPlugin,\n    maskitoCaretGuard,\n    maskitoPostfixPostprocessorGenerator,\n    maskitoPrefixPostprocessorGenerator,\n    maskitoRemoveOnBlurPlugin,\n} from '@maskito/kit';\n\nexport default {\n    // prefix (dollar sign) + digits + postfix ('.00')\n    mask: /^\\$?\\d*(\\.0{0,2})?$/,\n    postprocessors: [\n        maskitoPrefixPostprocessorGenerator('$'),\n        maskitoPostfixPostprocessorGenerator('.00'),\n    ],\n    plugins: [\n        maskitoAddOnFocusPlugin('$.00'),\n        maskitoRemoveOnBlurPlugin('$.00'),\n        // Disallow to put caret before the prefix or after the postfix\n        maskitoCaretGuard((value) => ['$'.length, value.length - '.00'.length]),\n    ],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/postfix/postfix-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath, DocExamplePrimaryTab} from '@demo/constants';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\n\nimport {PostfixDocExample1} from './examples/1-pattern-mask/component';\nimport {PostfixDocExample2} from './examples/2-postprocessor/component';\n\n@Component({\n    selector: 'postfix-doc',\n    imports: [\n        PostfixDocExample1,\n        PostfixDocExample2,\n        RouterLink,\n        TuiAddonDoc,\n        TuiLink,\n        TuiNotification,\n    ],\n    templateUrl: './postfix-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class PostfixDocComponent {\n    protected readonly maskExpressionDocPage = `/${DemoPath.MaskExpression}`;\n    protected readonly processorsDocPage = `/${DemoPath.Processors}`;\n\n    protected readonly patternMaskApproachExample1: Record<string, TuiRawLoaderContent> =\n        {\n            [DocExamplePrimaryTab.MaskitoOptions]: import(\n                './examples/1-pattern-mask/mask.ts?raw',\n                {with: {loader: 'text'}}\n            ),\n        };\n\n    protected readonly postprocessorApproachExample2: Record<\n        string,\n        TuiRawLoaderContent\n    > = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/2-postprocessor/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/postfix/postfix-doc.template.html",
    "content": "<tui-doc-page\n    header=\"With postfix\"\n    package=\"Recipes\"\n>\n    There are two approaches to add\n    <strong>postfix</strong>\n    for masked input. Every approach has its own behaviour and requires basic understanding of different core concepts.\n\n    <tui-doc-example\n        id=\"by-pattern-mask-expression\"\n        heading=\"By pattern mask expression\"\n        [content]=\"patternMaskApproachExample1\"\n        [description]=\"patternMaskApproachDescription\"\n    >\n        <ng-template #patternMaskApproachDescription>\n            This example demonstrates how to create postfix via dynamic\n            <strong>\n                <a\n                    tuiLink\n                    [routerLink]=\"maskExpressionDocPage\"\n                >\n                    pattern mask expression\n                </a>\n            </strong>\n            . Percent symbol is a trailing fixed character, which will be automatically added when user enters the first\n            digit.\n        </ng-template>\n        <postfix-doc-example-1 />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"by-postprocessor\"\n        heading=\"By postprocessor\"\n        [content]=\"postprocessorApproachExample2\"\n        [description]=\"postprocessorApproachDescription\"\n    >\n        <ng-template #postprocessorApproachDescription>\n            <p class=\"tui-space_top-0\">\n                This example demonstrates how to create postfix via\n                <strong>\n                    <a\n                        tuiLink\n                        [routerLink]=\"processorsDocPage\"\n                    >\n                        postprocessor\n                    </a>\n                </strong>\n                . It provides more flexibility, and you can configure any desired behaviour. You can use built-in\n                <code>maskitoPostfixPostprocessorGenerator</code>\n                or create your own.\n            </p>\n\n            <tui-notification\n                appearance=\"warning\"\n                size=\"m\"\n            >\n                <div>\n                    Don't forget that\n                    <code>mask</code>\n                    property should be compatible with a new prefix / postfix!\n                </div>\n            </tui-notification>\n        </ng-template>\n        <postfix-doc-example-2 />\n    </tui-doc-example>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/prefix/examples/1-pattern-mask/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'prefix-doc-example-1',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"20\"\n            [(ngModel)]=\"value\"\n        >\n            Enter price\n            <input\n                inputmode=\"tel\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PrefixDocExample1 {\n    protected readonly maskitoOptions = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/prefix/examples/1-pattern-mask/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nexport default {\n    mask: ({value}) => {\n        const digitsCount = value.replaceAll(/\\D/g, '').length;\n\n        return ['$', ...Array.from<RegExp>({length: digitsCount || 1}).fill(/\\d/)];\n    },\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/prefix/examples/2-postprocessor/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiInputModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'prefix-doc-example-2',\n    imports: [FormsModule, MaskitoDirective, TuiInputModule],\n    template: `\n        <tui-input\n            [style.max-width.rem]=\"20\"\n            [(ngModel)]=\"value\"\n        >\n            Enter price\n            <input\n                inputmode=\"tel\"\n                tuiTextfieldLegacy\n                [maskito]=\"maskitoOptions\"\n            />\n        </tui-input>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PrefixDocExample2 {\n    protected readonly maskitoOptions = mask;\n\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/prefix/examples/2-postprocessor/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {\n    maskitoAddOnFocusPlugin,\n    maskitoPrefixPostprocessorGenerator,\n    maskitoRemoveOnBlurPlugin,\n} from '@maskito/kit';\n\nexport default {\n    mask: /^\\$?\\d*$/, // dollar sign or digits\n    postprocessors: [maskitoPrefixPostprocessorGenerator('$')],\n    plugins: [maskitoAddOnFocusPlugin('$'), maskitoRemoveOnBlurPlugin('$')],\n} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/prefix/prefix-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath, DocExamplePrimaryTab} from '@demo/constants';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink, TuiNotification} from '@taiga-ui/core';\n\nimport {PrefixDocExample1} from './examples/1-pattern-mask/component';\nimport {PrefixDocExample2} from './examples/2-postprocessor/component';\n\n@Component({\n    selector: 'prefix-doc',\n    imports: [\n        PrefixDocExample1,\n        PrefixDocExample2,\n        RouterLink,\n        TuiAddonDoc,\n        TuiLink,\n        TuiNotification,\n    ],\n    templateUrl: './prefix-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class PrefixDocComponent {\n    protected readonly maskExpressionDocPage = `/${DemoPath.MaskExpression}`;\n    protected readonly processorsDocPage = `/${DemoPath.Processors}`;\n\n    protected readonly patternMaskApproachExample1: Record<string, TuiRawLoaderContent> =\n        {\n            [DocExamplePrimaryTab.MaskitoOptions]: import(\n                './examples/1-pattern-mask/mask.ts?raw',\n                {with: {loader: 'text'}}\n            ),\n        };\n\n    protected readonly postprocessorApproachExample2: Record<\n        string,\n        TuiRawLoaderContent\n    > = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import(\n            './examples/2-postprocessor/mask.ts?raw',\n            {with: {loader: 'text'}}\n        ),\n    };\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/prefix/prefix-doc.template.html",
    "content": "<tui-doc-page\n    header=\"With prefix\"\n    package=\"Recipes\"\n>\n    <section>\n        <p class=\"tui-space_top-0\">\n            Use prefixes to indicate things like currencies, area / phone country codes and etc.\n        </p>\n        <p class=\"tui-space_bottom-0\">\n            There are two approaches to add prefix for masked input. Every approach has its own behaviour and requires\n            basic understanding of different core concepts.\n        </p>\n    </section>\n\n    <tui-doc-example\n        id=\"by-pattern-mask-expression\"\n        heading=\"By pattern mask expression\"\n        [content]=\"patternMaskApproachExample1\"\n        [description]=\"patternMaskApproachDescription\"\n    >\n        <ng-template #patternMaskApproachDescription>\n            This example demonstrates how to create prefix via dynamic\n            <strong>\n                <a\n                    tuiLink\n                    [routerLink]=\"maskExpressionDocPage\"\n                >\n                    pattern mask expression\n                </a>\n            </strong>\n            . Dollar symbol is a fixed character, which will be automatically added when user forgets to type it or\n            deleted when user erase all digits.\n        </ng-template>\n        <prefix-doc-example-1 />\n    </tui-doc-example>\n\n    <tui-doc-example\n        id=\"by-postprocessor\"\n        heading=\"By postprocessor\"\n        [content]=\"postprocessorApproachExample2\"\n        [description]=\"postprocessorApproachDescription\"\n    >\n        <ng-template #postprocessorApproachDescription>\n            <p class=\"tui-space_top-0\">\n                This example demonstrates how to create prefix via\n                <strong>\n                    <a\n                        tuiLink\n                        [routerLink]=\"processorsDocPage\"\n                    >\n                        postprocessor\n                    </a>\n                </strong>\n                . It provides more flexibility, and you can configure any desired behaviour. You can use built-in\n                <code>maskitoPrefixPostprocessorGenerator</code>\n                or create your own.\n            </p>\n\n            <tui-notification\n                appearance=\"warning\"\n                size=\"m\"\n            >\n                <div>\n                    Don't forget that\n                    <code>mask</code>\n                    property should be compatible with a new prefix!\n                </div>\n            </tui-notification>\n        </ng-template>\n        <prefix-doc-example-2 />\n    </tui-doc-example>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/textarea/examples/1-latin/component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {TuiTextareaModule} from '@taiga-ui/legacy';\n\nimport mask from './mask';\n\n@Component({\n    selector: 'textarea-doc-example-1',\n    imports: [FormsModule, MaskitoDirective, TuiTextareaModule],\n    template: `\n        <tui-textarea [(ngModel)]=\"value\">\n            Enter address\n            <textarea\n                autocomplete=\"street-address\"\n                placeholder=\"Only latin letters and digits are allowed\"\n                tuiTextfieldLegacy\n                [maskito]=\"mask\"\n            ></textarea>\n        </tui-textarea>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TextareaDocExample1 {\n    protected readonly mask = mask;\n    protected value = '';\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/textarea/examples/1-latin/mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nexport default {mask: /^[a-z1-9\\s.,/]+$/i} satisfies MaskitoOptions;\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/textarea/examples/maskito-with-textarea.md",
    "content": "```ts\nimport {Maskito} from '@maskito/core';\n\nconst element: HTMLTextAreaElement = document.querySelector('textarea')!;\n\nconst maskedTextarea = new Maskito(element, {\n  mask: /^[a-z\\s]+$/i,\n});\n\n// Call it when the element is detached from DOM\nmaskedTextarea.destroy();\n```\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/textarea/textarea-doc.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {RouterLink} from '@angular/router';\nimport {DemoPath, DocExamplePrimaryTab} from '@demo/constants';\nimport {TuiAddonDoc, type TuiRawLoaderContent} from '@taiga-ui/addon-doc';\nimport {TuiLink} from '@taiga-ui/core';\n\nimport {TextareaDocExample1} from './examples/1-latin/component';\n\n@Component({\n    selector: 'textarea-doc',\n    imports: [RouterLink, TextareaDocExample1, TuiAddonDoc, TuiLink],\n    templateUrl: './textarea-doc.template.html',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class TextareaDocComponent {\n    protected readonly coreConceptsOverviewDocPage = `/${DemoPath.CoreConceptsOverview}`;\n    protected readonly maskitoWithTextareaDemo =\n        import('./examples/maskito-with-textarea.md');\n\n    protected readonly textareaExample1: Record<string, TuiRawLoaderContent> = {\n        [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/1-latin/mask.ts?raw', {\n            with: {loader: 'text'},\n        }),\n    };\n}\n"
  },
  {
    "path": "projects/demo/src/pages/recipes/textarea/textarea-doc.template.html",
    "content": "<tui-doc-page\n    header=\"Textarea\"\n    package=\"Recipes\"\n>\n    <section>\n        <p class=\"tui-space_top-0 tui-space_bottom-4\">\n            You can use\n            <strong>Maskito</strong>\n            with\n            <code>HTMLTextAreaElement</code>\n            too. API is the same as for\n            <code>HTMLInputElement</code>\n            .\n        </p>\n\n        <tui-doc-code [code]=\"maskitoWithTextareaDemo\" />\n\n        <p class=\"tui-space_bottom-0\">\n            Learn more in the\n            <a\n                tuiLink\n                [routerLink]=\"coreConceptsOverviewDocPage\"\n            >\n                \"Core Concepts\"\n            </a>\n            section.\n        </p>\n    </section>\n\n    <tui-doc-example\n        id=\"latin\"\n        heading=\"Latin letters and digits\"\n        [content]=\"textareaExample1\"\n    >\n        <textarea-doc-example-1 />\n    </tui-doc-example>\n</tui-doc-page>\n"
  },
  {
    "path": "projects/demo/src/pages/stackblitz/components/stackblitz-edit-button/stackblitz-edit-button.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {TuiButton} from '@taiga-ui/core';\n\n@Component({\n    selector: 'stackblitz-edit-button',\n    imports: [TuiButton],\n    template: `\n        <button\n            appearance=\"flat\"\n            iconStart=\"assets/icons/stackblitz.svg\"\n            size=\"s\"\n            title=\"Edit on StackBlitz\"\n            tuiButton\n            type=\"button\"\n        >\n            Edit\n        </button>\n    `,\n    styleUrl: './stackblitz-edit-button.style.less',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class StackblitzEditButtonComponent {}\n"
  },
  {
    "path": "projects/demo/src/pages/stackblitz/components/stackblitz-edit-button/stackblitz-edit-button.style.less",
    "content": "@import '@taiga-ui/core/styles/taiga-ui-local.less';\n\nbutton {\n    @media @tui-mobile {\n        font-size: 0;\n        margin-inline-end: -1rem;\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/stackblitz/components/stackblitz-starter/stackblitz-starter.component.ts",
    "content": "import {isPlatformBrowser} from '@angular/common';\nimport {\n    ChangeDetectionStrategy,\n    Component,\n    inject,\n    type OnInit,\n    PLATFORM_ID,\n} from '@angular/core';\nimport {tuiRawLoad, tuiTryParseMarkdownCodeBlock} from '@taiga-ui/addon-doc';\nimport {TuiLoader} from '@taiga-ui/core';\n\nimport {StackblitzService} from '../../stackblitz.service';\n\n@Component({\n    selector: 'stackblitz-starter',\n    imports: [TuiLoader],\n    template: `\n        <tui-loader\n            size=\"xxl\"\n            textContent=\"Stackblitz loading...\"\n            class=\"loader\"\n            [overlay]=\"true\"\n        />\n    `,\n    styleUrl: './stackblitz-starter.style.less',\n    changeDetection: ChangeDetectionStrategy.OnPush,\n    providers: [StackblitzService],\n})\nexport class StackblitzStarterComponent implements OnInit {\n    private readonly platformId = inject(PLATFORM_ID);\n    private readonly stackblitz = inject(StackblitzService);\n\n    public ngOnInit(): void {\n        if (isPlatformBrowser(this.platformId)) {\n            void this.openStackblitz();\n        }\n    }\n\n    protected async openStackblitz(): Promise<void> {\n        const [ts = '', css = ''] = await Promise.all(\n            [import('../../files/starter.ts.md'), import('../../files/styles.css')].map(\n                tuiRawLoad,\n            ),\n        );\n\n        return this.stackblitz.openStarter(\n            {\n                title: 'Maskito Starter',\n                description:\n                    'A starter with Maskito library\\nDocumentation: https://maskito.dev',\n                files: {\n                    'index.html': '<input />',\n                    'index.ts': tuiTryParseMarkdownCodeBlock(ts)[0]!,\n                    'styles.css': css,\n                },\n            },\n            {\n                newWindow: false,\n                openFile: 'index.ts',\n                hideExplorer: true,\n            },\n        );\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/pages/stackblitz/components/stackblitz-starter/stackblitz-starter.style.less",
    "content": "@import '@taiga-ui/core/styles/taiga-ui-local.less';\n\n.loader {\n    .fullsize(fixed);\n\n    z-index: 1;\n    background: var(--tui-background-base);\n}\n"
  },
  {
    "path": "projects/demo/src/pages/stackblitz/files/example.ts.md",
    "content": "```ts\nimport './styles.css';\n\nimport type {MaskitoElement} from '@maskito/core';\nimport {Maskito} from '@maskito/core';\nimport options from './mask';\n\nconst element: MaskitoElement = document.querySelector('input,textarea')!;\nconst mask = new Maskito(element, options);\n\nconsole.info('Call this function when the element is detached from DOM', mask.destroy);\n```\n"
  },
  {
    "path": "projects/demo/src/pages/stackblitz/files/starter.ts.md",
    "content": "```ts\nimport './styles.css';\n\nimport type {MaskitoElement, MaskitoOptions} from '@maskito/core';\nimport {Maskito} from '@maskito/core';\n\nconst options: MaskitoOptions = {\n  mask: /^\\d+$/,\n};\n\nconst element: MaskitoElement = document.querySelector('input,textarea')!;\nconst mask = new Maskito(element, options);\n\nconsole.info('Call this function when the element is detached from DOM', mask.destroy);\n```\n"
  },
  {
    "path": "projects/demo/src/pages/stackblitz/files/styles.css",
    "content": ":root {\n    --tui-text-primary: rgb(27, 31, 59);\n    --tui-text-tertiary: rgba(27, 31, 59, 0.4);\n    --tui-radius-m: 0.75rem;\n    --tui-background-base: #fff;\n    --tui-background-neutral-1-hover: #ededed;\n    --tui-background-accent-1: #526ed3;\n}\n\ninput,\ntextarea {\n    display: block;\n    inline-size: 100%;\n    max-inline-size: 25rem;\n    font-size: 0.9375rem;\n    font-family: 'Roboto', sans-serif;\n    border-radius: var(--tui-radius-m);\n    box-sizing: border-box;\n    border: 1px solid var(--tui-background-neutral-1-hover);\n    color: var(--tui-text-primary);\n    background: var(--tui-background-base);\n    outline: none;\n    transition:\n        box-shadow,\n        background,\n        0.2s ease-in-out;\n    box-shadow: 0 0.125rem 0.1875rem rgba(0, 0, 0, 0.1);\n}\n\ninput {\n    min-block-size: 2.75rem;\n    padding: 0 1rem;\n}\n\ntextarea {\n    min-block-size: 10rem;\n    padding: 0.5rem;\n}\n\ninput:focus::placeholder,\ntextarea:focus::placeholder {\n    color: var(--tui-text-tertiary);\n}\n\ninput:hover,\ntextarea:hover {\n    box-shadow: 0 0.125rem 0.3125rem rgba(0, 0, 0, 0.16);\n}\n\ninput:focus,\ntextarea:focus {\n    box-shadow: none;\n    border-color: var(--tui-background-accent-1);\n    border-width: 0.125rem;\n}\n"
  },
  {
    "path": "projects/demo/src/pages/stackblitz/index.ts",
    "content": "export * from './components/stackblitz-edit-button/stackblitz-edit-button.component';\nexport * from './components/stackblitz-starter/stackblitz-starter.component';\nexport * from './stackblitz.service';\n"
  },
  {
    "path": "projects/demo/src/pages/stackblitz/stackblitz.service.ts",
    "content": "import {Injectable} from '@angular/core';\nimport {DocExamplePrimaryTab} from '@demo/constants';\nimport phonePackageJson from '@maskito/phone/package.json';\nimport stackblitz, {type OpenOptions, type Project} from '@stackblitz/sdk';\nimport {\n    type TuiCodeEditor,\n    tuiRawLoad,\n    tuiTryParseMarkdownCodeBlock,\n} from '@taiga-ui/addon-doc';\nimport {PolymorpheusComponent} from '@taiga-ui/polymorpheus';\n\nimport {StackblitzEditButtonComponent} from './components/stackblitz-edit-button/stackblitz-edit-button.component';\n\n@Injectable()\nexport class StackblitzService implements TuiCodeEditor {\n    private readonly baseProjectConfigs: Pick<Project, 'dependencies' | 'template'> = {\n        template: 'typescript',\n        dependencies: {\n            '@maskito/core': 'latest',\n            '@maskito/kit': 'latest',\n            '@maskito/phone': 'latest',\n            'libphonenumber-js': phonePackageJson.peerDependencies['libphonenumber-js'],\n        },\n    };\n\n    public readonly name = 'Stackblitz';\n    public readonly content = new PolymorpheusComponent(StackblitzEditButtonComponent);\n\n    public async edit(\n        component: string,\n        id: string,\n        files: Record<string, string>,\n    ): Promise<void> {\n        const [tsMd = '', css = ''] = await Promise.all(\n            [import('./files/example.ts.md'), import('./files/styles.css')].map(\n                tuiRawLoad,\n            ),\n        );\n\n        return stackblitz.openProject(\n            {\n                ...this.baseProjectConfigs,\n                title: `maskito/${component}/${id}`,\n                description: `Maskito example of the component ${component}`,\n                files: {\n                    'index.html': component.includes('textarea')\n                        ? '<textarea></textarea>'\n                        : '<input />',\n                    'styles.css': css,\n                    'index.ts': tuiTryParseMarkdownCodeBlock(tsMd)[0] ?? '',\n                    'mask.ts': files[DocExamplePrimaryTab.MaskitoOptions] ?? '',\n                },\n            },\n            {openFile: 'index.ts,mask.ts'},\n        );\n    }\n\n    public openStarter(\n        {title, description, files}: Pick<Project, 'description' | 'files' | 'title'>,\n        openOptions?: OpenOptions,\n    ): void {\n        return stackblitz.openProject(\n            {\n                ...this.baseProjectConfigs,\n                title,\n                description,\n                files,\n            },\n            openOptions,\n        );\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/server.ts",
    "content": "import {dirname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\n\nimport {\n    AngularNodeAppEngine,\n    createNodeRequestHandler,\n    isMainModule,\n    writeResponseToNodeResponse,\n} from '@angular/ssr/node';\nimport express from 'express';\n\nconst serverDistFolder = dirname(fileURLToPath(import.meta.url));\nconst browserDistFolder = resolve(serverDistFolder, '../browser');\n\nconst app = express();\nconst angularApp = new AngularNodeAppEngine({\n    // https://angular.dev/best-practices/security#preventing-server-side-request-forgery-ssrf\n    allowedHosts: ['localhost'],\n});\n\n/**\n * Serve static files from /browser\n */\napp.use(\n    express.static(browserDistFolder, {\n        maxAge: '1y',\n        index: false,\n        redirect: false,\n    }),\n);\n\n/**\n * Handle all other requests by rendering the Angular application.\n */\napp.use((req, res, next) => {\n    angularApp\n        .handle(req)\n        .then(async (response) =>\n            response ? writeResponseToNodeResponse(response, res) : next(),\n        )\n        .catch(next);\n});\n\n/**\n * Start the server if this module is the main entry point.\n * The server listens on the port defined by the `PORT` environment variable, or defaults to 4000.\n */\nif (isMainModule(import.meta.url)) {\n    const port = Number(process.env['PORT'] ?? 4000);\n\n    app.listen(port, () => {\n        console.info(`Node Express server listening on http://localhost:${port}`);\n    });\n}\n\n/**\n * Request handler used by the Angular CLI (for dev-server and during build)\n */\nexport const reqHandler = createNodeRequestHandler(app);\n"
  },
  {
    "path": "projects/demo/src/styles.less",
    "content": "@import '@taiga-ui/core/styles/taiga-ui-local.less';\n\nbody {\n    margin: 0;\n}\n\nhtml,\nbody {\n    scroll-behavior: var(--tui-scroll-behavior);\n    block-size: 100%;\n\n    @media (prefers-reduced-motion) {\n        scroll-behavior: auto;\n    }\n}\n\nhtml {\n    color-scheme: light;\n}\n\n[tuiTheme='dark'] {\n    color-scheme: dark;\n}\n\nmarkdown {\n    li li {\n        // nested list\n        color: var(--tui-text-secondary);\n\n        &::before {\n            content: '\\2014';\n            inset-inline-start: 0;\n            inset-block-start: auto;\n            inline-size: auto;\n            block-size: auto;\n            background-color: transparent;\n        }\n    }\n}\n"
  },
  {
    "path": "projects/demo/src/test-setup.ts",
    "content": "import '@taiga-ui/testing/setup-jest';\n\nimport {enableProdMode} from '@angular/core';\n\n/**\n * - https://github.com/angular/angular/issues/54096\n * - https://github.com/thymikee/jest-preset-angular/issues/2582\n */\nenableProdMode();\n"
  },
  {
    "path": "projects/demo/src/typings.d.ts",
    "content": "declare module '*.md' {\n    const content: string;\n    export default content;\n}\n\ndeclare module '*.html' {\n    const content: string;\n    export default content;\n}\n\ndeclare module '*.less' {\n    const content: string;\n    export default content;\n}\n\ndeclare module '*.css' {\n    const content: string;\n    export default content;\n}\n\n/**\n * At this time, TypeScript does not support type definitions that are based on import attribute values.\n * We cannot import Typescript files as raw text without @ts-ignore.\n * This is a workaround to do it without @ts-ignore\n * (adding ?raw postfix takes less space than @ts-ignore comment above).\n */\ndeclare module '*.ts?raw' {\n    const content: string;\n    export default content;\n}\n\ndeclare module '*.tsx?raw' {\n    const content: string;\n    export default content;\n}\n"
  },
  {
    "path": "projects/demo/tsconfig.app.json",
    "content": "{\n    \"extends\": \"../../tsconfig.build.json\",\n    \"compilerOptions\": {\n        \"allowJs\": false,\n        \"outDir\": \"../out-tsc/app\",\n        \"target\": \"ES2022\",\n        \"useDefineForClassFields\": false\n    },\n    \"angularCompilerOptions\": {\n        \"compilationMode\": \"full\"\n    }\n}\n"
  },
  {
    "path": "projects/demo/tsconfig.typecheck.json",
    "content": "{\n    \"extends\": \"./tsconfig.app.json\",\n    \"compilerOptions\": {\n        \"paths\": {\n            \"@demo/constants\": [\"projects/demo/src/app/constants/index.ts\"],\n            \"@maskito/angular\": [\"dist/angular\"],\n            \"@maskito/core\": [\"dist/core\"],\n            \"@maskito/kit\": [\"dist/kit\"],\n            \"@maskito/phone\": [\"dist/phone\"],\n            \"@maskito/react\": [\"dist/react\"],\n            \"@maskito/vue\": [\"dist/vue\"]\n        }\n    }\n}\n"
  },
  {
    "path": "projects/demo-integrations/cypress-react.config.ts",
    "content": "import {nxComponentTestingPreset} from '@nx/angular/plugins/component-testing';\nimport {defineConfig} from 'cypress';\n\nexport default defineConfig({\n    component: {\n        ...nxComponentTestingPreset(__filename),\n        indexHtmlFile: 'src/support/component-index.html',\n        supportFile: 'src/support/component-react.ts',\n        specPattern: 'src/tests/component-testing/react/**/*.cy.tsx',\n        devServer: {\n            framework: 'react',\n            bundler: 'vite',\n        },\n    },\n});\n"
  },
  {
    "path": "projects/demo-integrations/cypress.config.ts",
    "content": "import {nxComponentTestingPreset} from '@nx/angular/plugins/component-testing';\nimport {nxE2EPreset} from '@nx/cypress/plugins/cypress-preset';\nimport {defineConfig} from 'cypress';\n\nexport const CYPRESS_CONFIG: Cypress.ConfigOptions = {\n    video: false,\n    fixturesFolder: 'src/fixtures',\n    viewportWidth: 500,\n    viewportHeight: 900,\n    responseTimeout: 60000,\n    pageLoadTimeout: 120000,\n    defaultCommandTimeout: 10000,\n    e2e: {\n        ...nxE2EPreset(__filename, {cypressDir: 'src'}),\n        // We've imported your old cypress plugins here.\n        // You may want to clean this up later by importing these.\n        setupNodeEvents(on, config) {\n            return require('./src/plugins/index.js')(on, config);\n        },\n        baseUrl: 'http://localhost:3333',\n        specPattern: 'src/tests/!(component-testing)/**/*.cy.ts',\n    },\n    component: {\n        ...nxComponentTestingPreset(__filename),\n        supportFile: 'src/support/component.ts',\n        indexHtmlFile: 'src/support/component-index.html',\n        specPattern: 'src/tests/component-testing/**/*.cy.ts',\n        // No need to recompile empty sandbox before each test spec\n        justInTimeCompile: false,\n    },\n};\n\nexport default defineConfig(CYPRESS_CONFIG);\n"
  },
  {
    "path": "projects/demo-integrations/package.json",
    "content": "{\n    \"name\": \"@maskito/demo-cypress\",\n    \"private\": true,\n    \"devDependencies\": {\n        \"@nx/cypress\": \"21.6.3\",\n        \"@nx/vite\": \"21.6.3\",\n        \"@vitejs/plugin-react\": \"4.7.0\",\n        \"cypress\": \"14.5.4\",\n        \"cypress-real-events\": \"1.15.0\"\n    }\n}\n"
  },
  {
    "path": "projects/demo-integrations/project.json",
    "content": "{\n    \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n    \"name\": \"demo-integrations\",\n    \"implicitDependencies\": [\"demo\"],\n    \"projectType\": \"application\",\n    \"sourceRoot\": \"projects/demo-integrations/src\",\n    \"targets\": {\n        \"component-test\": {\n            \"executor\": \"@nx/cypress:cypress\",\n            \"options\": {\n                \"cypressConfig\": \"{projectRoot}/cypress.config.ts\",\n                \"devServerTarget\": \"demo:build\",\n                \"skipServe\": true,\n                \"testingType\": \"component\"\n            }\n        },\n        \"ct-react\": {\n            \"executor\": \"@nx/cypress:cypress\",\n            \"options\": {\n                \"cypressConfig\": \"{projectRoot}/cypress-react.config.ts\",\n                \"skipServe\": true,\n                \"testingType\": \"component\"\n            }\n        },\n        \"e2e\": {\n            \"executor\": \"@nx/cypress:cypress\",\n            \"options\": {\n                \"browser\": \"chrome\",\n                \"cypressConfig\": \"{projectRoot}/cypress.config.ts\",\n                \"devServerTarget\": \"demo:serve:development\",\n                \"skipServe\": true,\n                \"testingType\": \"e2e\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "projects/demo-integrations/src/fixtures/example.json",
    "content": "{}\n"
  },
  {
    "path": "projects/demo-integrations/src/plugins/index.js",
    "content": "const {CYPRESS_CONFIG} = require('../../cypress.config');\n\nmodule.exports = (on) => {\n    on('before:browser:launch', (browser, launchOptions) => {\n        if (browser.name === 'chrome') {\n            launchOptions.args.push(\n                `--window-size=${CYPRESS_CONFIG.viewportWidth},${CYPRESS_CONFIG.viewportHeight}`,\n                '--force-device-scale-factor=2',\n                '--high-dpi-support=1',\n                '--disable-dev-shm-usage',\n                '--incognito',\n            );\n        }\n\n        if (browser.isHeadless) {\n            launchOptions.args.push('--disable-gpu');\n        }\n\n        return launchOptions;\n    });\n};\n"
  },
  {
    "path": "projects/demo-integrations/src/support/assertions/have-ng-control-value.ts",
    "content": "/// <reference types=\"cypress\" />\n/// <reference types=\"node\" />\n\n/**\n * Check if element has Angular form control with specified value\n *\n * @note WARNING! This assertion uses `window.ng` which works only if application was build with `\"optimization\": false`\n * @example\n * cy.get('tui-input').should('have.ngControlValue', '123')\n * */\nexport const haveNgControlValueAssertion: Chai.ChaiPlugin = (_chai) => {\n    chai.Assertion.addMethod('ngControlValue', function (expectedValue: string) {\n        const subject = this._obj[0];\n        const windowRef = Cypress.dom.getWindowByElement(subject);\n        // @ts-ignore\n        const angularTools = windowRef.ng;\n\n        const control =\n            angularTools.getComponent(subject)?.control ??\n            angularTools.getDirectives(subject).find((x: unknown) => {\n                const inputs = angularTools.getDirectiveMetadata(x).inputs;\n\n                return 'formControl' in inputs || 'ngModel' in inputs;\n            });\n\n        this.assert(\n            angularTools && control.value === expectedValue,\n            `expected #{this} to have Angular form control with value ${expectedValue}`,\n            `expected #{this} to do not have Angular form control with value ${expectedValue}`,\n            subject,\n        );\n    });\n};\n"
  },
  {
    "path": "projects/demo-integrations/src/support/assertions/index.ts",
    "content": "import {haveNgControlValueAssertion} from './have-ng-control-value';\n\ndeclare global {\n    namespace Cypress {\n        interface Chainer<Subject> {\n            /**\n             * Assertion that checks if given subject has Angular form control with specified value\n             *\n             * @example\n             * cy.get('tui-input').should('have.ngControlValue', '123')\n             * */\n            (chainer: 'have.ngControlValue'): Chainable<Subject>;\n        }\n    }\n}\n\nchai.use(haveNgControlValueAssertion);\n"
  },
  {
    "path": "projects/demo-integrations/src/support/commands/index.ts",
    "content": "/// <reference types=\"cypress\" />\n\nimport {paste} from './paste';\nimport {smartTick} from './smart-tick';\n\ndeclare global {\n    namespace Cypress {\n        interface Chainable<Subject> {\n            smartTick(\n                durationMs: number,\n                options?: Parameters<typeof smartTick>[2],\n            ): Chainable<Subject>;\n\n            paste(value: string): Chainable<Subject>;\n        }\n    }\n}\n\nCypress.Commands.add(\n    'smartTick',\n    {prevSubject: ['optional', 'element', 'window', 'document']},\n    smartTick,\n);\nCypress.Commands.add('paste', {prevSubject: 'element'}, paste);\n"
  },
  {
    "path": "projects/demo-integrations/src/support/commands/paste.ts",
    "content": "/**\n * Cypress does not have built-in support for pasting from clipboard.\n * This utility is VERY approximate alternative for it.\n *\n * @see https://github.com/cypress-io/cypress/issues/28861\n */\nexport function paste<T extends Cypress.PrevSubjectMap['element']>(\n    $subject: T,\n    data: string,\n): ReturnType<Cypress.CommandFn<'paste'>> {\n    const element = Cypress.dom.unwrap($subject)[0] as\n        | HTMLInputElement\n        | HTMLTextAreaElement;\n    const {value, selectionStart, selectionEnd} = element;\n\n    Cypress.log({\n        displayName: 'paste',\n        message: data,\n        consoleProps() {\n            return {\n                value,\n                selectionStart,\n                selectionEnd,\n            };\n        },\n    });\n\n    return cy\n        .document()\n        .invoke('addEventListener', 'beforeinput', cy.stub().as('beforeinput'), {\n            once: true,\n        })\n        .wrap($subject, {log: false})\n        .should('be.focused')\n        .trigger('beforeinput', {\n            inputType: 'insertFromPaste',\n            data,\n            log: false,\n        })\n        .document({log: false})\n        .get('@beforeinput')\n        .its('lastCall.lastArg.defaultPrevented')\n        .then((prevented) => (prevented ? null : cy.document()))\n        .then((doc) => doc?.execCommand('insertText', false, data))\n        .wrap($subject, {log: false});\n}\n"
  },
  {
    "path": "projects/demo-integrations/src/support/commands/smart-tick.ts",
    "content": "import type {ComponentFixture} from '@angular/core/testing';\n\nexport function smartTick<T extends Cypress.PrevSubjectMap<void>[Cypress.PrevSubject]>(\n    $subject: T,\n    durationMs: number, // ms\n    {\n        frequencyMs = 100,\n        fixture,\n    }: {\n        fixture?: ComponentFixture<unknown>;\n        frequencyMs?: number; // ms\n    } = {},\n): Cypress.Chainable<T> {\n    const iterations = Math.ceil(durationMs / frequencyMs);\n    const lastIterationMs = durationMs % frequencyMs || frequencyMs;\n\n    for (let i = 1; i <= iterations; i++) {\n        cy.tick(i === iterations ? lastIterationMs : frequencyMs, {log: false}).then(\n            () => fixture?.detectChanges(), // ensure @Input()-properties are changed\n        );\n        cy.wait(0, {log: false}); // allow React hooks to process\n    }\n\n    Cypress.log({\n        displayName: 'smartTick',\n        message: `${durationMs}ms`,\n        consoleProps() {\n            return {\n                durationMs,\n                frequencyMs,\n            };\n        },\n    });\n\n    return cy.wrap($subject, {log: false});\n}\n"
  },
  {
    "path": "projects/demo-integrations/src/support/component-index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\" />\n        <meta\n            content=\"IE=edge\"\n            http-equiv=\"X-UA-Compatible\"\n        />\n        <meta\n            content=\"width=device-width,initial-scale=1.0\"\n            name=\"viewport\"\n        />\n        <title>demo-integrations Components App</title>\n    </head>\n    <body>\n        <div data-cy-root></div>\n    </body>\n</html>\n"
  },
  {
    "path": "projects/demo-integrations/src/support/component-react.ts",
    "content": "import './assertions';\nimport './commands';\n"
  },
  {
    "path": "projects/demo-integrations/src/support/component.ts",
    "content": "import './assertions';\nimport './commands';\nimport 'cypress-real-events'; // https://github.com/cypress-io/cypress/issues/2839\n\nimport {mount} from 'cypress/angular';\n\ndeclare global {\n    namespace Cypress {\n        // eslint-disable-next-line @typescript-eslint/no-unused-vars\n        interface Chainable<Subject> {\n            mount: typeof mount;\n        }\n    }\n}\n\nexport const stableMount: typeof mount = (...mountArgs) =>\n    mount(...mountArgs).then(async (mountResponse) =>\n        mountResponse.fixture.whenStable().then(() => mountResponse),\n    );\n\nCypress.Commands.add('mount', stableMount);\n"
  },
  {
    "path": "projects/demo-integrations/src/support/constants/index.ts",
    "content": "export * from './real-events-support';\n"
  },
  {
    "path": "projects/demo-integrations/src/support/constants/real-events-support.ts",
    "content": "/**\n * Some tests use `cy.realPress([...])`, to simulate text selection.\n * But this command is not supported by all browsers.\n * ___\n * @see https://docs.cypress.io/guides/guides/cross-browser-testing#Running-Specific-Tests-by-Browser\n * @see https://github.com/dmtrKovalenko/cypress-real-events#requirements\n * @see https://github.com/cypress-io/cypress/issues/2839#issuecomment-867411151\n */\nexport const BROWSER_SUPPORTS_REAL_EVENTS: Cypress.TestConfigOverrides = {\n    browser: '!firefox',\n};\n"
  },
  {
    "path": "projects/demo-integrations/src/support/e2e.ts",
    "content": "/// <reference types=\"cypress\" />\nimport 'cypress-real-events'; // https://github.com/cypress-io/cypress/issues/2839\nimport './assertions';\nimport './commands';\n\nCypress.on('window:before:load', (win) => {\n    cy.spy(win.console, 'error');\n});\n\nafterEach(() => {\n    cy.window().then((win) => expect(win.console.error).to.have.callCount(0));\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/addons/phone/phone-basic.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\nimport {repeatKey} from '../../utils';\n\ndescribe('Phone', () => {\n    describe('Basic', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.PhonePackage}/API`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        describe('basic typing (1 character per keydown)', () => {\n            const tests = [\n                // [Typed value, Masked value, caretIndex]\n                ['920', '+7 920', '+7 920'.length],\n                ['920341', '+7 920 341', '+7 920 341'.length],\n                ['92034156', '+7 920 341-56', '+7 920 341-56'.length],\n                ['9203415627', '+7 920 341-56-27', '+7 920 341-56-27'.length],\n                ['92034156274234123', '+7 920 341-56-27', '+7 920 341-56-27'.length],\n                ['9 nd4 e', '+7 94', '+7 94'.length],\n            ] as const;\n\n            tests.forEach(([typedValue, maskedValue, caretIndex]) => {\n                it(`Type \"${typedValue}\" => \"${maskedValue}\"`, () => {\n                    cy.get('@input')\n                        .type(typedValue)\n                        .should('have.value', maskedValue)\n                        .should('have.prop', 'selectionStart', caretIndex)\n                        .should('have.prop', 'selectionEnd', caretIndex);\n                });\n            });\n        });\n\n        describe('basic erasing (value = \"+7 920 424-11-32\"', () => {\n            beforeEach(() => {\n                cy.get('@input').type('9204241132');\n            });\n\n            const tests = [\n                // [How many times \"Backspace\"-key was pressed, caretPosition, Masked value]\n                [1, '+7 920 424-11-3'.length, '+7 920 424-11-3'],\n                [2, '+7 920 424-11'.length, '+7 920 424-11'],\n                [3, '+7 920 424-1'.length, '+7 920 424-1'],\n                [4, '+7 920 424'.length, '+7 920 424'],\n                [13, '+7 '.length, '+7 '],\n            ] as const;\n\n            tests.forEach(([n, caretIndex, maskedValue]) => {\n                it(`Backspace x${n} => \"${maskedValue}\"`, () => {\n                    cy.get('@input')\n                        .type('{backspace}'.repeat(n))\n                        .should('have.value', maskedValue)\n                        .should('have.prop', 'selectionStart', caretIndex)\n                        .should('have.prop', 'selectionEnd', caretIndex);\n                });\n            });\n        });\n\n        describe('Editing somewhere in the middle of a value (NOT the last character)', () => {\n            beforeEach(() => {\n                cy.get('@input').type('920 424-11-32');\n            });\n\n            it('+7 920 424-1|1-32 => Backspace => +7 920 424-|13-2 => Type \"2\" => +7 920 424-2|1-32', () => {\n                cy.get('@input')\n                    .type('{leftArrow}'.repeat('13-2'.length))\n                    .type('{backspace}')\n                    .should('have.value', '+7 920 424-13-2')\n                    .should('have.prop', 'selectionStart', '+7 920 424-'.length)\n                    .should('have.prop', 'selectionEnd', '+7 920 424-'.length)\n                    .type('2')\n                    .should('have.value', '+7 920 424-21-32')\n                    .should('have.prop', 'selectionStart', '+7 920 424-2'.length)\n                    .should('have.prop', 'selectionEnd', '+7 920 424-2'.length);\n            });\n\n            it('+7 9|20 424-11-32 => Backspace => +7 2|04241132', () => {\n                cy.get('@input')\n                    .type('{leftArrow}'.repeat('20 424-11-32'.length))\n                    .type('{backspace}')\n                    .should('have.value', '+7 204241132')\n                    .should('have.prop', 'selectionStart', '+7 '.length)\n                    .should('have.prop', 'selectionEnd', '+7 '.length);\n            });\n        });\n\n        describe('Text selection', () => {\n            beforeEach(() => {\n                cy.get('@input').type('920 424-11-32');\n            });\n\n            describe(\n                'Select range and press Backspace',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    it('+7 920 424-11-32 => Select \"+7 920 424-|11|-32\" => Backspace => +7 920 424-|32', () => {\n                        cy.get('@input')\n                            .type('{leftArrow}'.repeat('-32'.length))\n                            .realPress(['Shift', ...repeatKey('ArrowLeft', '11'.length)]);\n\n                        cy.get('@input')\n                            .type('{backspace}')\n                            .should('have.value', '+7 920 424-32')\n                            .should('have.prop', 'selectionStart', '+7 920 424-'.length)\n                            .should('have.prop', 'selectionEnd', '+7 920 424-'.length);\n                    });\n\n                    it('+7 920 424-11-32 => Select \"+7 920 424-1|1-3|2\" => Backspace => +7 920 424-1|2', () => {\n                        cy.get('@input')\n                            .type('{leftArrow}')\n                            .realPress([\n                                'Shift',\n                                ...repeatKey('ArrowLeft', '1-3'.length),\n                            ]);\n\n                        cy.get('@input')\n                            .type('{backspace}')\n                            .should('have.value', '+7 920 424-12')\n                            .should('have.prop', 'selectionStart', '+7 920 424-1'.length)\n                            .should('have.prop', 'selectionEnd', '+7 920 424-1'.length);\n                    });\n                },\n            );\n\n            describe(\n                'Select range and type a digit',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    it('+7 920 424-11-32 => Select \"+7 920 424-|11|-32\" => Type \"5\" => +7 920 424-5|3-2', () => {\n                        cy.get('@input')\n                            .type('{leftArrow}'.repeat('-32'.length))\n                            .realPress(['Shift', ...repeatKey('ArrowLeft', '11'.length)]);\n\n                        cy.get('@input')\n                            .type('5')\n                            .should('have.value', '+7 920 424-53-2')\n                            .should('have.prop', 'selectionStart', '+7 920 424-5'.length)\n                            .should('have.prop', 'selectionEnd', '+7 920 424-5'.length);\n                    });\n\n                    it('+7 920 424-11-32 => Select \"+7 920 424-1|1-3|2\" => Type \"5\" => +7 920 424-15-|2', () => {\n                        cy.get('@input')\n                            .type('{leftArrow}')\n                            .realPress([\n                                'Shift',\n                                ...repeatKey('ArrowLeft', '1-3'.length),\n                            ]);\n\n                        cy.get('@input')\n                            .type('5')\n                            .should('have.value', '+7 920 424-15-2')\n                            .should(\n                                'have.prop',\n                                'selectionStart',\n                                '+7 920 424-15-'.length,\n                            )\n                            .should('have.prop', 'selectionEnd', '+7 920 424-15-'.length);\n                    });\n                },\n            );\n        });\n\n        describe('Non-removable country prefix', () => {\n            it('cannot be removed via selectAll + Backspace', () => {\n                cy.get('@input')\n                    .type('9123456789')\n                    .type('{selectall}{backspace}')\n                    .should('have.value', '+7 ')\n                    .should('have.prop', 'selectionStart', '+7 '.length)\n                    .should('have.prop', 'selectionEnd', '+7 '.length);\n            });\n\n            it('cannot be removed via selectAll + Delete', () => {\n                cy.get('@input')\n                    .type('9123456789')\n                    .type('{selectall}{del}')\n                    .should('have.value', '+7 ')\n                    .should('have.prop', 'selectionStart', '+7 '.length)\n                    .should('have.prop', 'selectionEnd', '+7 '.length);\n            });\n\n            it('cannot be removed via Backspace', () => {\n                cy.get('@input')\n                    .type('9123456789')\n                    .type('{backspace}'.repeat('+7 912 345-89'.length))\n                    .should('have.value', '+7 ')\n                    .should('have.prop', 'selectionStart', '+7 '.length)\n                    .should('have.prop', 'selectionEnd', '+7 '.length);\n            });\n\n            it('cannot be removed via Delete', () => {\n                cy.get('@input')\n                    .type('9123456789')\n                    .type('{moveToStart}')\n                    .type('{del}'.repeat('+7 912 345-89'.length))\n                    .should('have.value', '+7 ')\n                    .should('have.prop', 'selectionStart', '+7 '.length)\n                    .should('have.prop', 'selectionEnd', '+7 '.length);\n            });\n\n            it('appears on focus if input is empty', () => {\n                cy.get('@input')\n                    .blur()\n                    .should('have.value', '')\n                    .focus()\n                    .should('have.value', '+7 ')\n                    .should('have.prop', 'selectionStart', '+7 '.length)\n                    .should('have.prop', 'selectionEnd', '+7 '.length);\n            });\n\n            it('disappears on blur if there are no more digits except it', () => {\n                cy.get('@input')\n                    .focus()\n                    .should('have.value', '+7 ')\n                    .blur()\n                    .should('have.value', '');\n            });\n\n            describe('with caret guard', () => {\n                it('forbids to put caret before country prefix', () => {\n                    cy.get('@input')\n                        .should('have.value', '+7 ')\n                        .should('have.prop', 'selectionStart', '+7 '.length)\n                        .should('have.prop', 'selectionEnd', '+7 '.length)\n                        .type('{moveToStart}')\n                        .should('have.value', '+7 ')\n                        .should('have.prop', 'selectionStart', '+7 '.length)\n                        .should('have.prop', 'selectionEnd', '+7 '.length)\n                        .type('{leftArrow}'.repeat(5))\n                        .should('have.value', '+7 ')\n                        .should('have.prop', 'selectionStart', '+7 '.length)\n                        .should('have.prop', 'selectionEnd', '+7 '.length);\n                });\n\n                it('can be selected via selectAll', () => {\n                    cy.get('@input')\n                        .type('9123456789')\n                        .type('{selectall}')\n                        .should('have.value', '+7 912 345-67-89')\n                        .should('have.prop', 'selectionStart', 0)\n                        .should('have.prop', 'selectionEnd', '+7 912 345-67-89'.length);\n                });\n            });\n        });\n    });\n\n    describe('Some countries', () => {\n        it('US: +1 212 343-3355', () => {\n            openCountry('US');\n\n            cy.get('@input').type('2123433355');\n            cy.get('@input').should('have.value', '+1 212 343-3355');\n        });\n\n        it('KZ: +7 771 931-1111', () => {\n            openCountry('KZ');\n\n            cy.get('@input').type('7719311111');\n            cy.get('@input').should('have.value', '+7 771 931-1111');\n        });\n\n        it('BY: +375 44 748-82-69', () => {\n            openCountry('BY');\n\n            cy.get('@input').type('447488269');\n            cy.get('@input').should('have.value', '+375 44 748-82-69');\n        });\n\n        it('TR: +90 539 377-07-43', () => {\n            openCountry('TR');\n\n            cy.get('@input').type('5393770743');\n            cy.get('@input').should('have.value', '+90 539 377-07-43');\n        });\n    });\n});\n\nfunction openCountry(code: string): void {\n    cy.visit(`/${DemoPath.PhonePackage}/API?countryIsoCode=${code}`);\n    cy.get('#demo-content input').should('be.visible').first().focus().as('input');\n}\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/addons/phone/phone-national-trunk-prefix.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Phone | Replaces national trunk prefix by international one on paste', () => {\n    describe('AU | 0 as national trunk prefix', () => {\n        // https://en.wikipedia.org/wiki/Trunk_prefix#Example\n\n        it('[strict=false] Paste 0733333333 => +61 7 3333-3333', () => {\n            cy.visit(`/${DemoPath.PhonePackage}/API?countryIsoCode=AU&strict=false`);\n\n            cy.get('#demo-content input:first-of-type')\n                .focus()\n                .should('have.value', '')\n                .paste('0733333333')\n                .should('have.value', '+61 7 3333-3333');\n        });\n\n        it('[strict=true] Paste 0733333333 => +61 7 3333-3333', () => {\n            cy.visit(`/${DemoPath.PhonePackage}/API?countryIsoCode=AU&strict=true`);\n\n            cy.get('#demo-content input:first-of-type')\n                .focus()\n                .should('have.value', '+61 ')\n                .paste('0733333333')\n                .should('have.value', '+61 7 3333-3333');\n        });\n    });\n\n    describe('RU | 8 as national trunk prefix', () => {\n        it('[strict=false] Paste 89123456789 => +7 912 345-67-89', () => {\n            cy.visit(`/${DemoPath.PhonePackage}/API?countryIsoCode=RU&strict=false`);\n\n            cy.get('#demo-content input:first-of-type')\n                .focus()\n                .should('have.value', '')\n                .paste('89123456789')\n                .should('have.value', '+7 912 345-67-89');\n        });\n\n        it('[strict=true] Paste 89123456789 => +7 912 345-67-89', () => {\n            cy.visit(`/${DemoPath.PhonePackage}/API?countryIsoCode=RU&strict=true`);\n\n            cy.get('#demo-content input:first-of-type')\n                .focus()\n                .should('have.value', '+7 ')\n                .paste('89123456789')\n                .should('have.value', '+7 912 345-67-89');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/addons/phone/phone-non-strict.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\nimport {repeatKey} from '../../utils';\n\ndescribe('Phone', () => {\n    describe('Non-strict', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.PhonePackage}/API?strict=false`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        describe('basic typing (1 character per keydown)', () => {\n            const tests = [\n                // [Typed value, Masked value, caretIndex]\n                ['7920', '+7 920', '+7 920'.length],\n                ['7920341', '+7 920 341', '+7 920 341'.length],\n                ['792034156', '+7 920 341-56', '+7 920 341-56'.length],\n                ['79203415627', '+7 920 341-56-27', '+7 920 341-56-27'.length],\n                ['792034156274234123', '+7 920 341-56-27', '+7 920 341-56-27'.length],\n                ['79 nd4 e', '+7 94', '+7 94'.length],\n            ] as const;\n\n            tests.forEach(([typedValue, maskedValue, caretIndex]) => {\n                it(`Type \"${typedValue}\" => \"${maskedValue}\"`, () => {\n                    cy.get('@input')\n                        .type(typedValue)\n                        .should('have.value', maskedValue)\n                        .should('have.prop', 'selectionStart', caretIndex)\n                        .should('have.prop', 'selectionEnd', caretIndex);\n                });\n            });\n        });\n\n        describe('basic erasing (value = \"+7 920 424-11-32\"', () => {\n            beforeEach(() => {\n                cy.get('@input').type('+79204241132');\n            });\n\n            const tests = [\n                // [How many times \"Backspace\"-key was pressed, caretPosition, Masked value]\n                [1, '+7 920 424-11-3'.length, '+7 920 424-11-3'],\n                [2, '+7 920 424-11'.length, '+7 920 424-11'],\n                [3, '+7 920 424-1'.length, '+7 920 424-1'],\n                [4, '+7 920 424'.length, '+7 920 424'],\n                [13, ''.length, ''],\n            ] as const;\n\n            tests.forEach(([n, caretIndex, maskedValue]) => {\n                it(`Backspace x${n} => \"${maskedValue}\"`, () => {\n                    cy.get('@input')\n                        .type('{backspace}'.repeat(n))\n                        .should('have.value', maskedValue)\n                        .should('have.prop', 'selectionStart', caretIndex)\n                        .should('have.prop', 'selectionEnd', caretIndex);\n                });\n            });\n        });\n\n        describe('Editing somewhere in the middle of a value (NOT the last character)', () => {\n            beforeEach(() => {\n                cy.get('@input').type('+7 920 424-11-32');\n            });\n\n            it('+7 920 424-1|1-32 => Backspace => +7 920 424-|13-2 => Type \"2\" => +7 920 424-2|1-32', () => {\n                cy.get('@input')\n                    .type('{leftArrow}'.repeat('13-2'.length))\n                    .type('{backspace}')\n                    .should('have.value', '+7 920 424-13-2')\n                    .should('have.prop', 'selectionStart', '+7 920 424-'.length)\n                    .should('have.prop', 'selectionEnd', '+7 920 424-'.length)\n                    .type('2')\n                    .should('have.value', '+7 920 424-21-32')\n                    .should('have.prop', 'selectionStart', '+7 920 424-2'.length)\n                    .should('have.prop', 'selectionEnd', '+7 920 424-2'.length);\n            });\n\n            it('+7 9|20 424-11-32 => Backspace => +7 2|04241132', () => {\n                cy.get('@input')\n                    .type('{leftArrow}'.repeat('20 424-11-32'.length))\n                    .type('{backspace}')\n                    .should('have.value', '+7 204241132')\n                    .should('have.prop', 'selectionStart', '+7 '.length)\n                    .should('have.prop', 'selectionEnd', '+7 '.length);\n            });\n        });\n\n        describe('Text selection', () => {\n            beforeEach(() => {\n                cy.get('@input').type('+7 920 424-11-32');\n            });\n\n            describe(\n                'Select range and press Backspace',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    it('+7 920 424-11-32 => Select \"+7 920 424-|11|-32\" => Backspace => +7 920 424-|32', () => {\n                        cy.get('@input')\n                            .type('{leftArrow}'.repeat('-32'.length))\n                            .realPress(['Shift', ...repeatKey('ArrowLeft', '11'.length)]);\n\n                        cy.get('@input')\n                            .type('{backspace}')\n                            .should('have.value', '+7 920 424-32')\n                            .should('have.prop', 'selectionStart', '+7 920 424-'.length)\n                            .should('have.prop', 'selectionEnd', '+7 920 424-'.length);\n                    });\n\n                    it('+7 920 424-11-32 => Select \"+7 920 424-1|1-3|2\" => Backspace => +7 920 424-1|2', () => {\n                        cy.get('@input')\n                            .type('{leftArrow}')\n                            .realPress([\n                                'Shift',\n                                ...repeatKey('ArrowLeft', '1-3'.length),\n                            ]);\n\n                        cy.get('@input')\n                            .type('{backspace}')\n                            .should('have.value', '+7 920 424-12')\n                            .should('have.prop', 'selectionStart', '+7 920 424-1'.length)\n                            .should('have.prop', 'selectionEnd', '+7 920 424-1'.length);\n                    });\n                },\n            );\n\n            describe(\n                'Select range and type a digit',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    it('+7 920 424-11-32 => Select \"+7 920 424-|11|-32\" => Type \"5\" => +7 920 424-5|3-2', () => {\n                        cy.get('@input')\n                            .type('{leftArrow}'.repeat('-32'.length))\n                            .realPress(['Shift', ...repeatKey('ArrowLeft', '11'.length)]);\n\n                        cy.get('@input')\n                            .type('5')\n                            .should('have.value', '+7 920 424-53-2')\n                            .should('have.prop', 'selectionStart', '+7 920 424-5'.length)\n                            .should('have.prop', 'selectionEnd', '+7 920 424-5'.length);\n                    });\n\n                    it('+7 920 424-11-32 => Select \"+7 920 424-1|1-3|2\" => Type \"5\" => +7 920 424-15-|2', () => {\n                        cy.get('@input')\n                            .type('{leftArrow}')\n                            .realPress([\n                                'Shift',\n                                ...repeatKey('ArrowLeft', '1-3'.length),\n                            ]);\n\n                        cy.get('@input')\n                            .type('5')\n                            .should('have.value', '+7 920 424-15-2')\n                            .should(\n                                'have.prop',\n                                'selectionStart',\n                                '+7 920 424-15-'.length,\n                            )\n                            .should('have.prop', 'selectionEnd', '+7 920 424-15-'.length);\n                    });\n                },\n            );\n        });\n\n        describe('Pasting numbers', () => {\n            it('should not cut the last digit when pasting', () => {\n                cy.get('@input')\n                    .paste('12125552365')\n                    .should('have.value', '+1 212 555-2365');\n            });\n\n            it('should merge pasted numbers with existing input', () => {\n                cy.get('@input')\n                    .clear()\n                    .type('+6488')\n                    .paste('85584567')\n                    .should('have.value', '+64 888 558-4567');\n            });\n        });\n    });\n\n    describe('Some countries', () => {\n        it('US: +1 212 343-3355', () => {\n            openCountry('US');\n\n            cy.get('@input').type('12123433355');\n            cy.get('@input').should('have.value', '+1 212 343-3355');\n        });\n\n        it('KZ: +7 771 931-1111', () => {\n            openCountry('KZ');\n\n            cy.get('@input').type('77719311111');\n            cy.get('@input').should('have.value', '+7 771 931-1111');\n        });\n\n        it('BY: +375 44 748-82-69', () => {\n            openCountry('BY');\n\n            cy.get('@input').type('375447488269');\n            cy.get('@input').should('have.value', '+375 44 748-82-69');\n        });\n\n        it('TR: +90 539 377-07-43', () => {\n            openCountry('TR');\n\n            cy.get('@input').type('905393770743');\n            cy.get('@input').should('have.value', '+90 539 377-07-43');\n        });\n    });\n});\n\nfunction openCountry(code: string): void {\n    cy.visit(`/${DemoPath.PhonePackage}/API?strict=false&countryIsoCode=${code}`);\n    cy.get('#demo-content input').should('be.visible').first().focus().as('input');\n}\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/addons/phone/phone-separator.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\nimport {repeatKey} from '../../utils';\n\ndescribe('Phone', () => {\n    describe('Separator', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.PhonePackage}/API?separator$=1`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        describe('basic typing (1 character per keydown)', () => {\n            const tests = [\n                // [Typed value, Masked value, caretIndex]\n                ['920', '+7 920', '+7 920'.length],\n                ['920341', '+7 920 341', '+7 920 341'.length],\n                ['92034156', '+7 920 341 56', '+7 920 341 56'.length],\n                ['9203415627', '+7 920 341 56 27', '+7 920 341 56 27'.length],\n                ['92034156274234123', '+7 920 341 56 27', '+7 920 341 56 27'.length],\n                ['9 nd4 e', '+7 94', '+7 94'.length],\n            ] as const;\n\n            tests.forEach(([typedValue, maskedValue, caretIndex]) => {\n                it(`Type \"${typedValue}\" => \"${maskedValue}\"`, () => {\n                    cy.get('@input')\n                        .type(typedValue)\n                        .should('have.value', maskedValue)\n                        .should('have.prop', 'selectionStart', caretIndex)\n                        .should('have.prop', 'selectionEnd', caretIndex);\n                });\n            });\n        });\n\n        describe('basic erasing (value = \"+7 920 424 11 32\"', () => {\n            beforeEach(() => {\n                cy.get('@input').type('9204241132');\n            });\n\n            const tests = [\n                // [How many times \"Backspace\"-key was pressed, caretPosition, Masked value]\n                [1, '+7 920 424 11 3'.length, '+7 920 424 11 3'],\n                [2, '+7 920 424 11'.length, '+7 920 424 11'],\n                [3, '+7 920 424 1'.length, '+7 920 424 1'],\n                [4, '+7 920 424'.length, '+7 920 424'],\n                [13, '+7 '.length, '+7 '],\n            ] as const;\n\n            tests.forEach(([n, caretIndex, maskedValue]) => {\n                it(`Backspace x${n} => \"${maskedValue}\"`, () => {\n                    cy.get('@input')\n                        .type('{backspace}'.repeat(n))\n                        .should('have.value', maskedValue)\n                        .should('have.prop', 'selectionStart', caretIndex)\n                        .should('have.prop', 'selectionEnd', caretIndex);\n                });\n            });\n        });\n\n        describe('Editing somewhere in the middle of a value (NOT the last character)', () => {\n            beforeEach(() => {\n                cy.get('@input').type('920 424 11 32');\n            });\n\n            it('+7 9|20 424 11 32 => Backspace => +7 2|04241132', () => {\n                cy.get('@input')\n                    .type('{leftArrow}'.repeat('20 424 11 32'.length))\n                    .type('{backspace}')\n                    .should('have.value', '+7 204241132')\n                    .should('have.prop', 'selectionStart', '+7 '.length)\n                    .should('have.prop', 'selectionEnd', '+7 '.length);\n            });\n        });\n\n        describe('Text selection', () => {\n            beforeEach(() => {\n                cy.get('@input').type('920 424 11 32');\n            });\n\n            describe(\n                'Select range and press Backspace',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    it('+7 920 424-11-32 => Select \"+7 920 424 |11| 32\" => Backspace => +7 920 424 |32', () => {\n                        cy.get('@input')\n                            .type('{leftArrow}'.repeat(' 32'.length))\n                            .realPress(['Shift', ...repeatKey('ArrowLeft', '11'.length)]);\n\n                        cy.get('@input')\n                            .type('{backspace}')\n                            .should('have.value', '+7 920 424 32')\n                            .should('have.prop', 'selectionStart', '+7 920 424 '.length)\n                            .should('have.prop', 'selectionEnd', '+7 920 424 '.length);\n                    });\n                },\n            );\n\n            describe(\n                'Select range and type a digit',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    it('+7 920 424-11-32 => Select \"+7 920 424 |11| 32\" => Type \"5\" => +7 920 424 5|3 2', () => {\n                        cy.get('@input')\n                            .type('{leftArrow}'.repeat(' 32'.length))\n                            .realPress(['Shift', ...repeatKey('ArrowLeft', '11'.length)]);\n\n                        cy.get('@input')\n                            .type('5')\n                            .should('have.value', '+7 920 424 53 2')\n                            .should('have.prop', 'selectionStart', '+7 920 424 5'.length)\n                            .should('have.prop', 'selectionEnd', '+7 920 424 5'.length);\n                    });\n                },\n            );\n        });\n    });\n\n    describe('Some countries', () => {\n        it('US: +1 212 343 3355', () => {\n            openCountry('US');\n\n            cy.get('@input').type('2123433355');\n            cy.get('@input').should('have.value', '+1 212 343 3355');\n        });\n\n        it('KZ: +7 771 931 1111', () => {\n            openCountry('KZ');\n\n            cy.get('@input').type('7719311111');\n            cy.get('@input').should('have.value', '+7 771 931 1111');\n        });\n\n        it('BY: +375 44 748 82 69', () => {\n            openCountry('BY');\n\n            cy.get('@input').type('447488269');\n            cy.get('@input').should('have.value', '+375 44 748 82 69');\n        });\n\n        it('TR: +90 539 377 07 43', () => {\n            openCountry('TR');\n\n            cy.get('@input').type('5393770743');\n            cy.get('@input').should('have.value', '+90 539 377 07 43');\n        });\n    });\n});\n\nfunction openCountry(code: string): void {\n    cy.visit(`/${DemoPath.PhonePackage}/API?countryIsoCode=${code}&separator$=1`);\n    cy.get('#demo-content input').should('be.visible').first().focus().as('input');\n}\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/addons/phone/phone-strict.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Phone [strict]=true', () => {\n    describe('[countryIsoCode]=KZ', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.PhonePackage}/API?countryIsoCode=KZ&strict=true`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .should('have.value', '+7 ')\n                .as('input');\n        });\n\n        it('Paste +7 777 (+7 is treated as country prefix => no prefix duplication)', () => {\n            cy.get('@input')\n                .paste('+7 777')\n                .should('have.value', '+7 777')\n                .should('have.prop', 'selectionStart', '+7 777'.length)\n                .should('have.prop', 'selectionEnd', '+7 777'.length);\n        });\n\n        it('Paste +7777 (+7 is treated as country prefix => no prefix duplication)', () => {\n            cy.get('@input')\n                .paste('+7777')\n                .should('have.value', '+7 777')\n                .should('have.prop', 'selectionStart', '+7 777'.length)\n                .should('have.prop', 'selectionEnd', '+7 777'.length);\n        });\n\n        it('Paste 7777 (no plus sign => all 4 sevens are treated as of incomplete value (+7 is added automatically))', () => {\n            cy.get('@input')\n                .paste('7777')\n                .should('have.value', '+7 777 7')\n                .should('have.prop', 'selectionStart', '+7 777 7'.length)\n                .should('have.prop', 'selectionEnd', '+7 777 7'.length);\n        });\n    });\n\n    describe('[countryIsoCode]=RU', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.PhonePackage}/API?countryIsoCode=RU&strict=true`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .should('have.value', '+7 ')\n                .as('input');\n        });\n\n        [\n            '+7 912 345-67-89',\n            '+79123456789',\n            '79123456789', // even without plus sign => no country prefix duplication\n            '89123456789', // 8 should be replaced by +7 automatically\n            '9123456789',\n        ].forEach((value) => {\n            it(`Paste ${value}`, () => {\n                cy.get('@input')\n                    .paste(value)\n                    .should('have.value', '+7 912 345-67-89')\n                    .should('have.prop', 'selectionStart', '+7 912 345-67-89'.length)\n                    .should('have.prop', 'selectionEnd', '+7 912 345-67-89'.length);\n            });\n        });\n    });\n\n    describe('Pasting numbers', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.PhonePackage}/API?countryIsoCode=RU&strict=true`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .should('have.value', '+7 ')\n                .as('input');\n        });\n\n        it('should merge pasted numbers with existing input', () => {\n            cy.get('@input')\n                .clear()\n                .type('987')\n                .paste('654')\n                .should('have.value', '+7 987 654');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/angular/form-control-changes.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Angular FormControl and native input have the same values', () => {\n    beforeEach(() => {\n        cy.visit(`/${DemoPath.Time}/API`);\n\n        cy.get('#demo-content button').should('contain', 'Form value').click();\n\n        cy.get('#demo-content tui-input').as('inputWrapper');\n        cy.get('#demo-content input').should('be.visible').first().focus().as('input');\n    });\n\n    it('on initialization', () => {\n        cy.get('@input').should('have.value', '');\n        cy.get('@inputWrapper').should('have.ngControlValue', '');\n    });\n\n    it('after input a new character', () => {\n        cy.get('@input').type('{moveToStart}').type('2').should('have.value', '2');\n        cy.get('@inputWrapper').should('have.ngControlValue', '2');\n    });\n\n    it('after input many new characters', () => {\n        cy.get('@input').type('{moveToStart}').type('2359').should('have.value', '23:59');\n        cy.get('@inputWrapper').should('have.ngControlValue', '23:59');\n    });\n\n    it('after delete via \"Backspace\"-button', () => {\n        cy.get('@input')\n            .type('{moveToStart}')\n            .type('2359')\n            .type('{backspace}')\n            .should('have.value', '23:5');\n\n        cy.get('@inputWrapper').should('have.ngControlValue', '23:5');\n    });\n\n    it('after delete via \"Delete\"-button', () => {\n        cy.get('@input')\n            .type('{moveToStart}')\n            .type('2359')\n            .type('{leftArrow}'.repeat(2))\n            .type('{del}')\n            .should('have.value', '23:09');\n\n        cy.get('@inputWrapper').should('have.ngControlValue', '23:09');\n    });\n\n    it('after select all + \"Backspace\"-button', () => {\n        cy.get('@input')\n            .type('{moveToStart}')\n            .type('2359')\n            .type('{selectall}{backspace}')\n            .should('have.value', '');\n\n        cy.get('@inputWrapper').should('have.ngControlValue', '');\n    });\n\n    it('after select all + \"Delete\"-button', () => {\n        cy.get('@input')\n            .type('{moveToStart}')\n            .type('2359')\n            .type('{selectall}{del}')\n            .should('have.value', '');\n\n        cy.get('@inputWrapper').should('have.ngControlValue', '');\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/angular/unmask-handler.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Angular | Custom unmask handler', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.Angular);\n\n        cy.get('#unmask [automation-id=\"tui-doc-example\"]').as('example');\n        cy.get('@example').find('input').as('input');\n        cy.get('@example').find('code').as('controlValue');\n        cy.get('@example').find('button').as('patch');\n    });\n\n    it('initial textfield value', () => {\n        cy.get('@input').should('have.value', '1.000,42');\n    });\n\n    it('initial control value', () => {\n        cy.get('@input').should('have.ngControlValue', 1_000.42);\n        cy.get('@controlValue').should('have.text', '1000.42');\n    });\n\n    it('textfield value after programmatic control patch', () => {\n        cy.get('@patch').click();\n        cy.get('@input').should('have.value', '1.234.567,89');\n    });\n\n    it('control value after programmatic control patch', () => {\n        cy.get('@patch').click();\n        cy.get('@input').should('have.ngControlValue', 1_234_567.89);\n        cy.get('@controlValue').should('have.text', '1234567.89');\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/angular/disable-mask-on-null.cy.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nimport {TestInput} from '../utils';\n\ndescribe('@maskito/angular | Disable mask if null is passed as options', () => {\n    describe('type=\"email\" is not compatible with Maskito (it does not have `setSelectionRange`)', () => {\n        it('should throw error if non-nullable options are passed', (done) => {\n            const maskitoOptions: MaskitoOptions = {\n                mask: [/[a-z]/, /[a-z]/, '@', /[a-z]/],\n            };\n\n            cy.mount(TestInput, {\n                componentProperties: {\n                    type: 'email',\n                    maskitoOptions,\n                },\n            });\n\n            cy.on('uncaught:exception', ({message}) => {\n                expect(message).to.include(\n                    \"Failed to execute 'setSelectionRange' on 'HTMLInputElement': The input element's type ('email') does not support selection\",\n                );\n\n                // ensure that an uncaught exception was thrown\n                done();\n            });\n\n            cy.get('input').type('a12bc');\n        });\n\n        it('should not throw any error is options are equal to `null`', () => {\n            cy.mount(TestInput, {\n                componentProperties: {\n                    type: 'email',\n                    maskitoOptions: null,\n                },\n            });\n\n            cy.get('input').type('a12bc').should('have.value', 'a12bc');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/angular/pattern.cy.ts",
    "content": "import {MaskitoPattern} from '@maskito/angular';\n\ndescribe('@maskito/angular | MaskitoPattern', () => {\n    it('set regex over provided MaskitoOptions mask', () => {\n        cy.mount('<input [maskitoPattern]=\"pattern\" />', {\n            imports: [MaskitoPattern],\n            componentProperties: {pattern: /^\\d{0,4}$/},\n        });\n        cy.get('input').type('a12bc').should('have.value', '12');\n    });\n\n    it('set regex from string input', () => {\n        cy.mount('<input maskitoPattern=\"a[1-4]*\" />', {imports: [MaskitoPattern]});\n\n        cy.get('input').type('a12bc34').should('have.value', 'a1234');\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/angular-predicate/angular-predicate.cy.ts",
    "content": "import {ChangeDetectionStrategy, Component, signal} from '@angular/core';\nimport type {ComponentFixture} from '@angular/core/testing';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {\n    MaskitoElement,\n    MaskitoElementPredicate,\n    MaskitoOptions,\n} from '@maskito/core';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nimport {TestInput} from '../utils';\nimport {MultiTestInputComponent} from './multi-test.component';\n\ndescribe('@maskito/angular | Predicate', () => {\n    it('can detect run-time changes', () => {\n        cy.mount(MultiTestInputComponent);\n        cy.get('input').should('be.visible').first().as('card');\n        cy.get('input').should('be.visible').eq(1).as('name');\n\n        cy.get('@card')\n            .focus()\n            .type('12341234abcd12341234')\n            .should('have.value', '1234 1234 1234 1234');\n\n        cy.get('@name').focus().type('12341234abcd12341234').should('have.value', 'ABCD');\n    });\n\n    it('supports asynchronous predicate', () => {\n        const cardMask: MaskitoOptions = {\n            mask: [\n                ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n                ' ',\n                ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n                ' ',\n                ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n                ' ',\n                ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n            ],\n        };\n\n        cy.mount(TestInput, {\n            componentProperties: {\n                maskitoOptions: cardMask,\n                maskitoElementPredicate: async (element: HTMLElement) =>\n                    Promise.resolve(element as MaskitoElement),\n            },\n        });\n        cy.get('input').as('card');\n        cy.get('@card')\n            .focus()\n            .type('12341234abcd12341234')\n            .should('have.value', '1234 1234 1234 1234');\n    });\n\n    describe('ignores the previous predicate if it resolves after the switching to new one (race condition check)', () => {\n        let fixture!: ComponentFixture<unknown>;\n\n        beforeEach(() => {\n            const delay = async (ms: number): Promise<void> =>\n                new Promise((resolve) => {\n                    setTimeout(resolve, ms);\n                });\n\n            const invalidPredicate: MaskitoElementPredicate = async (element) =>\n                delay(1_000).then(() => element.querySelectorAll('input')[0]!);\n            const validPredicate: MaskitoElementPredicate = async (element) =>\n                delay(1_000).then(() => element.querySelectorAll('input')[1]!);\n\n            @Component({\n                imports: [MaskitoDirective],\n                template: `\n                    <div\n                        class=\"wrapper\"\n                        [maskito]=\"maskitoOptions\"\n                        [maskitoElement]=\"elementPredicate()\"\n                    >\n                        <input class=\"hidden-input\" />\n                        <input class=\"real-input\" />\n                    </div>\n                `,\n                changeDetection: ChangeDetectionStrategy.OnPush,\n            })\n            class ComplexTextfield {\n                protected maskitoOptions = maskitoNumberOptionsGenerator();\n                protected elementPredicate = signal(invalidPredicate);\n\n                constructor() {\n                    setTimeout(() => this.elementPredicate.set(validPredicate), 500);\n                }\n            }\n\n            cy.clock();\n            cy.mount(ComplexTextfield).then((res) => {\n                fixture = res.fixture;\n            });\n\n            cy.get('input').first().as('hidden');\n            cy.get('input').last().as('real');\n        });\n\n        it('allows to enter letters in both textfield (before any predicate is resolved)', () => {\n            cy.get('@hidden').focus().type('12abc3').should('have.value', '12abc3');\n            cy.get('@real').focus().type('12abc3').should('have.value', '12abc3');\n        });\n\n        it('allows to enter letters in both textfield (active predicate is changed; both are still resolving)', () => {\n            cy.smartTick(520, {fixture});\n\n            cy.get('@hidden').focus().type('12abc3').should('have.value', '12abc3');\n            cy.get('@real').focus().type('12abc3').should('have.value', '12abc3');\n        });\n\n        it('allows to enter letters in both textfield (invalid predicate was resolved AND SKIPPED; valid is still resolving)', () => {\n            cy.smartTick(520, {fixture});\n            cy.smartTick(500); // invalid predicate was resolved\n\n            cy.get('@hidden').focus().type('12abc3').should('have.value', '12abc3');\n            cy.get('@real').focus().type('12abc3').should('have.value', '12abc3');\n        });\n\n        it('forbids to enter letters only in real textfield (valid and invalid predicates were resolved)', () => {\n            cy.smartTick(520, {fixture});\n            cy.smartTick(500); // invalid predicate was resolved\n            cy.smartTick(500); // valid predicate was resolved\n\n            cy.get('@hidden').focus().type('12abc3').should('have.value', '12abc3');\n            cy.get('@real').focus().type('12abc3').should('have.value', '123');\n        });\n    });\n\n    describe('[maskitoOptions] are changed before long element predicate is resolved', () => {\n        let fixture!: ComponentFixture<unknown>;\n        const SWITCH_OPTIONS_TIME = 1_000;\n        const PREDICATE_RESOLVING_TIME = 2_000;\n\n        beforeEach(() => {\n            @Component({\n                imports: [MaskitoDirective],\n                template: `\n                    <input\n                        [maskito]=\"options()\"\n                        [maskitoElement]=\"elementPredicate\"\n                    />\n                `,\n                changeDetection: ChangeDetectionStrategy.OnPush,\n            })\n            class TestComponent {\n                private readonly numberOptions = {mask: /^\\d+$/};\n                private readonly engLettersOptions = {mask: /^[a-z]+$/i};\n                protected options = signal(this.numberOptions);\n\n                constructor() {\n                    setTimeout(() => {\n                        this.options.set(this.engLettersOptions);\n                    }, SWITCH_OPTIONS_TIME);\n                }\n\n                protected readonly elementPredicate: MaskitoElementPredicate = async (\n                    element,\n                ) =>\n                    new Promise((resolve) => {\n                        setTimeout(\n                            () => resolve(element as HTMLInputElement),\n                            PREDICATE_RESOLVING_TIME,\n                        );\n                    });\n            }\n\n            cy.clock();\n            cy.mount(TestComponent).then((res) => {\n                fixture = res.fixture;\n            });\n        });\n\n        it('can enter any value before no predicate is resolved', () => {\n            cy.get('input').focus().type('12abc3').should('have.value', '12abc3');\n        });\n\n        it('enabling of the first mask should be skipped if [maskitoOptions] were changed during resolving of element predicate', () => {\n            cy.smartTick(PREDICATE_RESOLVING_TIME, {fixture}); // predicate is resolved only once for digit cases\n            cy.get('input').focus().type('12abc3').should('have.value', '12abc3');\n        });\n\n        it('only the last mask should be applied if [maskitoOptions] were changed during resolving of element predicates', () => {\n            cy.smartTick(SWITCH_OPTIONS_TIME + PREDICATE_RESOLVING_TIME, {fixture}); // enough time to resolve element predicated for both cases\n            cy.get('input').focus().type('12abc3').should('have.value', 'abc');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/angular-predicate/multi-test.component.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {MaskitoElementPredicate, MaskitoOptions} from '@maskito/core';\n\n@Component({\n    selector: 'synchronous-test-input',\n    imports: [FormsModule, MaskitoDirective],\n    template: `\n        <div\n            [maskito]=\"card.matches(':focus') ? cardMask : nameMask\"\n            [maskitoElement]=\"card.matches(':focus') ? cardPredicate : namePredicate\"\n        >\n            <input\n                #card\n                [(ngModel)]=\"value.number\"\n            />\n            <input [(ngModel)]=\"value.name\" />\n        </div>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class MultiTestInputComponent {\n    protected value = {\n        number: '',\n        name: '',\n    };\n\n    protected readonly cardMask: MaskitoOptions = {\n        mask: [\n            ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n            ' ',\n            ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n            ' ',\n            ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n            ' ',\n            ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n        ],\n    };\n\n    protected readonly nameMask: MaskitoOptions = {\n        mask: /^[a-z\\s]+$/i,\n        postprocessors: [\n            ({value, selection}) => ({value: value.toUpperCase(), selection}),\n        ],\n    };\n\n    protected readonly cardPredicate: MaskitoElementPredicate = (element) =>\n        element.querySelectorAll('input')[0]!;\n\n    protected readonly namePredicate: MaskitoElementPredicate = (element) =>\n        element.querySelectorAll('input')[1]!;\n}\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/change-event-plugin/change-event-plugin.cy.ts",
    "content": "import {maskitoChangeEventPlugin, type MaskitoOptions} from '@maskito/core';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\nimport {createOutputSpy} from 'cypress/angular';\n\nimport {TestInput} from '../utils';\n\ndescribe('maskitoChangeEventPlugin', () => {\n    const numberMask = maskitoNumberOptionsGenerator({\n        thousandSeparator: ' ',\n        decimalSeparator: '.',\n        maximumFractionDigits: 2,\n    });\n    const maskitoOptions: MaskitoOptions = {\n        ...numberMask,\n        plugins: [...numberMask.plugins, maskitoChangeEventPlugin()],\n    };\n\n    beforeEach(() => {\n        cy.mount(TestInput, {\n            componentProperties: {\n                maskitoOptions,\n                change: createOutputSpy('changeEvent'),\n            },\n        });\n    });\n\n    it('Enter only valid value (Maskito does not prevent any typed character) => only 1 change event on blur', () => {\n        cy.get('input').type('123').should('have.value', '123');\n        cy.get('@changeEvent').should('not.be.called');\n        cy.get('input').blur();\n        cy.get('@changeEvent').should('have.callCount', 1);\n    });\n\n    it('Enter valid value + pseudo decimal separator (Maskito replaces pseudo separator with valid one) => only 1 change event on blur', () => {\n        cy.get('input').type('123,').should('have.value', '123.');\n        cy.get('@changeEvent').should('not.be.called');\n        cy.get('input').blur();\n        cy.get('@changeEvent').should('have.callCount', 1);\n    });\n\n    it('Enter only decimal separator (Maskito pads it with zero) => only 1 change event on blur', () => {\n        cy.get('input').type('.').should('have.value', '0.');\n        cy.get('@changeEvent').should('not.be.called');\n        cy.get('input').blur();\n        cy.get('@changeEvent').should('have.callCount', 1);\n    });\n\n    it('Enter only invalid value (Maskito rejects all typed characters) => no change event', () => {\n        cy.get('input').type('abc').should('have.value', '');\n        cy.get('@changeEvent').should('not.be.called');\n        cy.get('input').blur();\n        cy.get('@changeEvent').should('not.be.called');\n    });\n\n    it('Enter any value value and then erase it again => no change event', () => {\n        cy.get('input').type('123').should('have.value', '123');\n        cy.get('@changeEvent').should('not.be.called');\n        cy.get('input').clear().blur();\n        cy.get('@changeEvent').should('not.be.called');\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/initial-calibration-plugin/dispatch-event.cy.ts",
    "content": "import {maskitoInitialCalibrationPlugin, type MaskitoOptions} from '@maskito/core';\nimport {createOutputSpy} from 'cypress/angular';\n\nimport {TestInput} from '../utils';\n\ndescribe('InitialCalibrationPlugin | count number of dispatched input event', () => {\n    const maskitoOptions: MaskitoOptions = {\n        mask: /^\\d+$/,\n        plugins: [maskitoInitialCalibrationPlugin()],\n    };\n\n    it('Valid initial value => no dispatch of InputEvent', () => {\n        cy.mount(TestInput, {\n            componentProperties: {\n                initialValue: '123',\n                maskitoOptions,\n                input: createOutputSpy('inputEvent'),\n            },\n        });\n\n        cy.get('input').should('have.value', '123');\n        cy.get('@inputEvent').should('not.be.called');\n    });\n\n    it('Invalid initial value => dispatch of InputEvent', () => {\n        cy.mount(TestInput, {\n            componentProperties: {\n                initialValue: '4abc56',\n                maskitoOptions,\n                input: createOutputSpy('inputEvent'),\n            },\n        });\n\n        cy.get('input').should('have.value', '456');\n        cy.get('@inputEvent').should('have.callCount', 1);\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/multi-character-date-segment-separator/multi-character-date-segment-separator.cy.ts",
    "content": "import {\n    maskitoDateOptionsGenerator,\n    maskitoDateRangeOptionsGenerator,\n    maskitoDateTimeOptionsGenerator,\n} from '@maskito/kit';\n\nimport {TestInput} from '../utils';\n\ndescribe('Multi character date segment separator', () => {\n    const multiCharacterSeparator = '. '; // Slovenia;\n\n    [\n        {\n            title: 'Date',\n            maskitoOptions: maskitoDateOptionsGenerator({\n                mode: 'dd/mm/yyyy',\n                separator: multiCharacterSeparator,\n            }),\n            initialValue: '',\n        },\n        {\n            title: 'DateRange (1st date)',\n            maskitoOptions: maskitoDateRangeOptionsGenerator({\n                mode: 'dd/mm/yyyy',\n                dateSeparator: multiCharacterSeparator,\n            }),\n            initialValue: '',\n        },\n        {\n            title: 'DateRange (2nd date)',\n            maskitoOptions: maskitoDateRangeOptionsGenerator({\n                mode: 'dd/mm/yyyy',\n                rangeSeparator: '_',\n                dateSeparator: multiCharacterSeparator,\n            }),\n            initialValue: '12. 04. 1961_',\n        },\n        {\n            title: 'DateTime',\n            maskitoOptions: maskitoDateTimeOptionsGenerator({\n                dateMode: 'dd/mm/yyyy',\n                timeMode: 'HH:MM',\n                dateSeparator: multiCharacterSeparator,\n            }),\n            initialValue: '',\n        },\n    ].forEach(({title, maskitoOptions, initialValue}) => {\n        describe(title, () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions,\n                        initialValue,\n                    },\n                });\n                cy.get('input')\n                    .focus()\n                    .should('have.value', initialValue)\n                    .type('{moveToEnd}');\n            });\n\n            it('Type 31121999 => 31. 12. 1999', () => {\n                cy.get('input')\n                    .type('31121999')\n                    .should('have.value', `${initialValue}31. 12. 1999`);\n            });\n\n            it('Type 999 => 09. 09. 9 (zero padding works)', () => {\n                cy.get('input')\n                    .type('999')\n                    .should('have.value', `${initialValue}09. 09. 9`);\n            });\n\n            it('Type 35 => 03.05 (pads every digit with zero to prevent invalid date segments)', () => {\n                cy.get('input')\n                    .type('3')\n                    .should('have.value', `${initialValue}3`)\n                    .type('5')\n                    .should('have.value', `${initialValue}03. 05`);\n            });\n\n            it('Type 31.15 => 31.1 (prevent to enter impossible month date segment)', () => {\n                cy.get('input').type('3115').should('have.value', `${initialValue}31. 1`);\n            });\n\n            describe('Editing somewhere in the middle of a value (NOT the last character)', () => {\n                it('01.1|2.1998 => Backspace => 01.|02.1998 => Type \"1\" => 01.1|2.1998', () => {\n                    cy.get('input')\n                        .type('01121998')\n                        .type('{leftArrow}'.repeat('2. 1998'.length))\n                        .should(\n                            'have.prop',\n                            'selectionStart',\n                            `${initialValue}01. 1`.length,\n                        )\n                        .should(\n                            'have.prop',\n                            'selectionEnd',\n                            `${initialValue}01. 1`.length,\n                        )\n                        .type('{backspace}')\n                        .should('have.value', `${initialValue}01. 02. 1998`)\n                        .should(\n                            'have.prop',\n                            'selectionStart',\n                            `${initialValue}01. `.length,\n                        )\n                        .should('have.prop', 'selectionEnd', `${initialValue}01. `.length)\n                        .type('1')\n                        .should('have.value', `${initialValue}01. 12. 1998`)\n                        .should(\n                            'have.prop',\n                            'selectionStart',\n                            `${initialValue}01. 1`.length,\n                        )\n                        .should(\n                            'have.prop',\n                            'selectionEnd',\n                            `${initialValue}01. 1`.length,\n                        );\n                });\n\n                it('12|.01.2008 => Backspace => 1|0.01.2008 => Type \"1\" => 11|.01.2008', () => {\n                    cy.get('input')\n                        .type('12012008')\n                        .type('{leftArrow}'.repeat(' .01 .2008'.length))\n                        .should('have.prop', 'selectionStart', `${initialValue}12`.length)\n                        .should('have.prop', 'selectionEnd', `${initialValue}12`.length)\n                        .type('{backspace}')\n                        .should('have.value', `${initialValue}10. 01. 2008`)\n                        .should('have.prop', 'selectionStart', `${initialValue}1`.length)\n                        .should('have.prop', 'selectionEnd', `${initialValue}1`.length)\n                        .type('1')\n                        .should('have.value', `${initialValue}11. 01. 2008`)\n                        .should(\n                            'have.prop',\n                            'selectionStart',\n                            `${initialValue}11. `.length,\n                        )\n                        .should(\n                            'have.prop',\n                            'selectionEnd',\n                            `${initialValue}11. `.length,\n                        );\n                });\n\n                it('12.|12.2010 => Type \"9\" => 12.09.|2010', () => {\n                    cy.get('input')\n                        .type('12122010')\n                        .type('{leftArrow}'.repeat('12. 2010'.length))\n                        .should(\n                            'have.prop',\n                            'selectionStart',\n                            `${initialValue}12. `.length,\n                        )\n                        .should('have.prop', 'selectionEnd', `${initialValue}12. `.length)\n                        .type('9')\n                        .should('have.value', `${initialValue}12. 09. 2010`)\n                        .should(\n                            'have.prop',\n                            'selectionStart',\n                            `${initialValue}12. 09. `.length,\n                        )\n                        .should(\n                            'have.prop',\n                            'selectionEnd',\n                            `${initialValue}12. 09. `.length,\n                        );\n                });\n\n                it('|15.01.2012 => Type \"3\" => 3|0.01.2012', () => {\n                    cy.get('input')\n                        .type('15012012')\n                        .type('{leftArrow}'.repeat('15. 01. 2012'.length))\n                        .should('have.prop', 'selectionStart', initialValue.length)\n                        .should('have.prop', 'selectionEnd', initialValue.length)\n                        .type('3')\n                        .should('have.value', `${initialValue}30. 01. 2012`)\n                        .should('have.prop', 'selectionStart', `${initialValue}3`.length)\n                        .should('have.prop', 'selectionEnd', `${initialValue}3`.length);\n                });\n\n                it('02|.01.2008 => Backspace => 0|1.01.2008 => Type \"5\" => 05|.01.2008', () => {\n                    cy.get('input')\n                        .type('02012008')\n                        .type('{leftArrow}'.repeat('. 01. 2008'.length))\n                        .should('have.prop', 'selectionStart', `${initialValue}02`.length)\n                        .should('have.prop', 'selectionEnd', `${initialValue}02`.length)\n                        .type('{backspace}')\n                        .should('have.value', `${initialValue}01. 01. 2008`)\n                        .should('have.prop', 'selectionStart', `${initialValue}0`.length)\n                        .should('have.prop', 'selectionEnd', `${initialValue}0`.length)\n                        .type('5')\n                        .should('have.value', `${initialValue}05. 01. 2008`)\n                        .should(\n                            'have.prop',\n                            'selectionStart',\n                            `${initialValue}05. `.length,\n                        )\n                        .should(\n                            'have.prop',\n                            'selectionEnd',\n                            `${initialValue}05. `.length,\n                        );\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/native-max-length/native-maxlength-attribute.cy.ts",
    "content": "import {ChangeDetectionStrategy, Component, ElementRef, inject} from '@angular/core';\nimport type {MaskitoOptions} from '@maskito/core';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\nimport {TestInput} from '../utils';\n\ndescribe('Native attribute maxlength works', () => {\n    describe('<input maxlength=\"3\" /> & overwriteMode = shift', () => {\n        beforeEach(() => {\n            const maskitoOptions = maskitoNumberOptionsGenerator({\n                thousandSeparator: ' ',\n            });\n\n            cy.mount(TestInput, {\n                componentProperties: {\n                    maskitoOptions,\n                    maxLength: 3,\n                },\n            });\n            cy.get('input[maxlength=\"3\"]')\n                .should('have.prop', 'maxlength', 3)\n                .as('input');\n        });\n\n        it('accepts 2 digits', () => {\n            cy.get('@input').type('12').should('have.value', '12');\n        });\n\n        it('accepts 3 digits', () => {\n            cy.get('@input').type('123').should('have.value', '123');\n        });\n\n        it(\n            'can replace selected digit by new one (even if length of the value is already equal to maxlength-property)',\n            BROWSER_SUPPORTS_REAL_EVENTS,\n            () => {\n                cy.get('@input').type('123').realPress(['Shift', 'ArrowLeft']);\n\n                cy.get('@input').type('0').should('have.value', '120');\n            },\n        );\n\n        describe('rejects to enter digits more than maxlength-property', () => {\n            it('123| => Type 4 => 123|', () => {\n                cy.get('@input')\n                    .type('1234')\n                    .should('have.value', '123')\n                    .should('have.prop', 'selectionStart', '123'.length)\n                    .should('have.prop', 'selectionEnd', '123'.length);\n            });\n\n            it('12|3 => Type 0 => 12|3', () => {\n                cy.get('@input')\n                    .type('123')\n                    .type('{leftArrow}')\n                    .type('0')\n                    .should('have.value', '123')\n                    .should('have.prop', 'selectionStart', '12'.length)\n                    .should('have.prop', 'selectionEnd', '12'.length);\n            });\n\n            it('1|23 => Type 0 => 1|23', () => {\n                cy.get('@input')\n                    .type('123')\n                    .type('{leftArrow}'.repeat(2))\n                    .type('0')\n                    .should('have.value', '123')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            it('|123 => Type 9 => |123', () => {\n                cy.get('@input')\n                    .type('123')\n                    .type('{moveToStart}')\n                    .type('9')\n                    .should('have.value', '123')\n                    .should('have.prop', 'selectionStart', 0)\n                    .should('have.prop', 'selectionEnd', 0);\n            });\n\n            it('Empty input => Paste 123456789 => 123|', () => {\n                cy.get('@input')\n                    .focus()\n                    .paste('123456789')\n                    .should('have.value', '123')\n                    .should('have.prop', 'selectionStart', '123'.length)\n                    .should('have.prop', 'selectionEnd', '123'.length);\n            });\n        });\n    });\n\n    describe('<input maxlength=\"6\" /> & overwriteMode = replace', () => {\n        beforeEach(() => {\n            const maskitoOptions: MaskitoOptions = {\n                mask: /^[A-F\\d]*$/gi,\n                overwriteMode: 'replace',\n                postprocessors: [\n                    ({value, selection}) => ({\n                        selection,\n                        value: value.toUpperCase(),\n                    }),\n                ],\n            };\n\n            cy.mount(TestInput, {\n                componentProperties: {\n                    maskitoOptions,\n                    maxLength: 6,\n                },\n            });\n            cy.get('input[maxlength=\"6\"]')\n                .should('have.prop', 'maxlength', 6)\n                .as('input');\n        });\n\n        it('accepts valid 526ed3', () => {\n            cy.get('@input').type('526ed3').should('have.value', '526ED3');\n        });\n\n        describe('does not allow to type characters more than [maxlength]', () => {\n            it('many letters', () => {\n                cy.get('@input')\n                    .type('aaabbbcccdddeeefff')\n                    .should('have.value', 'AAABBB');\n            });\n\n            it('many digits', () => {\n                cy.get('@input').type('1234567890').should('have.value', '123456');\n            });\n        });\n\n        it('overwriteMode `replace` works even if value`s length is equal to [maxlength]', () => {\n            cy.get('@input')\n                .type('123456')\n                .type('{leftArrow}'.repeat(3))\n                .type('09')\n                .should('have.value', '123096');\n        });\n    });\n\n    describe('with oversimplified Number mask', () => {\n        beforeEach(() => {\n            const maskitoOptions: MaskitoOptions = {mask: /^\\d*$/};\n\n            cy.mount(TestInput, {\n                componentProperties: {\n                    maskitoOptions,\n                    maxLength: 4,\n                },\n            });\n        });\n\n        it('Press 4 (no more!) digits and click on cleaner => Empty textfield', () => {\n            cy.get('input').type('1234').type('{selectAll}');\n            cy.document().then((doc) => doc.execCommand('delete'));\n            cy.get('input').should('have.value', '');\n        });\n\n        it('Press >4 digits and click on cleaner => Empty textfield', () => {\n            cy.get('input').type('123456').type('{selectAll}');\n            cy.document().then((doc) => doc.execCommand('delete'));\n            cy.get('input').should('have.value', '');\n        });\n    });\n\n    describe('with Number mask', () => {\n        beforeEach(() => {\n            const inputYearMask: MaskitoOptions = maskitoNumberOptionsGenerator({\n                min: 0,\n                max: 9999,\n                thousandSeparator: '',\n            });\n\n            @Component({\n                imports: [TestInput],\n                template: `\n                    <test-input\n                        [maskitoOptions]=\"mask\"\n                        [maxLength]=\"4\"\n                    />\n                    <button\n                        id=\"cleaner\"\n                        type=\"button\"\n                        (click)=\"clear()\"\n                    >\n                        Cleaner\n                    </button>\n                `,\n                changeDetection: ChangeDetectionStrategy.OnPush,\n            })\n            class Sandbox {\n                private readonly el = inject(ElementRef).nativeElement;\n                protected readonly mask = inputYearMask;\n\n                protected clear(): void {\n                    const input = this.el.querySelector('input');\n\n                    input.select();\n                    input.ownerDocument.execCommand('delete');\n                }\n            }\n\n            cy.mount(Sandbox);\n            cy.get('input[maxlength=\"4\"]')\n                .should('have.prop', 'maxlength', 4)\n                .as('input');\n        });\n\n        it('Press 4 (no more!) digits and click on cleaner => Empty textfield', () => {\n            cy.get('@input').type('1234');\n            cy.get('#cleaner').click();\n\n            cy.get('@input').should('have.value', '');\n        });\n\n        it('Press >4 digits and click on cleaner => Empty textfield', () => {\n            cy.get('@input').type('123456789');\n            cy.get('#cleaner').click();\n\n            cy.get('@input').should('have.value', '');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/native-select-method/native-select-method.cy.ts",
    "content": "import {ChangeDetectionStrategy, Component} from '@angular/core';\nimport {MaskitoDirective} from '@maskito/angular';\nimport type {MaskitoOptions} from '@maskito/core';\nimport {maskitoEventHandler} from '@maskito/kit';\n\n@Component({\n    imports: [MaskitoDirective],\n    template: `\n        <input\n            value=\"123\"\n            [maskito]=\"maskitoOptions\"\n        />\n        <textarea [maskito]=\"maskitoOptions\">123</textarea>\n        <div\n            contenteditable=\"true\"\n            [maskito]=\"maskitoOptions\"\n            [textContent]=\"'123'\"\n        ></div>\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TestComponent {\n    protected readonly maskitoOptions: MaskitoOptions = {\n        mask: /^\\d+$/,\n        plugins: [\n            maskitoEventHandler('focus', (element) => element.select(), {once: true}),\n        ],\n    };\n}\n\ndescribe('Native method `.select()` works', () => {\n    ['input', 'textarea'].forEach((selector) => {\n        it(`for <${selector} />`, () => {\n            cy.mount(TestComponent);\n            cy.get(selector)\n                .should('have.value', '123')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0)\n                .should('not.be.focused')\n                .focus()\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 3);\n        });\n    });\n\n    it('for [contenteditable]', () => {\n        cy.mount(TestComponent);\n        cy.get('[contenteditable]')\n            .should('have.text', '123')\n            .should('not.be.focused')\n            .focus()\n            .type('0') // all selected value will be overwritten\n            .should('have.text', '0')\n            .focus()\n            .type('2') // no selection (plugin works only for the first focus), just append value\n            .should('have.text', '02');\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/number/alone-decimal-separator.cy.ts",
    "content": "import {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nimport {TestInput} from '../utils';\n\ndescribe('Number | should drop decimal separator if all digits are erased', () => {\n    beforeEach(() => {\n        cy.mount(TestInput, {\n            componentProperties: {\n                maskitoOptions: maskitoNumberOptionsGenerator({\n                    maximumFractionDigits: 2,\n                    minusSign: '-',\n                }),\n            },\n        });\n        cy.get('input').focus().should('have.value', '');\n    });\n\n    it('empty integer part & NOT empty decimal part => keeps decimal separator untouched', () => {\n        cy.get('input')\n            .type('0.12')\n            .type('{backspace}'.repeat(2))\n            .should('have.value', '0.');\n    });\n\n    it('NOT empty integer part & empty decimal part => keeps decimal separator untouched', () => {\n        cy.get('input')\n            .type('0.12')\n            .type('{moveToStart}')\n            .type('{del}')\n            .should('have.value', '.12');\n    });\n\n    describe('empty integer part & empty decimal part => drops decimal separator', () => {\n        [\n            {minusSign: '', testTitle: 'Without minus'},\n            {minusSign: '-', testTitle: 'With minus'},\n        ].forEach(({testTitle, minusSign}) => {\n            it(testTitle, () => {\n                cy.get('input')\n                    .type(`${minusSign}0.12`)\n                    .type('{backspace}'.repeat(2))\n                    .type(`{moveToStart}${'{rightArrow}'.repeat(minusSign.length)}`)\n                    .type('{del}')\n                    .should('have.value', minusSign)\n                    .should('have.prop', 'selectionStart', minusSign.length)\n                    .should('have.prop', 'selectionEnd', minusSign.length)\n                    // and then repeat everything in reversed order\n                    .type('0.12')\n                    .type(`{moveToStart}${'{rightArrow}'.repeat(minusSign.length)}`)\n                    .type('{del}')\n                    .type('{moveToEnd}')\n                    .type('{backspace}'.repeat(2))\n                    .should('have.value', minusSign)\n                    .should('have.prop', 'selectionStart', minusSign.length)\n                    .should('have.prop', 'selectionEnd', minusSign.length);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/number/min-max-bigint.cy.ts",
    "content": "import {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nimport {TestInput} from '../utils';\n\ndescribe('Number | min/max limits are bigint', () => {\n    beforeEach(() => {\n        cy.mount(TestInput, {\n            componentProperties: {\n                initialValue: '',\n                maskitoOptions: maskitoNumberOptionsGenerator({\n                    min: BigInt(Number.MIN_SAFE_INTEGER) - 777n,\n                    max: BigInt(Number.MAX_SAFE_INTEGER) + 300n,\n                    thousandSeparator: '',\n                    maximumFractionDigits: Infinity,\n                    minusSign: '-',\n                }),\n            },\n        });\n        cy.get('input').focus().should('have.value', '').as('input');\n    });\n\n    describe('max', () => {\n        it('Allows to enter > MAX_SAFE_INTEGER but less than max constraint', () => {\n            cy.get('input')\n                .type(String(BigInt(Number.MAX_SAFE_INTEGER) + 200n))\n                .should('have.value', String(BigInt(Number.MAX_SAFE_INTEGER) + 200n));\n        });\n\n        it('Allows to enter > MAX_SAFE_INTEGER but less than max constraint (even if decimal part contains many digits)', () => {\n            const value = String(\n                `${BigInt(Number.MAX_SAFE_INTEGER) + 100n}.12345678901234567890`,\n            );\n\n            cy.get('input').type(value).should('have.value', value);\n        });\n\n        it('forbids to enter value more than max', () => {\n            cy.get('input')\n                .type(String(BigInt(Number.MAX_SAFE_INTEGER) + 500n))\n                .should('have.value', String(BigInt(Number.MAX_SAFE_INTEGER) + 300n));\n        });\n    });\n\n    describe('min', () => {\n        it('Allows to enter < MIN_SAFE_INTEGER but more than min constraint', () => {\n            cy.get('input')\n                .type(String(BigInt(Number.MIN_SAFE_INTEGER) - 500n))\n                .should('have.value', String(BigInt(Number.MIN_SAFE_INTEGER) - 500n));\n        });\n\n        it('Allows to enter < MIN_SAFE_INTEGER but more than min constraint (even if decimal part contains many digits)', () => {\n            const value = String(\n                `${BigInt(Number.MIN_SAFE_INTEGER) - 500n}.12345678901234567890`,\n            );\n\n            cy.get('input').type(value).should('have.value', value);\n        });\n\n        it('forbids to enter value less than min', () => {\n            cy.get('input')\n                .type(String(BigInt(Number.MIN_SAFE_INTEGER) - 999n))\n                .should('have.value', String(BigInt(Number.MIN_SAFE_INTEGER) - 777n));\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/number/mirrored-prefix-postfix.cy.ts",
    "content": "import {maskitoNumberOptionsGenerator} from '@maskito/kit';\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from 'projects/demo-integrations/src/support/constants';\n\nimport {repeatKey} from '../../utils';\nimport {TestInput} from '../utils';\n\ndescribe('Number | [prefix]=\"$ \" | [postfix]=\" per day\" (without caret guard)', () => {\n    beforeEach(() => {\n        cy.mount(TestInput, {\n            componentProperties: {\n                initialValue: '$ 100 per day',\n                maskitoOptions: maskitoNumberOptionsGenerator({\n                    prefix: '$ ',\n                    postfix: ' per day',\n                }),\n            },\n        });\n        cy.get('input')\n            .focus()\n            .type('{selectAll}{del}')\n            .should('have.value', '$  per day')\n            .should('have.prop', 'selectionStart', '$ '.length)\n            .should('have.prop', 'selectionEnd', '$ '.length)\n            .as('input');\n    });\n\n    it('$  per day| => Type Backspace => $  per da|y', () => {\n        cy.get('@input')\n            .type('{moveToEnd}')\n            .type('{backspace}')\n            .should('have.value', '$  per day')\n            .should('have.prop', 'selectionStart', '$  per da'.length)\n            .should('have.prop', 'selectionEnd', '$  per da'.length);\n    });\n\n    it('$  per da|y => Type Backspace => $  per d|ay', () => {\n        cy.get('@input')\n            .type('{moveToEnd}{leftArrow}')\n            .type('{backspace}')\n            .should('have.value', '$  per day')\n            .should('have.prop', 'selectionStart', '$  per d'.length)\n            .should('have.prop', 'selectionEnd', '$  per d'.length);\n    });\n\n    it(\n        '$  p|er |day => Type Backspace => $  p|er da|y',\n        BROWSER_SUPPORTS_REAL_EVENTS,\n        () => {\n            cy.get('@input')\n                .type('{moveToEnd}')\n                .type('{leftArrow}'.repeat('day'.length))\n                .realPress(['Shift', ...repeatKey('ArrowLeft', '1.1'.length)]);\n\n            cy.get('@input')\n                .type('{backspace}')\n                .should('have.value', '$  per day')\n                .should('have.prop', 'selectionStart', '$  p'.length)\n                .should('have.prop', 'selectionEnd', '$  p'.length);\n        },\n    );\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/number/mirrored-value-postfix.cy.ts",
    "content": "import {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nimport {TestInput} from '../utils';\n\ndescribe('Number | [postfix]=\" EUR\" (no initial value & no caret guard)', () => {\n    beforeEach(() => {\n        cy.mount(TestInput, {\n            componentProperties: {\n                initialValue: '',\n                maskitoOptions: maskitoNumberOptionsGenerator({\n                    postfix: ' EUR',\n                    maximumFractionDigits: 2,\n                }),\n            },\n        });\n        cy.get('input').focus().should('have.value', '').as('input');\n    });\n\n    it('Empty input => Paste \"11.22 \" => 11.22 |EUR', () => {\n        cy.get('input')\n            .paste('11.22 ')\n            .should('have.value', '11.22 EUR')\n            .should('have.prop', 'selectionStart', '11.22 '.length)\n            .should('have.prop', 'selectionEnd', '11.22 '.length);\n    });\n\n    it('Empty input => Paste \"11.22  \" (with two trailing spaces) => 11.22 |EUR', () => {\n        cy.get('input')\n            .paste('11.22  ')\n            .should('have.value', '11.22 EUR')\n            .should('have.prop', 'selectionStart', '11.22 '.length)\n            .should('have.prop', 'selectionEnd', '11.22 '.length);\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/number/multi-character-prefix.cy.ts",
    "content": "import {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nimport {TestInput} from '../utils';\n\ndescribe('Number | multi-character prefix \"EUR \" (no initial value & no caret guard)', () => {\n    beforeEach(() => {\n        cy.mount(TestInput, {\n            componentProperties: {\n                maskitoOptions: maskitoNumberOptionsGenerator({prefix: 'EUR '}),\n            },\n        });\n        cy.get('input').focus().should('have.value', '').as('input');\n    });\n\n    ['E', 'U', 'R'].forEach((char) => {\n        it(`Empty input => Type \"${char} => Textfield's value is \"EUR \"`, () => {\n            cy.get('@input')\n                .type(char)\n                .should('have.value', 'EUR ')\n                .should('have.prop', 'selectionStart', 'EUR '.length)\n                .should('have.prop', 'selectionEnd', 'EUR '.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/number/overwrite-selection-range.cy.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nimport {TestInput} from '../utils';\n\ndescribe('Number | overwrite selection range', () => {\n    const numberOptions = maskitoNumberOptionsGenerator({maximumFractionDigits: 3});\n\n    it('overwrite of selection range inside preprocessor works ', () => {\n        const maskitoOptions: MaskitoOptions = {\n            ...numberOptions,\n            preprocessors: [\n                ({elementState, data}, inputType) => {\n                    const {value, selection} = elementState;\n                    const [start, end] = selection;\n\n                    return {\n                        data,\n                        elementState: {\n                            value,\n                            selection:\n                                inputType === 'insert' &&\n                                !start &&\n                                !end &&\n                                value.startsWith('0')\n                                    ? [0, 1]\n                                    : selection,\n                        },\n                    };\n                },\n                ...numberOptions.preprocessors,\n            ],\n        };\n\n        cy.mount(TestInput, {\n            componentProperties: {maskitoOptions, initialValue: '0.234'},\n        });\n        cy.get('input')\n            .focus()\n            .type('{moveToStart}')\n            .type('1')\n            .should('have.value', '1.234')\n            .should('have.prop', 'selectionStart', 1)\n            .should('have.prop', 'selectionEnd', 1);\n    });\n\n    it('overwrite of selection range inside postprocessor works ', () => {\n        const maskitoOptions: MaskitoOptions = {\n            mask: /^\\d*(?:\\.\\d*)?$/,\n            overwriteMode: 'replace',\n            postprocessors: [\n                (elementState) =>\n                    elementState.value === '.'\n                        ? {value: '0.00', selection: [2, 2]}\n                        : elementState,\n            ],\n        };\n\n        cy.mount(TestInput, {componentProperties: {maskitoOptions}});\n\n        cy.get('input')\n            .type('.')\n            .should('have.value', '0.00')\n            .should('have.prop', 'selectionStart', 2)\n            .should('have.prop', 'selectionEnd', 2);\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/number/postfix-multi-character.cy.ts",
    "content": "import {maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nimport {TestInput} from '../utils';\n\ndescribe('Number | postfix consists of many characters', () => {\n    describe('postfix = ` lbs. per day`', () => {\n        beforeEach(() => {\n            cy.mount(TestInput, {\n                componentProperties: {\n                    maskitoOptions: maskitoNumberOptionsGenerator({\n                        postfix: ' lbs. per day',\n                        thousandSeparator: ' ',\n                        decimalSeparator: '.',\n                        maximumFractionDigits: 2,\n                    }),\n                },\n            });\n            cy.get('input').focus();\n        });\n\n        it('Paste 100<space>', () => {\n            cy.get('input').paste('100 ').should('have.value', '100 lbs. per day');\n        });\n\n        it('Paste 100.<space>', () => {\n            cy.get('input').paste('100.').should('have.value', '100. lbs. per day');\n        });\n\n        it('Paste 100.42<space>', () => {\n            cy.get('input').paste('100.42').should('have.value', '100.42 lbs. per day');\n        });\n\n        it('Paste 100 lbs', () => {\n            cy.get('input').paste('100 lbs').should('have.value', '100 lbs. per day');\n        });\n\n        it('Paste 100 lbs.', () => {\n            cy.get('input').paste('100 lbs.').should('have.value', '100 lbs. per day');\n        });\n\n        it('Paste 100. lbs.', () => {\n            cy.get('input').paste('100. lbs.').should('have.value', '100. lbs. per day');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/number/postfix-with-point.cy.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {\n    maskitoAddOnFocusPlugin,\n    maskitoCaretGuard,\n    maskitoNumberOptionsGenerator,\n    maskitoRemoveOnBlurPlugin,\n} from '@maskito/kit';\n\nimport {TestInput} from '../utils';\n\ndescribe('Number | postfix with point', () => {\n    describe('` lbs.` postfix', () => {\n        describe('Basic', () => {\n            const maskitoOptions = maskitoNumberOptionsGenerator({\n                postfix: ' lbs.',\n                thousandSeparator: ' ',\n                maximumFractionDigits: 2,\n            });\n\n            it('Empty => Type 5 => 5 lbs.', () => {\n                cy.mount(TestInput, {componentProperties: {maskitoOptions}});\n                cy.get('input')\n                    .type('5')\n                    .should('have.value', '5 lbs.')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            it('Empty => Type 12 => 12 lbs.', () => {\n                cy.mount(TestInput, {componentProperties: {maskitoOptions}});\n                cy.get('input')\n                    .type('12')\n                    .should('have.value', '12 lbs.')\n                    .should('have.prop', 'selectionStart', 2)\n                    .should('have.prop', 'selectionEnd', 2);\n            });\n\n            it('Empty => Type 0.42 => 0.42 lbs.', () => {\n                cy.mount(TestInput, {componentProperties: {maskitoOptions}});\n                cy.get('input')\n                    .type('0')\n                    .should('have.value', '0 lbs.')\n                    .type('.')\n                    .should('have.value', '0. lbs.')\n                    .should('have.prop', 'selectionStart', '0.'.length)\n                    .should('have.prop', 'selectionEnd', '0.'.length)\n                    .type('42')\n                    .should('have.value', '0.42 lbs.')\n                    .should('have.prop', 'selectionStart', '0.42'.length)\n                    .should('have.prop', 'selectionEnd', '0.42'.length);\n            });\n        });\n\n        describe('Complex: maskitoCaretGuard + maskitoAddOnFocusPlugin + maskitoRemoveOnBlurPlugin', () => {\n            const postfix = ' lbs.';\n            const numberOptions = maskitoNumberOptionsGenerator({postfix});\n            const maskitoOptions: MaskitoOptions = {\n                ...numberOptions,\n                plugins: [\n                    ...numberOptions.plugins,\n                    maskitoAddOnFocusPlugin(postfix),\n                    maskitoRemoveOnBlurPlugin(postfix),\n                    maskitoCaretGuard((value) => [0, value.length - postfix.length]),\n                ],\n            };\n\n            it('Empty textfield => Focus => | lbs.', () => {\n                cy.mount(TestInput, {componentProperties: {maskitoOptions}});\n\n                cy.get('input')\n                    .should('have.value', '')\n                    .focus()\n                    .should('have.value', postfix)\n                    .should('have.prop', 'selectionStart', 0)\n                    .should('have.prop', 'selectionEnd', 0);\n            });\n\n            it('Textfield contains only postfix => Blur => | Empty textfield', () => {\n                cy.mount(TestInput, {componentProperties: {maskitoOptions}});\n\n                cy.get('input')\n                    .focus()\n                    .should('have.value', postfix)\n                    .blur()\n                    .should('have.value', '');\n            });\n\n            it('| lbs. => Type 5 => 5 lbs.', () => {\n                cy.mount(TestInput, {componentProperties: {maskitoOptions}});\n\n                cy.get('input')\n                    .type('5')\n                    .should('have.value', '5 lbs.')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            it('123| lbs. => Backspace => 12 lbs.', () => {\n                cy.mount(TestInput, {componentProperties: {maskitoOptions}});\n\n                cy.get('input')\n                    .type('123')\n                    .should('have.value', '123 lbs.')\n                    .should('have.prop', 'selectionStart', 3)\n                    .should('have.prop', 'selectionEnd', 3)\n                    .type('{backspace}')\n                    .should('have.value', '12 lbs.')\n                    .should('have.prop', 'selectionStart', 2)\n                    .should('have.prop', 'selectionEnd', 2);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/number/runtime-postfix-changes/runtime-postfix-changes.cy.ts",
    "content": "import {Sandbox} from './sandbox.component';\n\ndescribe('Number | runtime changes of postfix', () => {\n    describe('year & years', () => {\n        beforeEach(() => {\n            cy.mount(Sandbox, {componentProperties: {value: '1 year'}});\n            cy.get('input').focus().should('have.value', '1 year').as('input');\n        });\n\n        it('1| year => Type 0 => 10| years', () => {\n            cy.get('@input')\n                .type('{moveToStart}{rightArrow}')\n                .type('0')\n                .should('have.value', '10 years')\n                .should('have.prop', 'selectionStart', '10'.length)\n                .should('have.prop', 'selectionEnd', '10'.length);\n        });\n\n        it('1| year => Backspace => Empty', () => {\n            cy.get('@input')\n                .type('{moveToStart}{rightArrow}')\n                .type('{backspace}')\n                .should('have.value', '')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('10| years => Backspace => 1| year', () => {\n            cy.get('@input')\n                .type('{moveToStart}{rightArrow}')\n                .type('0')\n                .should('have.value', '10 years')\n                .type('{backspace}')\n                .should('have.value', '1 year')\n                .should('have.prop', 'selectionStart', '1'.length)\n                .should('have.prop', 'selectionEnd', '1'.length);\n        });\n\n        it('select all + delete', () => {\n            cy.get('@input')\n                .should('have.value', '1 year')\n                .type('{selectAll}{del}')\n                .should('have.value', '')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n    });\n\n    describe('mouse & mice (new postfix ends with the same characters as previous postfix; but has different beginning part)', () => {\n        const pluralize = {\n            '=NaN': '',\n            '=1': ' mouse',\n            other: ' mice',\n        };\n\n        it('10| mice => Backspace => 1 mouse', () => {\n            cy.mount(Sandbox, {componentProperties: {pluralize, value: '10 mice'}});\n\n            cy.get('input')\n                .type('{moveToStart}')\n                .type('{rightArrow}'.repeat(2))\n                .type('{backspace}')\n                .should('have.value', '1 mouse')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        it('1| mouse => Type 0 => 10| mice', () => {\n            cy.mount(Sandbox, {componentProperties: {pluralize, value: '1 mouse'}});\n\n            cy.get('input')\n                .type('{moveToStart}')\n                .type('{rightArrow}')\n                .type('0')\n                .should('have.value', '10 mice')\n                .should('have.prop', 'selectionStart', 2)\n                .should('have.prop', 'selectionEnd', 2);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/number/runtime-postfix-changes/sandbox.component.ts",
    "content": "import {I18nPluralPipe} from '@angular/common';\nimport {\n    ChangeDetectionStrategy,\n    Component,\n    computed,\n    input,\n    model,\n    Pipe,\n    type PipeTransform,\n} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {maskitoInitialCalibrationPlugin, type MaskitoOptions} from '@maskito/core';\nimport {maskitoNumberOptionsGenerator, maskitoParseNumber} from '@maskito/kit';\n\n@Pipe({name: 'calculateMask'})\nexport class TestPipe4 implements PipeTransform {\n    public transform(postfix: string): MaskitoOptions {\n        const options = maskitoNumberOptionsGenerator({\n            postfix,\n            thousandSeparator: ' ',\n        });\n\n        return {\n            ...options,\n            plugins: [...options.plugins, maskitoInitialCalibrationPlugin()],\n        };\n    }\n}\n\n@Component({\n    selector: 'test-doc-example-4',\n    imports: [FormsModule, I18nPluralPipe, MaskitoDirective, TestPipe4],\n    template: `\n        <input\n            placeholder=\"Enter number\"\n            [maskito]=\"parsedValue() | i18nPlural: pluralize() | calculateMask\"\n            [(ngModel)]=\"value\"\n        />\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class Sandbox {\n    protected readonly value = model('');\n    protected readonly parsedValue = computed(() => maskitoParseNumber(this.value()));\n\n    protected readonly pluralize = input({\n        '=NaN': '',\n        one: ' year',\n        few: ' years',\n        many: ' years',\n        other: ' years',\n    });\n}\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/number/with-initial-value.cy.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {maskitoCaretGuard, maskitoNumberOptionsGenerator} from '@maskito/kit';\n\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\nimport {repeatKey} from '../../utils';\nimport {TestInput} from '../utils';\n\ndescribe('Number | With initial value', () => {\n    let maskitoOptions!: MaskitoOptions;\n\n    describe('with prefix & postfix', () => {\n        beforeEach(() => {\n            const prefix = '$';\n            const postfix = ' kg';\n\n            const numberOptions = maskitoNumberOptionsGenerator({\n                prefix,\n                postfix,\n                thousandSeparator: ' ',\n            });\n\n            maskitoOptions = {\n                ...numberOptions,\n                plugins: [\n                    ...numberOptions.plugins,\n                    maskitoCaretGuard((value) => [\n                        prefix.length,\n                        value.length - postfix.length,\n                    ]),\n                ],\n            };\n        });\n\n        it('$6 432 kg => Select all + Backspace => $| kg', () => {\n            cy.mount(TestInput, {\n                componentProperties: {maskitoOptions, initialValue: '$6 432 kg'},\n            });\n\n            cy.get('input')\n                .type('{selectAll}{backspace}')\n                .should('have.value', '$ kg')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        it('$6 4|32 kg => Delete => $64|2 kg', () => {\n            cy.mount(TestInput, {\n                componentProperties: {maskitoOptions, initialValue: '$6 432 kg'},\n            });\n\n            cy.get('input')\n                .focus()\n                .type('{moveToEnd}')\n                .should('have.prop', 'selectionStart', '$6 432'.length)\n                .should('have.prop', 'selectionEnd', '$6 432'.length)\n                .type('{leftArrow}'.repeat(2))\n                .should('have.prop', 'selectionStart', '$6 4'.length)\n                .should('have.prop', 'selectionEnd', '$6 4'.length)\n                .type('{del}')\n                .should('have.value', '$642 kg')\n                .should('have.prop', 'selectionStart', '$64'.length)\n                .should('have.prop', 'selectionEnd', '$64'.length);\n        });\n    });\n\n    it('123 45|6 789 => Type 0 (the 1st time input event) => 1 234 50|6 789', () => {\n        cy.mount(TestInput, {\n            componentProperties: {\n                maskitoOptions: maskitoNumberOptionsGenerator({thousandSeparator: ' '}),\n                initialValue: '123 456 789',\n            },\n        });\n\n        cy.get('input')\n            .focus()\n            .type('{moveToStart}')\n            .type('{rightArrow}'.repeat('123 45'.length))\n            .type('0')\n            .should('have.value', '1 234 506 789')\n            .should('have.prop', 'selectionStart', '1 234 50'.length)\n            .should('have.prop', 'selectionEnd', '1 234 50'.length);\n    });\n\n    describe('select all initial value', () => {\n        beforeEach(() => {\n            cy.mount(TestInput, {\n                componentProperties: {\n                    maskitoOptions: maskitoNumberOptionsGenerator({\n                        thousandSeparator: '_',\n                    }),\n                    initialValue: '1_234',\n                },\n            });\n        });\n\n        it('and press Delete', () => {\n            cy.get('input')\n                .focus()\n                .should('have.value', '1_234')\n                .type('{selectAll}{del}')\n                .should('have.value', '')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('and press Backspace', () => {\n            cy.get('input')\n                .focus()\n                .should('have.value', '1_234')\n                .type('{selectAll}{backspace}')\n                .should('have.value', '')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n    });\n\n    describe('select some existing characters and then type new digit', () => {\n        beforeEach(() => {\n            cy.mount(TestInput, {\n                componentProperties: {\n                    maskitoOptions: maskitoNumberOptionsGenerator({\n                        thousandSeparator: '_',\n                    }),\n                    initialValue: '123_456',\n                },\n            });\n        });\n\n        it(\n            'Initial 12|3_456| => Type 9 (the 1st (input) event) => 129|',\n            BROWSER_SUPPORTS_REAL_EVENTS,\n            () => {\n                cy.get('input')\n                    .type('{moveToStart}')\n                    .type('{rightArrow}'.repeat('12'.length))\n                    .realPress(['Shift', ...repeatKey('ArrowRight', '3_456'.length)]);\n\n                cy.get('input')\n                    .type('9')\n                    .should('have.value', '129')\n                    .should('have.prop', 'selectionStart', '129'.length)\n                    .should('have.prop', 'selectionEnd', '129'.length);\n            },\n        );\n\n        it(\n            'Enter 12|3_456| => Type 9 (NOT the 1st (input) event) => 129|',\n            BROWSER_SUPPORTS_REAL_EVENTS,\n            () => {\n                cy.get('input')\n                    .clear()\n                    .type('123_456')\n                    .realPress(['Shift', ...repeatKey('ArrowLeft', '3_456'.length)]);\n\n                cy.get('input')\n                    .type('9')\n                    .should('have.value', '129')\n                    .should('have.prop', 'selectionStart', '129'.length)\n                    .should('have.prop', 'selectionEnd', '129'.length);\n            },\n        );\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/overwrite-mode/overwrite-mode-replace.cy.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\nimport {repeatKey} from '../../utils';\nimport {TestInput} from '../utils';\n\ndescribe('overwriteMode = replace', () => {\n    const digitsOnlyMask: MaskitoOptions = {\n        mask: /^\\d+$/g,\n        overwriteMode: 'replace',\n    };\n\n    describe('selection contains several characters', () => {\n        beforeEach(() => {\n            cy.mount(TestInput, {componentProperties: {maskitoOptions: digitsOnlyMask}});\n        });\n\n        it('12|34| => Press 0 => 120', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n            cy.get('input')\n                .type('1234')\n                .should('have.value', '1234')\n                .realPress(['Shift', ...repeatKey('ArrowLeft', '34'.length)]);\n\n            cy.get('input').type('0').should('have.value', '120');\n        });\n\n        it('1|23|4 => Press 0 => 104', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n            cy.get('input')\n                .type('1234')\n                .should('have.value', '1234')\n                .realPress([\n                    'ArrowLeft',\n                    'Shift',\n                    ...repeatKey('ArrowLeft', '23'.length),\n                ]);\n\n            cy.get('input').type('0').should('have.value', '104');\n        });\n\n        it('1234 => select all => Press 0 => 0', () => {\n            cy.get('input')\n                .type('1234')\n                .should('have.value', '1234')\n                .type('{selectall}')\n                .type('0')\n                .should('have.value', '0');\n        });\n\n        it('1|23|4 => Backspace => 14', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n            cy.get('input')\n                .type('1234')\n                .should('have.value', '1234')\n                .realPress([\n                    'ArrowLeft',\n                    'Shift',\n                    ...repeatKey('ArrowLeft', '23'.length),\n                ]);\n\n            cy.get('input').type('{backspace}').should('have.value', '14');\n        });\n\n        it('1|23|4 => Delete => 14', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n            cy.get('input')\n                .type('1234')\n                .should('have.value', '1234')\n                .realPress([\n                    'ArrowLeft',\n                    'Shift',\n                    ...repeatKey('ArrowLeft', '23'.length),\n                ]);\n\n            cy.get('input').type('{del}').should('have.value', '14');\n        });\n    });\n\n    describe('selectionStart === selectionEnd', () => {\n        beforeEach(() => {\n            cy.mount(TestInput, {componentProperties: {maskitoOptions: digitsOnlyMask}});\n        });\n\n        it('|123 => Press 0 => 0|23', () => {\n            cy.get('input')\n                .type('123')\n                .should('have.value', '123')\n                .type('{moveToStart}')\n                .type('0')\n                .should('have.value', '023')\n                .should('have.a.prop', 'selectionStart', 1)\n                .should('have.a.prop', 'selectionEnd', 1);\n        });\n\n        it('1|23 => Press 0 => 10|3', () => {\n            cy.get('input')\n                .type('123')\n                .should('have.value', '123')\n                .type('{moveToStart}{rightArrow}')\n                .type('0')\n                .should('have.value', '103')\n                .should('have.a.prop', 'selectionStart', 2)\n                .should('have.a.prop', 'selectionEnd', 2);\n        });\n\n        it('12|3 => Press 0 => 120|', () => {\n            cy.get('input')\n                .type('123')\n                .should('have.value', '123')\n                .type('{moveToEnd}{leftArrow}')\n                .type('0')\n                .should('have.value', '120')\n                .should('have.a.prop', 'selectionStart', 3)\n                .should('have.a.prop', 'selectionEnd', 3);\n        });\n\n        it('123| => Press 4 => 1234|', () => {\n            cy.get('input')\n                .type('123')\n                .should('have.value', '123')\n                .type('4')\n                .should('have.value', '1234')\n                .should('have.a.prop', 'selectionStart', 4)\n                .should('have.a.prop', 'selectionEnd', 4);\n        });\n\n        it('12|3 => Press Backspace => 1|3', () => {\n            cy.get('input')\n                .type('123')\n                .should('have.value', '123')\n                .type('{moveToEnd}{leftArrow}')\n                .type('{backspace}')\n                .should('have.value', '13')\n                .should('have.a.prop', 'selectionStart', 1)\n                .should('have.a.prop', 'selectionEnd', 1);\n        });\n\n        it('1|23 => Press Delete => 1|3', () => {\n            cy.get('input')\n                .type('123')\n                .should('have.value', '123')\n                .type('{moveToStart}{rightArrow}')\n                .type('{del}')\n                .should('have.value', '13')\n                .should('have.a.prop', 'selectionStart', 1)\n                .should('have.a.prop', 'selectionEnd', 1);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/paste/cy-paste-utility.cy.ts",
    "content": "import {MaskitoDirective} from '@maskito/angular';\nimport {createOutputSpy} from 'cypress/angular';\n\nimport {TestInput} from '../utils';\n\ndescribe('Ensure cy.paste() emulates required browser features', () => {\n    const maskitoOptions = {mask: /^\\d+$/g};\n\n    describe('Emits `beforeinput` event', () => {\n        beforeEach(() => {\n            cy.mount(TestInput, {\n                componentProperties: {\n                    maskitoOptions,\n                    beforeinput: createOutputSpy('beforeinputEvent'),\n                },\n            });\n            cy.get('input').focus();\n        });\n\n        it('only once', () => {\n            cy.get('input').paste('123');\n            cy.get('@beforeinputEvent').should('have.been.calledOnce');\n        });\n\n        it('with `inputType: insertFromPaste`', () => {\n            cy.get('input').paste('123');\n\n            cy.get('@beforeinputEvent').should('have.been.calledWithMatch', {\n                inputType: 'insertFromPaste',\n            });\n        });\n\n        it('with `data` property', () => {\n            cy.get('input').paste('123');\n\n            cy.get('@beforeinputEvent').should('have.been.calledWithMatch', {\n                data: '123',\n            });\n        });\n    });\n\n    describe('Emits `input` event', () => {\n        beforeEach(() => {\n            cy.mount(TestInput, {\n                componentProperties: {\n                    maskitoOptions,\n                    beforeinput: createOutputSpy('beforeinputEvent'),\n                    input: createOutputSpy('inputEvent'),\n                },\n            });\n            cy.get('input').focus();\n        });\n\n        it('only once', () => {\n            cy.get('input').paste('123');\n            cy.get('@inputEvent').should('have.been.calledOnce');\n        });\n\n        it('after `beforeinput` event', () => {\n            cy.get('input').paste('123');\n            cy.get('@beforeinputEvent').then((beforeSpy) =>\n                cy.get('@inputEvent').should('have.been.calledAfter', beforeSpy),\n            );\n        });\n\n        it('only if the previous `beforeinput` event was not prevented', () => {\n            cy.get('input').paste('abc');\n            cy.get('@beforeinputEvent').should('have.been.calledOnce');\n            cy.get('@inputEvent').should('not.have.been.called');\n        });\n    });\n\n    describe('HTMLInputElement.value', () => {\n        beforeEach(() => {\n            cy.mount(\n                `<input\n                    [maskito]=\"maskitoOptions\"\n                    (beforeinput)=\"beforeinput.emit($event.target.value)\"\n                    (input)=\"input.emit($event.target.value)\"\n                />\n                `,\n                {\n                    imports: [MaskitoDirective],\n                    componentProperties: {\n                        maskitoOptions,\n                        beforeinput: createOutputSpy('beforeinputEvent'),\n                        input: createOutputSpy('inputEvent'),\n                    },\n                },\n            );\n            cy.get('input').focus();\n        });\n\n        it('is not yet updated in `beforeinput` event`', () => {\n            cy.get('input').paste('123');\n            cy.get('@beforeinputEvent').should('have.been.calledOnceWith', '');\n        });\n\n        it('is already updated in `input` event', () => {\n            cy.get('input').paste('123');\n            cy.get('@inputEvent').should('have.been.calledOnceWith', '123');\n        });\n    });\n\n    it('respects the `maxlength` attribute', () => {\n        cy.mount(TestInput, {componentProperties: {maskitoOptions, maxLength: 3}});\n\n        cy.get('input').focus().paste('12345').should('have.value', '123');\n    });\n\n    it('is capable to paste in the middle of already existing value', () => {\n        cy.mount(TestInput, {componentProperties: {maskitoOptions}});\n\n        cy.get('input')\n            .type('15')\n            .type('{leftArrow}')\n            .paste('234')\n            .should('have.value', '12345')\n            .should('have.prop', 'selectionStart', '1234'.length)\n            .should('have.prop', 'selectionEnd', '1234'.length);\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/phone/phone-national-format.cy.ts",
    "content": "import {maskitoPhoneOptionsGenerator} from '@maskito/phone';\nimport metadata from 'libphonenumber-js/min/metadata';\n\nimport {TestInput} from '../utils';\n\ndescribe('Phone | National format', () => {\n    describe('United States', () => {\n        describe('Typing digits', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: maskitoPhoneOptionsGenerator({\n                            countryIsoCode: 'US',\n                            metadata,\n                            format: 'NATIONAL',\n                        }),\n                        initialValue: '',\n                    },\n                });\n            });\n\n            it('Type 2123433355 => (212) 343-3355', () => {\n                cy.get('input')\n                    .focus()\n                    .type('2123433355')\n                    .should('have.value', '(212) 343-3355');\n            });\n\n            it('Type 212 => (212)', () => {\n                cy.get('input').focus().type('212').should('have.value', '(212)');\n            });\n\n            it('Type 2123 => (212) 3', () => {\n                cy.get('input').focus().type('2123').should('have.value', '(212) 3');\n            });\n\n            it('Type 212343 => (212) 343', () => {\n                cy.get('input').focus().type('212343').should('have.value', '(212) 343');\n            });\n        });\n\n        describe('Backspace behavior', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: maskitoPhoneOptionsGenerator({\n                            countryIsoCode: 'US',\n                            metadata,\n                            format: 'NATIONAL',\n                        }),\n                        initialValue: '(212) 343-3355',\n                    },\n                });\n            });\n\n            it('(212) 343-3355| => Backspace => (212) 343-335|', () => {\n                cy.get('input')\n                    .should('have.value', '(212) 343-3355')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{backspace}')\n                    .should('have.value', '(212) 343-335')\n                    .should('have.prop', 'selectionStart', '(212) 343-335'.length)\n                    .should('have.prop', 'selectionEnd', '(212) 343-335'.length);\n            });\n\n            it('(212) 343|-3355 => Backspace => (212) 34|3-355', () => {\n                cy.get('input')\n                    .should('have.value', '(212) 343-3355')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('-3355'.length))\n                    .type('{backspace}')\n                    .should('have.value', '(212) 343-355')\n                    .should('have.prop', 'selectionStart', '(212) 34'.length)\n                    .should('have.prop', 'selectionEnd', '(212) 34'.length);\n            });\n\n            it('(212) 3|43-3355 => Backspace => (212) |433-355', () => {\n                cy.get('input')\n                    .should('have.value', '(212) 343-3355')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('43-3355'.length))\n                    .type('{backspace}')\n                    .should('have.value', '(212) 433-355')\n                    .should('have.prop', 'selectionStart', '(212) '.length)\n                    .should('have.prop', 'selectionEnd', '(212) '.length);\n            });\n        });\n    });\n\n    describe('Russia', () => {\n        describe('Typing digits', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: maskitoPhoneOptionsGenerator({\n                            countryIsoCode: 'RU',\n                            metadata,\n                            format: 'NATIONAL',\n                        }),\n                        initialValue: '',\n                    },\n                });\n            });\n\n            it('Type 9202800155 => 920 280-01-55', () => {\n                cy.get('input')\n                    .focus()\n                    .type('9202800155')\n                    .should('have.value', '920 280-01-55');\n            });\n\n            it('Type 920 => 920', () => {\n                cy.get('input').focus().type('920').should('have.value', '920');\n            });\n\n            it('Type 9202 => 920 2', () => {\n                cy.get('input').focus().type('9202').should('have.value', '920 2');\n            });\n\n            it('Type 920280 => 920 280', () => {\n                cy.get('input').focus().type('920280').should('have.value', '920 280');\n            });\n        });\n\n        describe('Backspace behavior', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: maskitoPhoneOptionsGenerator({\n                            countryIsoCode: 'RU',\n                            metadata,\n                            format: 'NATIONAL',\n                        }),\n                        initialValue: '920 280-01-55',\n                    },\n                });\n            });\n\n            it('920 280-01-55| => Backspace => 920 280-01-5|', () => {\n                cy.get('input')\n                    .should('have.value', '920 280-01-55')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{backspace}')\n                    .should('have.value', '920 280-01-5')\n                    .should('have.prop', 'selectionStart', '920 280-01-5'.length)\n                    .should('have.prop', 'selectionEnd', '920 280-01-5'.length);\n            });\n\n            it('920 280-01|-55 => Backspace => 920 280-0|5-5', () => {\n                cy.get('input')\n                    .should('have.value', '920 280-01-55')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('-55'.length))\n                    .type('{backspace}')\n                    .should('have.value', '920 280-05-5')\n                    .should('have.prop', 'selectionStart', '920 280-0'.length)\n                    .should('have.prop', 'selectionEnd', '920 280-0'.length);\n            });\n\n            it('920 2|80-01-55 => Backspace => 920 |800-15-5', () => {\n                cy.get('input')\n                    .should('have.value', '920 280-01-55')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('80-01-55'.length))\n                    .type('{backspace}')\n                    .should('have.value', '920 800-15-5')\n                    .should('have.prop', 'selectionStart', '920 '.length)\n                    .should('have.prop', 'selectionEnd', '920 '.length);\n            });\n        });\n    });\n\n    describe('Spain', () => {\n        describe('Typing digits', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: maskitoPhoneOptionsGenerator({\n                            countryIsoCode: 'ES',\n                            metadata,\n                            format: 'NATIONAL',\n                        }),\n                        initialValue: '',\n                    },\n                });\n            });\n\n            it('Type 612345678 => 612 34-56-78', () => {\n                cy.get('input')\n                    .focus()\n                    .type('612345678')\n                    .should('have.value', '612 34-56-78');\n            });\n\n            it('Type 612 => 612', () => {\n                cy.get('input').focus().type('612').should('have.value', '612');\n            });\n\n            it('Type 6123 => 612 3', () => {\n                cy.get('input').focus().type('6123').should('have.value', '612 3');\n            });\n\n            it('Type 612345 => 612 34-5', () => {\n                cy.get('input').focus().type('612345').should('have.value', '612 34-5');\n            });\n        });\n\n        describe('Backspace behavior', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: maskitoPhoneOptionsGenerator({\n                            countryIsoCode: 'ES',\n                            metadata,\n                            format: 'NATIONAL',\n                        }),\n                        initialValue: '612 34-56-78',\n                    },\n                });\n            });\n\n            it('612 34-56-78| => Backspace => 612 34-56-7|', () => {\n                cy.get('input')\n                    .should('have.value', '612 34-56-78')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{backspace}')\n                    .should('have.value', '612 34-56-7')\n                    .should('have.prop', 'selectionStart', '612 34-56-7'.length)\n                    .should('have.prop', 'selectionEnd', '612 34-56-7'.length);\n            });\n\n            it('612 34-56|-78 => Backspace => 612 34-5|7-8', () => {\n                cy.get('input')\n                    .should('have.value', '612 34-56-78')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('-78'.length))\n                    .type('{backspace}')\n                    .should('have.value', '612 34-57-8')\n                    .should('have.prop', 'selectionStart', '612 34-5'.length)\n                    .should('have.prop', 'selectionEnd', '612 34-5'.length);\n            });\n\n            it('612 3|4-56-78 => Backspace => 612 |45-67-8', () => {\n                cy.get('input')\n                    .should('have.value', '612 34-56-78')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('4-56-78'.length))\n                    .type('{backspace}')\n                    .should('have.value', '612 45-67-8')\n                    .should('have.prop', 'selectionStart', '612 '.length)\n                    .should('have.prop', 'selectionEnd', '612 '.length);\n            });\n        });\n    });\n\n    describe('France', () => {\n        describe('Typing digits', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: maskitoPhoneOptionsGenerator({\n                            countryIsoCode: 'FR',\n                            metadata,\n                            format: 'NATIONAL',\n                        }),\n                        initialValue: '',\n                    },\n                });\n            });\n\n            it('Type 0612345678 => 06 12-34-56-78', () => {\n                cy.get('input')\n                    .focus()\n                    .type('0612345678')\n                    .should('have.value', '06 12-34-56-78');\n            });\n\n            it('Type 06 => 06', () => {\n                cy.get('input').focus().type('06').should('have.value', '06');\n            });\n\n            it('Type 0612 => 06 12', () => {\n                cy.get('input').focus().type('0612').should('have.value', '06 12');\n            });\n\n            it('Type 061234 => 06 12-34', () => {\n                cy.get('input').focus().type('061234').should('have.value', '06 12-34');\n            });\n        });\n\n        describe('Backspace behavior', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: maskitoPhoneOptionsGenerator({\n                            countryIsoCode: 'FR',\n                            metadata,\n                            format: 'NATIONAL',\n                        }),\n                        initialValue: '06 12-34-56-78',\n                    },\n                });\n            });\n\n            it('06 12-34-56-78| => Backspace => 06 12-34-56-7|', () => {\n                cy.get('input')\n                    .should('have.value', '06 12-34-56-78')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{backspace}')\n                    .should('have.value', '06 12-34-56-7')\n                    .should('have.prop', 'selectionStart', '06 12-34-56-7'.length)\n                    .should('have.prop', 'selectionEnd', '06 12-34-56-7'.length);\n            });\n\n            it('06 12-34-56|-78 => Backspace => 06 12-34-5|7-8', () => {\n                cy.get('input')\n                    .should('have.value', '06 12-34-56-78')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('-78'.length))\n                    .type('{backspace}')\n                    .should('have.value', '06 12-34-57-8')\n                    .should('have.prop', 'selectionStart', '06 12-34-5'.length)\n                    .should('have.prop', 'selectionEnd', '06 12-34-5'.length);\n            });\n\n            it('06 12-3|4-56-78 => Backspace => 06 12-|45-67-8', () => {\n                cy.get('input')\n                    .should('have.value', '06 12-34-56-78')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('4-56-78'.length))\n                    .type('{backspace}')\n                    .should('have.value', '06 12-45-67-8')\n                    .should('have.prop', 'selectionStart', '06 12-'.length)\n                    .should('have.prop', 'selectionEnd', '06 12-'.length);\n            });\n        });\n    });\n\n    describe('Custom separator', () => {\n        beforeEach(() => {\n            cy.mount(TestInput, {\n                componentProperties: {\n                    maskitoOptions: maskitoPhoneOptionsGenerator({\n                        countryIsoCode: 'US',\n                        metadata,\n                        format: 'NATIONAL',\n                        separator: ' ',\n                    }),\n                    initialValue: '',\n                },\n            });\n        });\n\n        it('Type 2123433355 with space separator => (212) 343 3355', () => {\n            cy.get('input')\n                .focus()\n                .type('2123433355')\n                .should('have.value', '(212) 343 3355');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/phone/phone-with-initial-value.cy.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {maskitoPhoneOptionsGenerator} from '@maskito/phone';\nimport metadata from 'libphonenumber-js/min/metadata';\n\nimport {TestInput} from '../utils';\n\ndescribe('Phone | With initial value', () => {\n    describe('Strict mode (Kazakhstan)', () => {\n        // Create fresh options for each test to ensure clean closure state\n        function createMaskitoOptions(): MaskitoOptions {\n            return maskitoPhoneOptionsGenerator({\n                countryIsoCode: 'KZ',\n                metadata,\n                strict: true,\n            });\n        }\n\n        describe('Backspace on initial render', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: createMaskitoOptions(),\n                        initialValue: '+7 771 931-1111',\n                    },\n                });\n            });\n\n            it('+7 771 931-1111| => Backspace => +7 771 931-111|', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+7 771 931-1111')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{backspace}')\n                    .should('have.value', '+7 771 931-111')\n                    .should('have.prop', 'selectionStart', '+7 771 931-111'.length)\n                    .should('have.prop', 'selectionEnd', '+7 771 931-111'.length);\n            });\n\n            it('+7 771 9|31-1111 => Backspace => +7 771 |311-111', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+7 771 931-1111')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('31-1111'.length))\n                    .should('have.prop', 'selectionStart', '+7 771 9'.length)\n                    .should('have.prop', 'selectionEnd', '+7 771 9'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+7 771 311-111')\n                    .should('have.prop', 'selectionStart', '+7 771 '.length)\n                    .should('have.prop', 'selectionEnd', '+7 771 '.length);\n            });\n\n            it('+7 771 93|1-1111 => Backspace => +7 771 9|11-111', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+7 771 931-1111')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('1-1111'.length))\n                    .should('have.prop', 'selectionStart', '+7 771 93'.length)\n                    .should('have.prop', 'selectionEnd', '+7 771 93'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+7 771 911-111')\n                    .should('have.prop', 'selectionStart', '+7 771 9'.length)\n                    .should('have.prop', 'selectionEnd', '+7 771 9'.length);\n            });\n\n            it('+7 771 931|-1111 => Backspace => +7 771 93|1-111', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+7 771 931-1111')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('-1111'.length))\n                    .should('have.prop', 'selectionStart', '+7 771 931'.length)\n                    .should('have.prop', 'selectionEnd', '+7 771 931'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+7 771 931-111')\n                    .should('have.prop', 'selectionStart', '+7 771 93'.length)\n                    .should('have.prop', 'selectionEnd', '+7 771 93'.length);\n            });\n\n            it('+7 77|1 931-1111 => Backspace => +7 7|19 311-111', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+7 771 931-1111')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('1 931-1111'.length))\n                    .should('have.prop', 'selectionStart', '+7 77'.length)\n                    .should('have.prop', 'selectionEnd', '+7 77'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+7 719 311-111')\n                    .should('have.prop', 'selectionStart', '+7 7'.length)\n                    .should('have.prop', 'selectionEnd', '+7 7'.length);\n            });\n        });\n\n        describe('Backspace after typing (confirms mask works when value is typed)', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: createMaskitoOptions(),\n                        initialValue: '+7 ',\n                    },\n                });\n            });\n\n            it('Type value, then backspace in middle works correctly', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .focus()\n                    .type('7719311111')\n                    .should('have.value', '+7 771 931-1111')\n                    // Now test backspace in the middle\n                    .type('{leftArrow}'.repeat('31-1111'.length))\n                    .should('have.prop', 'selectionStart', '+7 771 9'.length)\n                    .should('have.prop', 'selectionEnd', '+7 771 9'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+7 771 311-111')\n                    .should('have.prop', 'selectionStart', '+7 771 '.length)\n                    .should('have.prop', 'selectionEnd', '+7 771 '.length);\n            });\n        });\n    });\n\n    describe('Strict mode (United States)', () => {\n        describe('Backspace on initial render', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: maskitoPhoneOptionsGenerator({\n                            countryIsoCode: 'US',\n                            metadata,\n                            strict: true,\n                        }),\n                        initialValue: '+1 212 343-3355',\n                    },\n                });\n            });\n\n            it('+1 212 343-3355| => Backspace => +1 212 343-335|', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+1 212 343-3355')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{backspace}')\n                    .should('have.value', '+1 212 343-335')\n                    .should('have.prop', 'selectionStart', '+1 212 343-335'.length)\n                    .should('have.prop', 'selectionEnd', '+1 212 343-335'.length);\n            });\n\n            it('+1 212 3|43-3355 => Backspace => +1 212 |433-355', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+1 212 343-3355')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('43-3355'.length))\n                    .should('have.prop', 'selectionStart', '+1 212 3'.length)\n                    .should('have.prop', 'selectionEnd', '+1 212 3'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+1 212 433-355')\n                    .should('have.prop', 'selectionStart', '+1 212 '.length)\n                    .should('have.prop', 'selectionEnd', '+1 212 '.length);\n            });\n\n            it('+1 212 343|-3355 => Backspace => +1 212 34|3-355', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+1 212 343-3355')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('-3355'.length))\n                    .should('have.prop', 'selectionStart', '+1 212 343'.length)\n                    .should('have.prop', 'selectionEnd', '+1 212 343'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+1 212 343-355')\n                    .should('have.prop', 'selectionStart', '+1 212 34'.length)\n                    .should('have.prop', 'selectionEnd', '+1 212 34'.length);\n            });\n\n            it('+1 21|2 343-3355 => Backspace => +1 2|23 433-355', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+1 212 343-3355')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('2 343-3355'.length))\n                    .should('have.prop', 'selectionStart', '+1 21'.length)\n                    .should('have.prop', 'selectionEnd', '+1 21'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+1 223 433-355')\n                    .should('have.prop', 'selectionStart', '+1 2'.length)\n                    .should('have.prop', 'selectionEnd', '+1 2'.length);\n            });\n        });\n    });\n\n    describe('Strict mode (France)', () => {\n        describe('Backspace on initial render', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: maskitoPhoneOptionsGenerator({\n                            countryIsoCode: 'FR',\n                            metadata,\n                            strict: true,\n                        }),\n                        initialValue: '+33 6 12-34-56-78',\n                    },\n                });\n            });\n\n            it('+33 6 12-34-56-78| => Backspace => +33 6 12-34-56-7|', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+33 6 12-34-56-78')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{backspace}')\n                    .should('have.value', '+33 6 12-34-56-7')\n                    .should('have.prop', 'selectionStart', '+33 6 12-34-56-7'.length)\n                    .should('have.prop', 'selectionEnd', '+33 6 12-34-56-7'.length);\n            });\n\n            it('+33 6 12-3|4-56-78 => Backspace => +33 6 12-|45-67-8', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+33 6 12-34-56-78')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('4-56-78'.length))\n                    .should('have.prop', 'selectionStart', '+33 6 12-3'.length)\n                    .should('have.prop', 'selectionEnd', '+33 6 12-3'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+33 6 12-45-67-8')\n                    .should('have.prop', 'selectionStart', '+33 6 12-'.length)\n                    .should('have.prop', 'selectionEnd', '+33 6 12-'.length);\n            });\n\n            it('+33 6 1|2-34-56-78 => Backspace => +33 6 |23-45-67-8', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+33 6 12-34-56-78')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('2-34-56-78'.length))\n                    .should('have.prop', 'selectionStart', '+33 6 1'.length)\n                    .should('have.prop', 'selectionEnd', '+33 6 1'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+33 6 23-45-67-8')\n                    .should('have.prop', 'selectionStart', '+33 6 '.length)\n                    .should('have.prop', 'selectionEnd', '+33 6 '.length);\n            });\n\n            it('+33 6 12-34|-56-78 => Backspace => +33 6 12-3|5-67-8', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+33 6 12-34-56-78')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('-56-78'.length))\n                    .should('have.prop', 'selectionStart', '+33 6 12-34'.length)\n                    .should('have.prop', 'selectionEnd', '+33 6 12-34'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+33 6 12-35-67-8')\n                    .should('have.prop', 'selectionStart', '+33 6 12-3'.length)\n                    .should('have.prop', 'selectionEnd', '+33 6 12-3'.length);\n            });\n        });\n    });\n\n    describe('Non-strict mode (United States)', () => {\n        describe('Backspace on initial render', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: maskitoPhoneOptionsGenerator({\n                            countryIsoCode: 'US',\n                            metadata,\n                            strict: false,\n                        }),\n                        initialValue: '+1 212 343-3355',\n                    },\n                });\n            });\n\n            it('+1 212 343-3355| => Backspace => +1 212 343-335|', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+1 212 343-3355')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{backspace}')\n                    .should('have.value', '+1 212 343-335')\n                    .should('have.prop', 'selectionStart', '+1 212 343-335'.length)\n                    .should('have.prop', 'selectionEnd', '+1 212 343-335'.length);\n            });\n\n            it('+1 212 3|43-3355 => Backspace => +1 212 |433-355', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+1 212 343-3355')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('43-3355'.length))\n                    .should('have.prop', 'selectionStart', '+1 212 3'.length)\n                    .should('have.prop', 'selectionEnd', '+1 212 3'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+1 212 433-355')\n                    .should('have.prop', 'selectionStart', '+1 212 '.length)\n                    .should('have.prop', 'selectionEnd', '+1 212 '.length);\n            });\n\n            it('+1 212 343|-3355 => Backspace => +1 212 34|3-355', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+1 212 343-3355')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('-3355'.length))\n                    .should('have.prop', 'selectionStart', '+1 212 343'.length)\n                    .should('have.prop', 'selectionEnd', '+1 212 343'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+1 212 343-355')\n                    .should('have.prop', 'selectionStart', '+1 212 34'.length)\n                    .should('have.prop', 'selectionEnd', '+1 212 34'.length);\n            });\n        });\n    });\n\n    describe('Non-strict mode (France)', () => {\n        describe('Backspace on initial render', () => {\n            beforeEach(() => {\n                cy.mount(TestInput, {\n                    componentProperties: {\n                        maskitoOptions: maskitoPhoneOptionsGenerator({\n                            countryIsoCode: 'FR',\n                            metadata,\n                            strict: false,\n                        }),\n                        initialValue: '+33 6 12-34-56-78',\n                    },\n                });\n            });\n\n            it('+33 6 12-34-56-78| => Backspace => +33 6 12-34-56-7|', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+33 6 12-34-56-78')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{backspace}')\n                    .should('have.value', '+33 6 12-34-56-7')\n                    .should('have.prop', 'selectionStart', '+33 6 12-34-56-7'.length)\n                    .should('have.prop', 'selectionEnd', '+33 6 12-34-56-7'.length);\n            });\n\n            it('+33 6 12-3|4-56-78 => Backspace => +33 6 12-|45-67-8', () => {\n                cy.get('input')\n                    .should('be.visible')\n                    .should('have.value', '+33 6 12-34-56-78')\n                    .focus()\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat('4-56-78'.length))\n                    .should('have.prop', 'selectionStart', '+33 6 12-3'.length)\n                    .should('have.prop', 'selectionEnd', '+33 6 12-3'.length)\n                    .type('{backspace}')\n                    .should('have.value', '+33 6 12-45-67-8')\n                    .should('have.prop', 'selectionStart', '+33 6 12-'.length)\n                    .should('have.prop', 'selectionEnd', '+33 6 12-'.length);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/placeholder/placeholder-dispatch-input-events.cy.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {maskitoDateOptionsGenerator, maskitoWithPlaceholder} from '@maskito/kit';\nimport {createOutputSpy} from 'cypress/angular';\n\nimport {TestInput} from '../utils';\n\ndescribe('Placeholder | count number of dispatched input event', () => {\n    describe('Card verification code example', () => {\n        const maskitoOptions: MaskitoOptions = {\n            ...maskitoWithPlaceholder('xxx'),\n            mask: /^\\d{0,3}$/,\n        };\n\n        beforeEach(() => {\n            cy.mount(TestInput, {\n                componentProperties: {\n                    maskitoOptions,\n                    input: createOutputSpy('inputEvent'),\n                },\n            });\n        });\n\n        it('Empty => Type 1 => 1|xx', () => {\n            cy.get('input')\n                .focus()\n                .should('have.value', '')\n                .type('1')\n                .should('have.value', '1xx')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n\n            cy.get('@inputEvent').should('have.callCount', 1);\n        });\n\n        it('1|xx => Type 2 => 12|x', () => {\n            cy.get('input')\n                .type('12')\n                .should('have.value', '12x')\n                .should('have.prop', 'selectionStart', 2)\n                .should('have.prop', 'selectionEnd', 2);\n\n            cy.get('@inputEvent').should('have.callCount', 2);\n        });\n\n        it('12|x => Type 3 => 123|', () => {\n            cy.get('input')\n                .type('123')\n                .should('have.value', '123')\n                .should('have.prop', 'selectionStart', 3)\n                .should('have.prop', 'selectionEnd', 3);\n\n            cy.get('@inputEvent').should('have.callCount', 3);\n        });\n\n        it('123| => Backspace => 12|x', () => {\n            cy.get('input')\n                .type('123')\n                .type('{backspace}')\n                .should('have.value', '12x')\n                .should('have.prop', 'selectionStart', 2)\n                .should('have.prop', 'selectionEnd', 2);\n\n            cy.get('@inputEvent').should('have.callCount', 4);\n        });\n\n        it('12|x => Backspace => 1|xx', () => {\n            cy.get('input')\n                .type('12')\n                .type('{backspace}')\n                .should('have.value', '1xx')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n\n            cy.get('@inputEvent').should('have.callCount', 3);\n        });\n\n        it('1|xx => Backspace => |xxx', () => {\n            cy.get('input')\n                .type('1')\n                .type('{backspace}')\n                .should('have.value', 'xxx')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n\n            cy.get('@inputEvent').should('have.callCount', 2);\n        });\n    });\n\n    describe('With built-in Date mask (from @maskito/kit)', () => {\n        const dateOptions = maskitoDateOptionsGenerator({\n            mode: 'dd/mm/yyyy',\n            separator: '/',\n        });\n        const {plugins, ...placeholderOptions} = maskitoWithPlaceholder('dd/mm/yyyy');\n        const maskitoOptions: MaskitoOptions = {\n            ...dateOptions,\n            plugins: plugins.concat(dateOptions.plugins),\n            preprocessors: [\n                ...placeholderOptions.preprocessors,\n                ...dateOptions.preprocessors,\n            ],\n            postprocessors: [\n                ...dateOptions.postprocessors,\n                ...placeholderOptions.postprocessors,\n            ],\n        };\n\n        beforeEach(() => {\n            cy.mount(TestInput, {\n                componentProperties: {\n                    maskitoOptions,\n                    input: createOutputSpy('inputEvent'),\n                },\n            });\n        });\n\n        (\n            [\n                // [typedValue, expectedValue, expectedCaretIndex]\n                ['1', '1d/mm/yyyy', 1],\n                ['12', '12/mm/yyyy', '12'.length],\n                ['129', '12/09/yyyy', '12/09'.length],\n                ['1292', '12/09/2yyy', '12/09/2'.length],\n                ['12920', '12/09/20yy', '12/09/20'.length],\n                ['129202', '12/09/202y', '12/09/202'.length],\n                ['1292023', '12/09/2023', '12/09/2023'.length],\n            ] as const\n        ).forEach(([typedValue, expectedValue, expectedCaretIndex]) => {\n            it(`Empty => Type ${typedValue} => ${expectedValue}`, () => {\n                cy.get('input')\n                    .focus()\n                    .should('have.value', '')\n                    .type(typedValue)\n                    .should('have.value', expectedValue)\n                    .should('have.prop', 'selectionStart', expectedCaretIndex)\n                    .should('have.prop', 'selectionEnd', expectedCaretIndex);\n\n                cy.get('@inputEvent').should('have.callCount', typedValue.length);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/placeholder/placeholder-has-same-characters-as-textfield.cy.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {maskitoWithPlaceholder} from '@maskito/kit';\n\nimport {TestInput} from '../utils';\n\ndescribe('Placeholder | intersects with some characters from textfield value', () => {\n    const maskitoOptions: MaskitoOptions = {\n        ...maskitoWithPlaceholder('DD/MMM/YYYY', true),\n        mask: [\n            /\\d/,\n            /\\d/,\n            '/',\n            /[a-z]/i,\n            /[a-z]/i,\n            /[a-z]/i,\n            '/',\n            /\\d/,\n            /\\d/,\n            /\\d/,\n            /\\d/,\n        ],\n    };\n\n    beforeEach(() => {\n        cy.mount(TestInput, {componentProperties: {maskitoOptions}});\n    });\n\n    it('Empty => focus => show placeholder', () => {\n        cy.get('input').focus().should('have.value', 'DD/MMM/YYYY');\n    });\n\n    it('Empty => Type 31 => 31/MMM/YYYY', () => {\n        cy.get('input')\n            .type('31')\n            .should('have.value', '31/MMM/YYYY')\n            .should('have.prop', 'selectionStart', 2)\n            .should('have.prop', 'selectionEnd', 2)\n            .blur()\n            .should('have.value', '31');\n    });\n\n    it('31|/MMM/YYYY => Type May => 31/May|/YYYY', () => {\n        cy.get('input')\n            .type('31')\n            .should('have.value', '31/MMM/YYYY')\n            .type('M')\n            .should('have.value', '31/MMM/YYYY')\n            .should('have.prop', 'selectionStart', '31/M'.length)\n            .should('have.prop', 'selectionEnd', '31/M'.length)\n            .type('ay')\n            .should('have.value', '31/May/YYYY')\n            .should('have.prop', 'selectionStart', '31/May'.length)\n            .should('have.prop', 'selectionEnd', '31/May'.length)\n            .blur()\n            .should('have.value', '31/May');\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/placeholder/placeholder-partial-removal-on-blur.cy.ts",
    "content": "import {type MaskitoOptions, maskitoUpdateElement} from '@maskito/core';\nimport {maskitoEventHandler, maskitoWithPlaceholder} from '@maskito/kit';\n\nimport {TestInput} from '../utils';\n\ndescribe('Placeholder | partial removal of placeholder characters on blur', () => {\n    const PLACEHOLDER = 'xx xx xx xx##';\n    const {plugins, ...rest} = maskitoWithPlaceholder(PLACEHOLDER);\n\n    const maskitoOptions: MaskitoOptions = {\n        ...rest,\n        mask: PLACEHOLDER.split('').map((x) => (x === 'x' || x === '#' ? /\\d/ : x)),\n        plugins: [\n            ...plugins,\n            maskitoEventHandler('focus', (element) => {\n                const value = element.value || '';\n\n                maskitoUpdateElement(\n                    element,\n                    `${value}${PLACEHOLDER.slice(value.length)}`,\n                );\n            }),\n            maskitoEventHandler('blur', (element) =>\n                maskitoUpdateElement(element, element.value.replaceAll('#', '')),\n            ),\n        ],\n    };\n\n    beforeEach(() => {\n        cy.mount(TestInput, {componentProperties: {maskitoOptions}});\n    });\n\n    it('Empty => focus => show full placeholder', () => {\n        cy.get('input')\n            .should('have.value', '')\n            .focus()\n            .should('have.value', PLACEHOLDER);\n    });\n\n    [\n        {typedCharacters: '99', formattedValue: '99 xx xx xx##', caretIndex: '99'.length},\n        {\n            typedCharacters: '9988',\n            formattedValue: '99 88 xx xx##',\n            caretIndex: '99 88'.length,\n        },\n        {\n            typedCharacters: '99887',\n            formattedValue: '99 88 7x xx##',\n            caretIndex: '99 88 7'.length,\n        },\n        {\n            typedCharacters: '998877',\n            formattedValue: '99 88 77 xx##',\n            caretIndex: '99 88 77'.length,\n        },\n        {\n            typedCharacters: '99887766',\n            formattedValue: '99 88 77 66##',\n            caretIndex: '99 88 77 66'.length,\n        },\n        {\n            typedCharacters: '998877665',\n            formattedValue: '99 88 77 665#',\n            caretIndex: '99 88 77 665'.length,\n        },\n        {\n            typedCharacters: '9988776654',\n            formattedValue: '99 88 77 6654',\n            caretIndex: '99 88 77 6654'.length,\n        },\n    ].forEach(({typedCharacters, formattedValue, caretIndex}) => {\n        it(`Only placeholder characters => Type ${typedCharacters} => ${formattedValue}`, () => {\n            cy.get('input')\n                .type(typedCharacters)\n                .should('have.value', formattedValue)\n                .should('have.prop', 'selectionStart', caretIndex)\n                .should('have.prop', 'selectionEnd', caretIndex);\n        });\n\n        const withoutHashtags = formattedValue.replaceAll('#', '');\n\n        it(`Focused textfield with ${formattedValue} => Blur => ${withoutHashtags}`, () => {\n            cy.get('input')\n                .type(typedCharacters)\n                .blur()\n                .should('have.value', withoutHashtags);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/react/async-predicate-options-race/reactApp.tsx",
    "content": "import type {MaskitoElementPredicate, MaskitoOptions} from '@maskito/core';\nimport {useMaskito} from '@maskito/react';\nimport {type ComponentType, useEffect, useState} from 'react';\n\nimport {AwesomeInput} from '../awesomeInput';\n\nexport const SWITCH_OPTIONS_TIME = 1_000;\nexport const PREDICATE_RESOLVING_TIME = 2_000;\n\nconst numberOptions: MaskitoOptions = {mask: /^\\d+$/};\nconst engLettersOptions: MaskitoOptions = {mask: /^[a-z]+$/i};\n\nconst elementPredicate: MaskitoElementPredicate = async (element) =>\n    new Promise((resolve) => {\n        setTimeout(() => resolve(element.querySelector('.real-input') as HTMLInputElement), PREDICATE_RESOLVING_TIME);\n    });\n\nexport const App: ComponentType = () => {\n    const [options, setOptions] = useState(numberOptions);\n    const maskRef = useMaskito({options, elementPredicate});\n\n    useEffect(() => {\n        setTimeout(() => {\n            setOptions(engLettersOptions);\n        }, SWITCH_OPTIONS_TIME);\n    }, []);\n\n    return <AwesomeInput ref={maskRef} />;\n};\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/react/async-predicate-options-race/reactAsyncPredicateOptionsRace.cy.tsx",
    "content": "import {mount} from 'cypress/react';\n\nimport {App, PREDICATE_RESOLVING_TIME, SWITCH_OPTIONS_TIME} from './reactApp';\n\ndescribe('React async predicate + maskitoOptions race', () => {\n    beforeEach(() => {\n        cy.clock();\n        mount(<App />);\n        cy.get('.real-input').should('be.visible').as('textfield');\n    });\n\n    it('can enter any value before no predicate is resolved', () => {\n        cy.get('@textfield').focus().type('12abc3').should('have.value', '12abc3');\n    });\n\n    it('enabling of the first mask should be skipped if `options` were changed during resolving of element predicate', () => {\n        cy.smartTick(PREDICATE_RESOLVING_TIME); // predicate is resolved only once for digit cases\n        cy.get('@textfield').focus().type('12abc3').should('have.value', '12abc3');\n    });\n\n    it('only the last mask should be applied if [maskitoOptions] were changed during resolving of element predicates', () => {\n        cy.smartTick(SWITCH_OPTIONS_TIME + PREDICATE_RESOLVING_TIME); // enough time to resolve element predicated for both cases\n        cy.get('@textfield').focus().type('12abc3').should('have.value', 'abc');\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/react/async-predicates-race/reactApp.tsx",
    "content": "import {type MaskitoElementPredicate, maskitoInitialCalibrationPlugin, type MaskitoOptions} from '@maskito/core';\nimport {maskitoTimeOptionsGenerator} from '@maskito/kit';\nimport {useMaskito} from '@maskito/react';\nimport {type ComponentType, useEffect, useState} from 'react';\n\nimport {AwesomeInput} from '../awesomeInput';\n\nconst timeOptions = maskitoTimeOptionsGenerator({mode: 'HH:MM'});\n\nconst options: MaskitoOptions = {\n    ...timeOptions,\n    plugins: [...timeOptions.plugins, maskitoInitialCalibrationPlugin()],\n};\n\nconst correctPredicate: MaskitoElementPredicate = (host) => host.querySelector<HTMLInputElement>('.real-input')!;\nconst wrongPredicate: MaskitoElementPredicate = (host) => host.querySelector('input')!;\n\nconst longCorrectPredicate: MaskitoElementPredicate = async (host) =>\n    new Promise((resolve) => {\n        setTimeout(() => {\n            resolve(correctPredicate(host));\n        }, 2_000);\n    });\n\nconst longInvalidPredicate: MaskitoElementPredicate = async (host) =>\n    new Promise((resolve) => {\n        setTimeout(() => resolve(wrongPredicate(host)), 7_000);\n    });\n\nconst fastValidPredicate: MaskitoElementPredicate = async (host) =>\n    new Promise((resolve) => {\n        setTimeout(() => resolve(correctPredicate(host)), 500);\n    });\n\nexport const App: ComponentType = () => {\n    const [useCorrectPredicate, setUseCorrectPredicate] = useState(false);\n    const inputRef2sec = useMaskito({options, elementPredicate: longCorrectPredicate});\n    const inputRefRaceCondition = useMaskito({\n        options,\n        elementPredicate: useCorrectPredicate ? fastValidPredicate : longInvalidPredicate,\n    });\n\n    useEffect(() => {\n        setTimeout(() => {\n            setUseCorrectPredicate(true);\n        }, 2_000);\n    }, []);\n\n    return (\n        <>\n            <AwesomeInput\n                ref={inputRef2sec}\n                id=\"async-predicate-2s-resolves\"\n                placeholder=\"Async predicate (2s)\"\n            />\n\n            <AwesomeInput\n                ref={inputRefRaceCondition}\n                id=\"race-condition-check\"\n                placeholder=\"Race condition check\"\n            />\n        </>\n    );\n};\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/react/async-predicates-race/reactAsyncPredicatesRace.cy.tsx",
    "content": "import {mount} from 'cypress/react';\n\nimport {App} from './reactApp';\n\ndescribe('Async predicate works', () => {\n    describe('Basic async predicate (it returns promise which resolves in 2s)', () => {\n        beforeEach(() => {\n            cy.clock();\n            mount(<App />);\n            cy.get('#async-predicate-2s-resolves').should('be.visible').as('input');\n        });\n\n        it('does not apply mask until `elementPredicate` resolves', () => {\n            const typedText = 'Element predicate will resolves only in 2000 ms';\n\n            cy.get('@input').type(typedText);\n\n            cy.smartTick(300);\n            cy.get('@input').should('have.value', typedText);\n            cy.smartTick(700);\n            cy.get('@input').should('have.value', typedText);\n            cy.smartTick(2000);\n            cy.get('@input').should('have.value', '20:00');\n        });\n\n        it('rejects invalid character (after `elementPredicate` resolves)', () => {\n            cy.smartTick(2_000);\n\n            cy.get('@input').type('0taiga_family').should('have.value', '0');\n        });\n\n        it('automatically adds fixed characters (after `elementPredicate` resolves)', () => {\n            cy.smartTick(2_000);\n\n            cy.get('@input').type('1234').should('have.value', '12:34');\n        });\n\n        it('automatically pads time segments with zeroes for large digits (after `elementPredicate` resolves)', () => {\n            cy.smartTick(2_000);\n\n            cy.get('@input').type('99').should('have.value', '09:09');\n        });\n    });\n\n    describe('race condition check', () => {\n        beforeEach(() => {\n            cy.clock();\n            mount(<App />);\n            cy.get('#race-condition-check').should('be.visible').as('input');\n        });\n\n        it('does not apply mask until the first (fast valid) `elementPredicate` resolves', () => {\n            const typedText = 'UseEffect will be triggered in 2s and predicate will resolve only in 0.5 seconds';\n\n            cy.get('@input').type(typedText);\n\n            cy.smartTick(500); // Selected predicate is longInvalidPredicate (pending state)\n            cy.get('@input').should('have.value', typedText);\n\n            cy.smartTick(1000); // Selected predicate is longInvalidPredicate (still pending state)\n            cy.get('@input').should('have.value', typedText);\n\n            cy.smartTick(600); // Selected predicate is fastValidPredicate (pending state)\n            cy.get('@input').should('have.value', typedText);\n\n            cy.smartTick(1000); // Selected predicate is fastValidPredicate (promise is resolved)\n            cy.get('@input').should('have.value', '20:5');\n        });\n\n        it('ignores the previous predicate if it resolves after the switching to new one', () => {\n            cy.smartTick(10_000);\n\n            cy.get('@input').type('taiga1134 family').should('have.value', '11:34');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/react/awesomeInput.tsx",
    "content": "import {forwardRef, type InputHTMLAttributes} from 'react';\n\nconst hiddenInputStyles = {display: 'none'};\n\nexport const AwesomeInput = forwardRef<HTMLDivElement, InputHTMLAttributes<HTMLInputElement>>((props, ref) => (\n    <div ref={ref}>\n        <input style={hiddenInputStyles} />\n        <input\n            className=\"real-input\"\n            {...props}\n        />\n        <input style={hiddenInputStyles} />\n    </div>\n));\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/react/change-event/changeEvent.cy.tsx",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {maskitoNumberOptionsGenerator} from '@maskito/kit';\nimport {useMaskito} from '@maskito/react';\nimport {mount} from 'cypress/react';\nimport {type ChangeEvent, type JSX, useCallback, useState} from 'react';\n\ndescribe('React synthetic \"onChange\" event', () => {\n    describe('uncontrolled input', () => {\n        beforeEach(() => {\n            const handler = cy.spy().as('handler');\n            const options = maskitoNumberOptionsGenerator({maximumFractionDigits: 2, thousandSeparator: ' '});\n\n            function App(): JSX.Element {\n                const maskRef = useMaskito({options});\n\n                return (\n                    <input\n                        ref={maskRef}\n                        onChange={(e) => handler(e.target.value)}\n                    />\n                );\n            }\n\n            mount(<App />);\n        });\n\n        it('type valid value (no mask interference) => onChange is dispatched', () => {\n            cy.get('input').type('0').should('have.value', '0');\n            cy.get('@handler').should('have.been.calledOnceWith', '0');\n        });\n\n        it('type invalid value (mask rejects it) => onChange is NOT dispatched', () => {\n            cy.get('input').type('t').should('have.value', '');\n            cy.get('@handler').should('not.have.been.called');\n        });\n\n        it('type partially valid value (mask corrects it and patches textfield) => onChange is dispatched ONCE', () => {\n            cy.get('input').type(',').should('have.value', '0.');\n            cy.get('@handler').should('have.been.calledOnceWith', '0.');\n        });\n\n        it('paste partially valid value (mask corrects it and patches textfield) => onChange is dispatched ONCE', () => {\n            cy.get('input').focus().paste('123456,78').should('have.value', '123 456.78');\n            cy.get('@handler').should('have.been.calledOnceWith', '123 456.78');\n        });\n    });\n\n    describe('controlled input', () => {\n        beforeEach(() => {\n            const capitalize = (x: string): string => `${x.charAt(0).toUpperCase()}${x.slice(1)}`;\n            const handler = cy.spy().as('handler');\n            const options: MaskitoOptions = {\n                mask: /^[a-z]+$/i,\n                postprocessors: [({value, selection}) => ({value: value.replaceAll('t', 'T'), selection})],\n            };\n\n            function App(): JSX.Element {\n                const maskRef = useMaskito({options});\n                const [value, setValue] = useState('');\n                const onChange = useCallback(\n                    ({target: {value}}: ChangeEvent<HTMLInputElement>) => {\n                        handler(value);\n                        setValue(capitalize(value));\n                    },\n                    [setValue, handler],\n                );\n\n                return (\n                    <input\n                        ref={maskRef}\n                        value={value}\n                        onChange={onChange}\n                    />\n                );\n            }\n\n            mount(<App />);\n        });\n\n        it('type valid value (no mask interference) => onChange is dispatched', () => {\n            cy.get('input').type('N').should('have.value', 'N');\n            cy.get('@handler').should('have.been.calledOnceWith', 'N');\n            cy.get('input').type('i').should('have.value', 'Ni');\n            cy.get('@handler').should('have.been.calledWith', 'Ni');\n        });\n\n        it('type invalid value (mask rejects it) => onChange is NOT dispatched', () => {\n            cy.get('input').type('123').should('have.value', '');\n            cy.get('@handler').should('not.have.been.called');\n        });\n\n        it('type partially valid value (mask corrects it and patches textfield) => onChange is dispatched', () => {\n            cy.get('input').type('Nikit').should('have.value', 'NikiT');\n            cy.get('@handler').should('have.been.calledWith', 'NikiT');\n        });\n\n        it('type partially valid value (state action corrects it and patches textfield) => onChange is dispatched with initial value', () => {\n            cy.get('input').type('n').should('have.value', 'N');\n            cy.get('@handler').should('have.been.calledWith', 'n');\n        });\n\n        it('paste partially valid value (mask+state action correct it and patches textfield) => onChange is dispatched ONCE', () => {\n            cy.get('input').focus().paste('nikita').should('have.value', 'NikiTa');\n            cy.get('@handler').should('have.been.calledOnceWith', 'nikiTa');\n        });\n    });\n\n    describe('controlled input with noop state handler', () => {\n        beforeEach(() => {\n            const handler = cy.spy().as('handler');\n            const options: MaskitoOptions = {\n                mask: /^[a-z]+$/i,\n                postprocessors: [({value, selection}) => ({value: value.toUpperCase(), selection})],\n            };\n\n            function App(): JSX.Element {\n                const maskRef = useMaskito({options});\n                const [value] = useState('');\n\n                return (\n                    <input\n                        ref={maskRef}\n                        value={value}\n                        onChange={(e) => handler(e.target.value)}\n                    />\n                );\n            }\n\n            mount(<App />);\n        });\n\n        it('type invalid value (mask rejects it) => onChange is NOT dispatched', () => {\n            cy.get('input').type('123').should('have.value', '');\n            cy.get('@handler').should('not.have.been.called');\n        });\n\n        it('type valid value (no mask interference) => textfield value is still empty', () => {\n            cy.get('input').type('T').should('have.value', '');\n            cy.get('@handler').should('have.been.calledWith', 'T');\n        });\n\n        it('type partially valid value (mask corrects it and patches textfield) => textfield value is still empty', () => {\n            cy.get('input').type('t').should('have.value', '');\n            cy.get('@handler').should('have.been.calledWith', 'T');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/component-testing/utils.ts",
    "content": "import {\n    ChangeDetectionStrategy,\n    Component,\n    EventEmitter,\n    Input,\n    Output,\n} from '@angular/core';\nimport {MaskitoDirective} from '@maskito/angular';\nimport {MASKITO_DEFAULT_ELEMENT_PREDICATE, type MaskitoOptions} from '@maskito/core';\n\n@Component({\n    selector: 'test-input',\n    imports: [MaskitoDirective],\n    template: `\n        <input\n            [attr.maxlength]=\"maxLength\"\n            [attr.type]=\"type\"\n            [attr.value]=\"initialValue\"\n            [maskito]=\"maskitoOptions\"\n            [maskitoElement]=\"maskitoElementPredicate\"\n            (beforeinput)=\"beforeinput.emit($event)\"\n            (change)=\"change.emit($event)\"\n            (input)=\"input.emit($event)\"\n        />\n    `,\n    changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TestInput {\n    @Input()\n    public initialValue = '';\n\n    @Input()\n    public maskitoOptions: MaskitoOptions | null = null;\n\n    @Input()\n    public maskitoElementPredicate = MASKITO_DEFAULT_ELEMENT_PREDICATE;\n\n    @Output()\n    public readonly beforeinput = new EventEmitter();\n\n    @Output()\n    public readonly input = new EventEmitter();\n\n    @Output()\n    public readonly change = new EventEmitter();\n\n    @Input()\n    public maxLength = Infinity;\n\n    @Input()\n    public type: HTMLInputElement['type'] = 'text';\n}\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date/date-basic.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\nimport {repeatKey} from '../../utils';\n\ndescribe('Date', () => {\n    describe('Basic', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?mode=dd%2Fmm%2Fyyyy`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        describe('basic typing (1 character per keydown)', () => {\n            const tests = [\n                // [Typed value, Masked value, caretIndex]\n                ['1', '1', 1],\n                ['12', '12', '12'.length],\n                ['121', '12.1', '12.1'.length],\n                ['1211', '12.11', '12.11'.length],\n                ['0', '0', 1],\n                ['00', '0', '0'.length],\n            ] as const;\n\n            tests.forEach(([typedValue, maskedValue, caretIndex]) => {\n                it(`Type \"${typedValue}\" => \"${maskedValue}\"`, () => {\n                    cy.get('@input')\n                        .type(typedValue)\n                        .should('have.value', maskedValue)\n                        .should('have.prop', 'selectionStart', caretIndex)\n                        .should('have.prop', 'selectionEnd', caretIndex);\n                });\n            });\n\n            it('Type \"9\" => \"09|\"', () => {\n                cy.get('@input')\n                    .type('9')\n                    .should('have.value', '09')\n                    .should('have.prop', 'selectionStart', '09'.length)\n                    .should('have.prop', 'selectionEnd', '09'.length);\n            });\n\n            it('27| => type 9 => 27.09|', () => {\n                cy.get('@input')\n                    .type('27')\n                    .should('have.value', '27')\n                    .should('have.prop', 'selectionStart', '27'.length)\n                    .should('have.prop', 'selectionEnd', '27'.length)\n                    .type('9')\n                    .should('have.value', '27.09')\n                    .should('have.prop', 'selectionStart', '27.09'.length)\n                    .should('have.prop', 'selectionEnd', '27.09'.length);\n            });\n\n            it('3| => Type 7 => 03.07|', () => {\n                cy.get('@input')\n                    .type('3')\n                    .should('have.value', '3')\n                    .should('have.prop', 'selectionStart', '3'.length)\n                    .should('have.prop', 'selectionEnd', '3'.length)\n                    .type('7')\n                    .should('have.value', '03.07')\n                    .should('have.prop', 'selectionStart', '03.07'.length)\n                    .should('have.prop', 'selectionEnd', '03.07'.length);\n            });\n\n            it('year less than 100', () => {\n                cy.get('@input')\n                    .type('10100012')\n                    .should('have.value', '10.10.0012')\n                    .should('have.prop', 'selectionStart', '10.10.0012'.length)\n                    .should('have.prop', 'selectionEnd', '10.10.0012'.length);\n            });\n        });\n\n        describe('basic erasing (value = \"10.11.2002\" & caret is placed after the last value)', () => {\n            beforeEach(() => {\n                cy.get('@input').type('10112002');\n            });\n\n            const tests = [\n                // [How many times \"Backspace\"-key was pressed, caretPosition, Masked value]\n                [1, '10.11.200'.length, '10.11.200'],\n                [2, '10.11.20'.length, '10.11.20'],\n                [3, '10.11.2'.length, '10.11.2'],\n                [4, '10.11'.length, '10.11'],\n            ] as const;\n\n            tests.forEach(([n, caretIndex, maskedValue]) => {\n                it(`Backspace x${n} => \"${maskedValue}\"`, () => {\n                    cy.get('@input')\n                        .type('{backspace}'.repeat(n))\n                        .should('have.value', maskedValue)\n                        .should('have.prop', 'selectionStart', caretIndex)\n                        .should('have.prop', 'selectionEnd', caretIndex);\n                });\n            });\n\n            it('Delete => no value change && no caret index change', () => {\n                cy.get('@input')\n                    .type('{del}')\n                    .should('have.value', '10.11.2002')\n                    .should('have.prop', 'selectionStart', '10.11.2002'.length)\n                    .should('have.prop', 'selectionEnd', '10.11.2002'.length);\n            });\n\n            it('Type `deleteWordBackward` of `InputEvent` works', () => {\n                cy.get('@input')\n                    .type('{ctrl+backspace}')\n                    .should('have.value', '')\n                    .should('have.prop', 'selectionStart', ''.length)\n                    .should('have.prop', 'selectionEnd', ''.length);\n            });\n\n            it('Type `deleteWordForward` of `InputEvent` works', () => {\n                cy.get('@input')\n                    .type('{moveToStart}')\n                    .type('{ctrl+del}')\n                    .should('have.value', '')\n                    .should('have.prop', 'selectionStart', ''.length)\n                    .should('have.prop', 'selectionEnd', ''.length);\n            });\n\n            it('Type `deleteSoftLineForward` of `InputEvent` works', () => {\n                cy.get('@input')\n                    .type('{moveToStart}')\n                    .trigger('beforeinput', {inputType: 'deleteSoftLineForward'})\n                    .trigger('input', {inputType: 'deleteSoftLineForward'})\n                    .should('have.value', '')\n                    .should('have.prop', 'selectionStart', ''.length)\n                    .should('have.prop', 'selectionEnd', ''.length);\n            });\n\n            it('Type `deleteSoftLineBackward` of `InputEvent` works', () => {\n                cy.get('@input')\n                    .trigger('beforeinput', {inputType: 'deleteSoftLineBackward'})\n                    .trigger('input', {inputType: 'deleteSoftLineBackward'})\n                    .should('have.value', '')\n                    .should('have.prop', 'selectionStart', ''.length)\n                    .should('have.prop', 'selectionEnd', ''.length);\n            });\n        });\n\n        describe('Editing somewhere in the middle of a value (NOT the last character)', () => {\n            it('01.1|2.1998 => Backspace => 01.|02.1998 => Type \"1\" => 01.1|2.1998', () => {\n                cy.get('@input')\n                    .type('01121998')\n                    .type('{leftArrow}'.repeat(6))\n                    .should('have.prop', 'selectionStart', '01.1'.length)\n                    .should('have.prop', 'selectionEnd', '01.1'.length)\n                    .type('{backspace}')\n                    .should('have.value', '01.02.1998')\n                    .should('have.prop', 'selectionStart', '01.'.length)\n                    .should('have.prop', 'selectionEnd', '01.'.length)\n                    .type('1')\n                    .should('have.value', '01.12.1998')\n                    .should('have.prop', 'selectionStart', '01.1'.length)\n                    .should('have.prop', 'selectionEnd', '01.1'.length);\n            });\n\n            it('12|.01.2008 => Backspace => 1|0.01.2008 => Type \"1\" => 11|.01.2008', () => {\n                cy.get('@input')\n                    .type('12012008')\n                    .type('{leftArrow}'.repeat('.01.2008'.length))\n                    .should('have.prop', 'selectionStart', '12'.length)\n                    .should('have.prop', 'selectionEnd', '12'.length)\n                    .type('{backspace}')\n                    .should('have.value', '10.01.2008')\n                    .should('have.prop', 'selectionStart', '1'.length)\n                    .should('have.prop', 'selectionEnd', '1'.length)\n                    .type('1')\n                    .should('have.value', '11.01.2008')\n                    .should('have.prop', 'selectionStart', '11:'.length)\n                    .should('have.prop', 'selectionEnd', '11:'.length);\n            });\n\n            it('12.|12.2010 => Type \"9\" => 12.09.|2010', () => {\n                cy.get('@input')\n                    .type('12122010')\n                    .type('{leftArrow}'.repeat('12.2010'.length))\n                    .should('have.prop', 'selectionStart', '12.'.length)\n                    .should('have.prop', 'selectionEnd', '12.'.length)\n                    .type('9')\n                    .should('have.value', '12.09.2010')\n                    .should('have.prop', 'selectionStart', '12.09.'.length)\n                    .should('have.prop', 'selectionEnd', '12.09.'.length);\n            });\n\n            it('|15.01.2012 => Type \"3\" => 3|0.01.2012', () => {\n                cy.get('@input')\n                    .type('15012012')\n                    .type('{moveToStart}')\n                    .should('have.prop', 'selectionStart', 0)\n                    .should('have.prop', 'selectionEnd', 0)\n                    .type('3')\n                    .should('have.value', '30.01.2012')\n                    .should('have.prop', 'selectionStart', '3'.length)\n                    .should('have.prop', 'selectionEnd', '3'.length);\n            });\n\n            it('02|.01.2008 => Backspace => 0|1.01.2008 => Type \"5\" => 05|.01.2008', () => {\n                cy.get('@input')\n                    .type('02012008')\n                    .type('{leftArrow}'.repeat('.01.2008'.length))\n                    .should('have.prop', 'selectionStart', '02'.length)\n                    .should('have.prop', 'selectionEnd', '02'.length)\n                    .type('{backspace}')\n                    .should('have.value', '01.01.2008')\n                    .should('have.prop', 'selectionStart', '0'.length)\n                    .should('have.prop', 'selectionEnd', '0'.length)\n                    .type('5')\n                    .should('have.value', '05.01.2008')\n                    .should('have.prop', 'selectionStart', '05.'.length)\n                    .should('have.prop', 'selectionEnd', '05.'.length);\n            });\n        });\n\n        describe('Fixed values', () => {\n            it('Press Backspace after fixed value => no value change => move caret to the left', () => {\n                cy.get('@input')\n                    .type('10122022')\n                    .type('{leftArrow}'.repeat('2022'.length))\n                    .should('have.prop', 'selectionStart', '10.12.'.length)\n                    .should('have.prop', 'selectionEnd', '10.12.'.length)\n                    .type('{backspace}')\n                    .should('have.value', '10.12.2022')\n                    .should('have.prop', 'selectionStart', '10.12'.length)\n                    .should('have.prop', 'selectionEnd', '10.12'.length);\n            });\n\n            it('Press Delete after fixed value => no value change => move caret to the right', () => {\n                cy.get('@input')\n                    .type('10122022')\n                    .type('{leftArrow}'.repeat('.2022'.length))\n                    .should('have.prop', 'selectionStart', '10.12'.length)\n                    .should('have.prop', 'selectionEnd', '10.12'.length)\n                    .type('{del}')\n                    .should('have.value', '10.12.2022')\n                    .should('have.prop', 'selectionStart', '10.12.'.length)\n                    .should('have.prop', 'selectionEnd', '10.12.'.length);\n            });\n        });\n\n        describe('Text selection', () => {\n            describe('Select range and press Backspace', () => {\n                it(\n                    '10.|12|.2022 => Backspace => 10.|01.2022',\n                    BROWSER_SUPPORTS_REAL_EVENTS,\n                    () => {\n                        cy.get('@input')\n                            .type('10122022')\n                            .type('{leftArrow}'.repeat('.2022'.length))\n                            .realPress([\n                                'Shift',\n                                ...repeatKey('ArrowLeft', '12'.length),\n                                'Backspace',\n                            ]);\n\n                        cy.get('@input')\n                            .should('have.value', '10.01.2022')\n                            .should('have.prop', 'selectionStart', '10.'.length)\n                            .should('have.prop', 'selectionEnd', '10.'.length);\n                    },\n                );\n\n                it(\n                    '1|1.1|1.2011 => Backspace => 1|0.01.2011',\n                    BROWSER_SUPPORTS_REAL_EVENTS,\n                    () => {\n                        cy.get('@input')\n                            .type('11112011')\n                            .type('{leftArrow}'.repeat('1.2011'.length))\n                            .realPress([\n                                'Shift',\n                                ...repeatKey('ArrowLeft', '1.1'.length),\n                                'Backspace',\n                            ]);\n\n                        cy.get('@input')\n                            .should('have.value', '10.01.2011')\n                            .should('have.prop', 'selectionStart', '1'.length)\n                            .should('have.prop', 'selectionEnd', '1'.length);\n                    },\n                );\n            });\n\n            describe('Select range and press new digit', () => {\n                it(\n                    '|12|.11.2022 => Press 3 => 3|0.11.2022',\n                    BROWSER_SUPPORTS_REAL_EVENTS,\n                    () => {\n                        cy.get('@input')\n                            .type('12112022')\n                            .type('{leftArrow}'.repeat('.11.2022'.length))\n                            .realPress(['Shift', ...repeatKey('ArrowLeft', '12'.length)]);\n\n                        cy.get('@input')\n                            .type('3')\n                            .should('have.value', '30.11.2022')\n                            .should('have.prop', 'selectionStart', '3'.length)\n                            .should('have.prop', 'selectionEnd', '3'.length);\n                    },\n                );\n\n                it(\n                    '|12|.11.2022 => Press 0 => 0|1.11.2022',\n                    BROWSER_SUPPORTS_REAL_EVENTS,\n                    () => {\n                        cy.get('@input')\n                            .type('12112022')\n                            .type('{leftArrow}'.repeat('.11.2022'.length))\n                            .realPress(['Shift', ...repeatKey('ArrowLeft', '12'.length)]);\n\n                        cy.get('@input')\n                            .type('0')\n                            .should('have.value', '01.11.2022')\n                            .should('have.prop', 'selectionStart', '0'.length)\n                            .should('have.prop', 'selectionEnd', '0'.length);\n                    },\n                );\n\n                it('|12.11.2022| => Press 0 => 0|', () => {\n                    cy.get('@input')\n                        .type('12112022')\n                        .type('{selectAll}')\n                        .type('0')\n                        .should('have.value', '0')\n                        .should('have.prop', 'selectionStart', '0'.length)\n                        .should('have.prop', 'selectionEnd', '0'.length);\n                });\n\n                it('|12.11.2022| => Press 1 => 1|', () => {\n                    cy.get('@input')\n                        .type('12112022')\n                        .type('{selectAll}')\n                        .type('1')\n                        .should('have.value', '1')\n                        .should('have.prop', 'selectionStart', '1'.length)\n                        .should('have.prop', 'selectionEnd', '1'.length);\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date/date-fullwidth-to-halfwidth.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\n/* NOTE: yyyy/mm/dd is very common in Japan */\ndescribe('Date', () => {\n    describe('Full width character parsing', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?mode=yyyy%2Fmm%2Fdd&separator=%2F`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        describe('Accepts all of full width characters', () => {\n            it('２０１９１２３１ => 2019/12/31', () => {\n                cy.get('@input')\n                    .type('２０１９１２３１')\n                    .should('have.value', '2019/12/31')\n                    .should('have.prop', 'selectionStart', '2019/12/31'.length)\n                    .should('have.prop', 'selectionEnd', '2019/12/31'.length);\n            });\n        });\n\n        describe('pads digits with zero if date segment exceeds its max possible value', () => {\n            // NOTE: months can be > 12 => pads the first 2 with zero\n            it('２０１０２| => type ９ => 2010/02|', () => {\n                cy.get('@input')\n                    .type('２０１０２')\n                    .should('have.value', '2010/02')\n                    .should('have.prop', 'selectionStart', '2010/02'.length)\n                    .should('have.prop', 'selectionEnd', '2010/02'.length)\n                    .type('９')\n                    .should('have.value', '2010/02/09')\n                    .should('have.prop', 'selectionStart', '2010/02/09'.length)\n                    .should('have.prop', 'selectionEnd', '2010/02/09'.length);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date/date-min-max.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Date', () => {\n    describe('Max date', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?max=2020-05-05`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('Input less than max value', () => {\n            cy.get('@input')\n                .type('31122019')\n                .should('have.value', '31.12.2019')\n                .should('have.prop', 'selectionStart', '31.12.2019'.length)\n                .should('have.prop', 'selectionEnd', '31.12.2019'.length);\n        });\n\n        it('05.12.202| => type 5 => 05.05.2020 (max value)', () => {\n            cy.get('@input')\n                .type('0512202')\n                .should('have.value', '05.12.202')\n                .should('have.prop', 'selectionStart', '05.12.202'.length)\n                .should('have.prop', 'selectionEnd', '05.12.202'.length)\n                .type('5')\n                .should('have.value', '05.05.2020')\n                .should('have.prop', 'selectionStart', '05.05.2020'.length)\n                .should('have.prop', 'selectionEnd', '05.05.2020'.length);\n        });\n\n        it('0|3.05.2020 => type 7 => 05|.05.2020 (max value)', () => {\n            cy.get('@input')\n                .type('03052020')\n                .type('{moveToStart}{rightArrow}')\n                .type('7')\n                .should('have.value', '05.05.2020')\n                .should('have.prop', 'selectionStart', '05.'.length)\n                .should('have.prop', 'selectionEnd', '05.'.length);\n        });\n\n        it('03.0|5.2020 => type 7 => 05.05|.2020 (max value)', () => {\n            cy.get('@input')\n                .type('03052020')\n                .type('{leftArrow}'.repeat('5.2020'.length))\n                .type('7')\n                .should('have.value', '05.05.2020')\n                .should('have.prop', 'selectionStart', '05.05.'.length)\n                .should('have.prop', 'selectionEnd', '05.05.'.length);\n        });\n    });\n\n    describe('Min date', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?min=2020-05-05`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('Input more than min value', () => {\n            cy.get('@input')\n                .type('20102022')\n                .should('have.value', '20.10.2022')\n                .should('have.prop', 'selectionStart', '20.10.2022'.length)\n                .should('have.prop', 'selectionEnd', '20.10.2022'.length);\n        });\n\n        it('05.12.201| => type 9 => 05.05.2020 (min value)', () => {\n            cy.get('@input')\n                .type('0512201')\n                .should('have.value', '05.12.201')\n                .should('have.prop', 'selectionStart', '05.12.201'.length)\n                .should('have.prop', 'selectionEnd', '05.12.201'.length)\n                .type('9')\n                .should('have.value', '05.05.2020')\n                .should('have.prop', 'selectionStart', '05.05.2020'.length)\n                .should('have.prop', 'selectionEnd', '05.05.2020'.length);\n        });\n\n        it('0|7.05.2020 => type 2 => 05|.05.2020 (min value)', () => {\n            cy.get('@input')\n                .type('07052020')\n                .type('{moveToStart}{rightArrow}')\n                .type('2')\n                .should('have.value', '05.05.2020')\n                .should('have.prop', 'selectionStart', '05.'.length)\n                .should('have.prop', 'selectionEnd', '05.'.length);\n        });\n\n        it('03.0|6.2020 => type 2 => 05.05|.2020 (min value)', () => {\n            cy.get('@input')\n                .type('03062020')\n                .type('{leftArrow}'.repeat('6.2020'.length))\n                .type('2')\n                .should('have.value', '05.05.2020')\n                .should('have.prop', 'selectionStart', '05.05.'.length)\n                .should('have.prop', 'selectionEnd', '05.05.'.length);\n        });\n    });\n\n    describe('Min date 2000', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?min=2000-01-01`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('Input less than min value', () => {\n            cy.get('@input')\n                .type('10101997')\n                .should('have.value', '01.01.2000')\n                .should('have.prop', 'selectionStart', '01.01.2000'.length)\n                .should('have.prop', 'selectionEnd', '01.01.2000'.length);\n        });\n    });\n\n    describe('Max date, shortened year', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?max=2020-05-05&mode=mm%2Fyy&separator=%2F`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('Input less than max value', () => {\n            cy.get('@input')\n                .type('0419')\n                .should('have.value', '04/19')\n                .should('have.prop', 'selectionStart', '04/19'.length)\n                .should('have.prop', 'selectionEnd', '04/19'.length);\n        });\n\n        it('03/2| => type 9 => 05/20 (max value)', () => {\n            cy.get('@input')\n                .type('032')\n                .should('have.value', '03/2')\n                .should('have.prop', 'selectionStart', '03/2'.length)\n                .should('have.prop', 'selectionEnd', '03/2'.length)\n                .type('9')\n                .should('have.value', '05/20')\n                .should('have.prop', 'selectionStart', '05/20'.length)\n                .should('have.prop', 'selectionEnd', '05/20'.length);\n        });\n    });\n\n    describe('Min date, shortened year', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?min=2020-05-05&mode=mm%2Fyy&separator=%2F`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('Input more than min value', () => {\n            cy.get('@input')\n                .type('0625')\n                .should('have.value', '06/25')\n                .should('have.prop', 'selectionStart', '06/25'.length)\n                .should('have.prop', 'selectionEnd', '06/25'.length);\n        });\n\n        it('03/1| => type 9 => 05/20 (min value)', () => {\n            cy.get('@input')\n                .type('031')\n                .should('have.value', '03/1')\n                .should('have.prop', 'selectionStart', '03/1'.length)\n                .should('have.prop', 'selectionEnd', '03/1'.length)\n                .type('9')\n                .should('have.value', '05/20')\n                .should('have.prop', 'selectionStart', '05/20'.length)\n                .should('have.prop', 'selectionEnd', '05/20'.length);\n        });\n    });\n\n    describe('Min date, yyyy', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?min=2020-05-05&mode=yyyy`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('Input less than min value', () => {\n            cy.get('@input')\n                .type('2019')\n                .should('have.value', '2020')\n                .should('have.prop', 'selectionStart', '2020'.length)\n                .should('have.prop', 'selectionEnd', '2020'.length);\n        });\n\n        it('Input more than min value', () => {\n            cy.get('@input')\n                .type('2021')\n                .should('have.value', '2021')\n                .should('have.prop', 'selectionStart', '2021'.length)\n                .should('have.prop', 'selectionEnd', '2021'.length);\n        });\n    });\n\n    describe('Max date, yyyy', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?max=2020-05-05&mode=yyyy`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('Input less than max value', () => {\n            cy.get('@input')\n                .type('2019')\n                .should('have.value', '2019')\n                .should('have.prop', 'selectionStart', '2019'.length)\n                .should('have.prop', 'selectionEnd', '2019'.length);\n        });\n\n        it('Input more than max value', () => {\n            cy.get('@input')\n                .type('2021')\n                .should('have.value', '2020')\n                .should('have.prop', 'selectionStart', '2020'.length)\n                .should('have.prop', 'selectionEnd', '2020'.length);\n        });\n    });\n\n    describe('Min date, mm/yyyy', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?min=2020-05-05&mode=mm%2Fyyyy&separator=%2F`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('Input less than min value', () => {\n            cy.get('@input')\n                .type('042019')\n                .should('have.value', '05/2020')\n                .should('have.prop', 'selectionStart', '05/2020'.length)\n                .should('have.prop', 'selectionEnd', '05/2020'.length);\n        });\n\n        it('Input more than min value', () => {\n            cy.get('@input')\n                .type('062020')\n                .should('have.value', '06/2020')\n                .should('have.prop', 'selectionStart', '06/2020'.length)\n                .should('have.prop', 'selectionEnd', '06/2020'.length);\n        });\n    });\n\n    describe('Max date, mm/yyyy', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?max=2020-05-05&mode=mm%2Fyyyy&separator=%2F`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('Input less than max value', () => {\n            cy.get('@input')\n                .type('042019')\n                .should('have.value', '04/2019')\n                .should('have.prop', 'selectionStart', '04/2019'.length)\n                .should('have.prop', 'selectionEnd', '04/2019'.length);\n        });\n\n        it('Input more than max value', () => {\n            cy.get('@input')\n                .type('062020')\n                .should('have.value', '05/2020')\n                .should('have.prop', 'selectionStart', '05/2020'.length)\n                .should('have.prop', 'selectionEnd', '05/2020'.length);\n        });\n    });\n\n    describe('Min date yyyy/mm', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?min=2020-05-05&mode=yyyy%2Fmm&separator=%2F`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('Input less than min value', () => {\n            cy.get('@input')\n                .type('201904')\n                .should('have.value', '2020/05')\n                .should('have.prop', 'selectionStart', '2020/05'.length)\n                .should('have.prop', 'selectionEnd', '2020/05'.length);\n        });\n\n        it('Input more than min value', () => {\n            cy.get('@input')\n                .type('202106')\n                .should('have.value', '2021/06')\n                .should('have.prop', 'selectionStart', '2021/06'.length)\n                .should('have.prop', 'selectionEnd', '2021/06'.length);\n        });\n    });\n\n    describe('Max date yyyy/mm', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?max=2020-05-05&mode=yyyy%2Fmm&separator=%2F`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('Input less than max value', () => {\n            cy.get('@input')\n                .type('201904')\n                .should('have.value', '2019/04')\n                .should('have.prop', 'selectionStart', '2019/04'.length)\n                .should('have.prop', 'selectionEnd', '2019/04'.length);\n        });\n\n        it('Input more than max value', () => {\n            cy.get('@input')\n                .type('202106')\n                .should('have.value', '2020/05')\n                .should('have.prop', 'selectionStart', '2020/05'.length)\n                .should('have.prop', 'selectionEnd', '2020/05'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date/date-mode.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Date', () => {\n    describe('Mode', () => {\n        describe('yyyy/mm/dd', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Date}/API?mode=yyyy%2Fmm%2Fdd`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it(' \"yyyy/mm/dd\" => 2019.12.31', () => {\n                cy.get('@input')\n                    .type('20193')\n                    .should('have.value', '2019.03')\n                    .should('have.prop', 'selectionStart', '2019.03'.length)\n                    .should('have.prop', 'selectionEnd', '2019.03'.length)\n                    .type('22')\n                    .should('have.value', '2019.03.22')\n                    .should('have.prop', 'selectionStart', '2019.03.22'.length)\n                    .should('have.prop', 'selectionEnd', '2019.03.22'.length);\n            });\n        });\n\n        describe('mm/dd/yyyy', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Date}/API?mode=mm%2Fdd%2Fyyyy`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it(' \"mm/dd/yyyy\" => 03.12.2020', () => {\n                cy.get('@input')\n                    .type('312')\n                    .should('have.value', '03.12')\n                    .should('have.prop', 'selectionStart', '03.12'.length)\n                    .should('have.prop', 'selectionEnd', '03.12'.length)\n                    .type('2022')\n                    .should('have.value', '03.12.2022')\n                    .should('have.prop', 'selectionStart', '03.12.2022'.length)\n                    .should('have.prop', 'selectionEnd', '03.12.2022'.length);\n            });\n        });\n\n        describe('mm/yy', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Date}/API?mode=mm%2Fyy`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it('\"mm/yy\" => 03.20', () => {\n                cy.get('@input')\n                    .type('3')\n                    .should('have.value', '03')\n                    .should('have.prop', 'selectionStart', '03'.length)\n                    .should('have.prop', 'selectionEnd', '03'.length)\n                    .type('20')\n                    .should('have.value', '03.20')\n                    .should('have.prop', 'selectionStart', '03.20'.length)\n                    .should('have.prop', 'selectionEnd', '03.20'.length);\n            });\n\n            it('\"mm/yy\" => 03.22', () => {\n                cy.get('@input')\n                    .type('0')\n                    .should('have.value', '0')\n                    .should('have.prop', 'selectionStart', '0'.length)\n                    .should('have.prop', 'selectionEnd', '0'.length)\n                    .type('322')\n                    .should('have.value', '03.22')\n                    .should('have.prop', 'selectionStart', '03.22'.length)\n                    .should('have.prop', 'selectionEnd', '03.22'.length);\n            });\n\n            it('\"mm/yy\" => 12.04', () => {\n                cy.get('@input')\n                    .type('1.2.')\n                    .should('have.value', '12.')\n                    .type('04')\n                    .should('have.value', '12.04');\n            });\n        });\n\n        describe('mm/yyyy', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Date}/API?mode=mm/yyyy`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it('\"mm/yyyy\" => 02.2025', () => {\n                cy.get('@input')\n                    .type('2')\n                    .should('have.value', '02')\n                    .should('have.prop', 'selectionStart', '02'.length)\n                    .should('have.prop', 'selectionEnd', '02'.length)\n                    .type('2025')\n                    .should('have.value', '02.2025')\n                    .should('have.prop', 'selectionStart', '02.2025'.length)\n                    .should('have.prop', 'selectionEnd', '02.2025'.length);\n            });\n\n            it('\"mm/yyyy\" => 11.1999', () => {\n                cy.get('@input')\n                    .type('1')\n                    .should('have.value', '1')\n                    .should('have.prop', 'selectionStart', '1'.length)\n                    .should('have.prop', 'selectionEnd', '1'.length)\n                    .type('11999')\n                    .should('have.value', '11.1999')\n                    .should('have.prop', 'selectionStart', '11.1999'.length)\n                    .should('have.prop', 'selectionEnd', '11.1999'.length);\n            });\n\n            it('\"mm/yyyy\" => 05.2004', () => {\n                cy.get('@input')\n                    .type('0.')\n                    .should('have.value', '0')\n                    .type('5')\n                    .should('have.value', '05')\n                    .type('.2004')\n                    .should('have.value', '05.2004');\n            });\n        });\n\n        describe('yyyy/mm', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Date}/API?mode=yyyy/mm`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it('\"yyyy/mm\" => 2025.02', () => {\n                cy.get('@input')\n                    .type('2025')\n                    .should('have.value', '2025')\n                    .should('have.prop', 'selectionStart', '2025'.length)\n                    .should('have.prop', 'selectionEnd', '2025'.length)\n                    .type('2')\n                    .should('have.value', '2025.02')\n                    .should('have.prop', 'selectionStart', '2025.02'.length)\n                    .should('have.prop', 'selectionEnd', '2025.02'.length);\n            });\n\n            it('\"yyyy/mm\" => 1999.11', () => {\n                cy.get('@input')\n                    .type('19991')\n                    .should('have.value', '1999.1')\n                    .should('have.prop', 'selectionStart', '1999.1'.length)\n                    .should('have.prop', 'selectionEnd', '1999.1'.length)\n                    .type('1')\n                    .should('have.value', '1999.11')\n                    .should('have.prop', 'selectionStart', '1999.11'.length)\n                    .should('have.prop', 'selectionEnd', '1999.11'.length);\n            });\n\n            it('\"yyyy/mm\" => 2004.05', () => {\n                cy.get('@input')\n                    .type('20.0.4')\n                    .should('have.value', '2004')\n                    .type('.')\n                    .should('have.value', '2004.')\n                    .type('05')\n                    .should('have.value', '2004.05');\n            });\n        });\n\n        describe('yyyy', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Date}/API?mode=yyyy`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it('\"yyyy\" => 2025', () => {\n                cy.get('@input')\n                    .type('2')\n                    .should('have.value', '2')\n                    .should('have.prop', 'selectionStart', '2'.length)\n                    .should('have.prop', 'selectionEnd', '2'.length)\n                    .type('025')\n                    .should('have.value', '2025')\n                    .should('have.prop', 'selectionStart', '2025'.length)\n                    .should('have.prop', 'selectionEnd', '2025'.length);\n            });\n        });\n\n        describe('dd/mm', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Date}/API?mode=dd%2Fmm`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it('\"dd/mm\" => 14.07', () => {\n                cy.get('@input')\n                    .type('1407')\n                    .should('have.value', '14.07')\n                    .should('have.prop', 'selectionStart', '14.07'.length)\n                    .should('have.prop', 'selectionEnd', '14.07'.length);\n            });\n\n            it('\"dd/mm\" => 01.01', () => {\n                cy.get('@input')\n                    .type('0101')\n                    .should('have.value', '01.01')\n                    .should('have.prop', 'selectionStart', '01.11'.length)\n                    .should('have.prop', 'selectionEnd', '01.11'.length);\n            });\n\n            it('\"dd/mm\" => 05.11', () => {\n                cy.get('@input')\n                    .type('511')\n                    .should('have.value', '05.11')\n                    .should('have.prop', 'selectionStart', '05.11'.length)\n                    .should('have.prop', 'selectionEnd', '05.11'.length);\n            });\n\n            it('\"dd/mm\" => 05.05', () => {\n                cy.get('@input')\n                    .type('55')\n                    .should('have.value', '05.05')\n                    .should('have.prop', 'selectionStart', '05.05'.length)\n                    .should('have.prop', 'selectionEnd', '05.05'.length);\n            });\n\n            it('dd/mm\" => 01.05', () => {\n                cy.get('@input')\n                    .type('3104')\n                    .should('have.value', '01.05')\n                    .should('have.prop', 'selectionStart', '01.05'.length)\n                    .should('have.prop', 'selectionEnd', '01.05'.length);\n            });\n        });\n\n        describe('mm/dd', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Date}/API?mode=mm%2Fdd`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it('\"mm/dd\" => 02.29', () => {\n                cy.get('@input')\n                    .type('0229')\n                    .should('have.value', '02.29')\n                    .should('have.prop', 'selectionStart', '02.29'.length)\n                    .should('have.prop', 'selectionEnd', '02.29'.length);\n            });\n\n            it('\"mm/dd\" => 01.01', () => {\n                cy.get('@input')\n                    .type('0101')\n                    .should('have.value', '01.01')\n                    .should('have.prop', 'selectionStart', '01.01'.length)\n                    .should('have.prop', 'selectionEnd', '01.01'.length);\n            });\n\n            it('\"mm/dd\" => 09.12', () => {\n                cy.get('@input')\n                    .type('912')\n                    .should('have.value', '09.12')\n                    .should('have.prop', 'selectionStart', '09.12'.length)\n                    .should('have.prop', 'selectionEnd', '09.12'.length);\n            });\n\n            it('\"mm/dd\" => 09.09', () => {\n                cy.get('@input')\n                    .type('99')\n                    .should('have.value', '09.09')\n                    .should('have.prop', 'selectionStart', '09.09'.length)\n                    .should('have.prop', 'selectionEnd', '09.09'.length);\n            });\n\n            it('dd/mm\" => 05.01', () => {\n                cy.get('@input')\n                    .type('431')\n                    .should('have.value', '05.01')\n                    .should('have.prop', 'selectionStart', '05.01'.length)\n                    .should('have.prop', 'selectionEnd', '05.01'.length);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date/date-segments-zero-padding.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\n\ndescribe('Date | Date segments zero padding (pads digits with zero if date segment exceeds its max possible value)', () => {\n    describe('[mode]=\"dd.mm.yyyy\"', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?mode=dd%2Fmm%2Fyyyy&separator=.`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        describe('pads the 1st digit > 3 with zero for days', () => {\n            [0, 1, 2, 3].forEach((digit) => {\n                it(`Type ${digit} => ${digit}`, () => {\n                    cy.get('@input')\n                        .type(`${digit}`)\n                        .should('have.value', `${digit}`)\n                        .should('have.prop', 'selectionStart', 1)\n                        .should('have.prop', 'selectionEnd', 1);\n                });\n            });\n\n            [4, 5, 6, 7, 8, 9].forEach((digit) => {\n                it(`Type ${digit} => 0${digit}`, () => {\n                    cy.get('@input')\n                        .type(`${digit}`)\n                        .should('have.value', `0${digit}`)\n                        .should('have.prop', 'selectionStart', `0${digit}`.length)\n                        .should('have.prop', 'selectionEnd', `0${digit}`.length);\n                });\n            });\n\n            it(\n                '|11|.11.2011 => Type 7 => 07.|11.2011',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('11.11.2011')\n                        .type('{moveToStart}')\n                        .realPress(['Shift', 'ArrowRight', 'ArrowRight']);\n\n                    cy.get('@input')\n                        .should('have.prop', 'selectionStart', 0)\n                        .should('have.prop', 'selectionEnd', '07'.length)\n                        .type('7')\n                        .should('have.value', '07.11.2011')\n                        .should('have.prop', 'selectionStart', '07.'.length)\n                        .should('have.prop', 'selectionEnd', '07.'.length);\n                },\n            );\n        });\n\n        describe('pads the 1st digit > 1 with zero for months', () => {\n            [0, 1].forEach((digit) => {\n                it(`Type 01.${digit} => 01.${digit}`, () => {\n                    cy.get('@input')\n                        .type(`01${digit}`)\n                        .should('have.value', `01.${digit}`)\n                        .should('have.prop', 'selectionStart', `01.${digit}`.length)\n                        .should('have.prop', 'selectionEnd', `01.${digit}`.length);\n                });\n            });\n\n            [2, 3, 4, 5, 6, 7, 8, 9].forEach((digit) => {\n                it(`Type 01.${digit} => 01.0${digit}`, () => {\n                    cy.get('@input')\n                        .type(`01${digit}`)\n                        .should('have.value', `01.0${digit}`)\n                        .should('have.prop', 'selectionStart', `01.0${digit}`.length)\n                        .should('have.prop', 'selectionEnd', `01.0${digit}`.length);\n                });\n            });\n\n            it(\n                '11.|11|.2011 => Type 2 => 11.02.|2011',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('11.11.2011')\n                        .type('{moveToEnd}')\n                        .type('{leftArrow}'.repeat('.2011'.length))\n                        .realPress(['Shift', 'ArrowLeft', 'ArrowLeft']);\n\n                    cy.get('@input')\n                        .should('have.prop', 'selectionStart', '11.'.length)\n                        .should('have.prop', 'selectionEnd', '11.11'.length)\n                        .type('2')\n                        .should('have.value', '11.02.2011')\n                        .should('have.prop', 'selectionStart', '01.02.'.length)\n                        .should('have.prop', 'selectionEnd', '01.02.'.length);\n                },\n            );\n        });\n\n        describe('if users enters two digits and its combination exceeds the first (and only first!) non-year date segment - pad the first digit with zero', () => {\n            it('Empty input => Type 35 => 03.05|', () => {\n                cy.get('@input')\n                    .type('35')\n                    .should('have.value', '03.05')\n                    .should('have.prop', 'selectionStart', '03.05'.length)\n                    .should('have.prop', 'selectionEnd', '03.05'.length);\n            });\n\n            it('|19.01.2025 => Type 3 => 3|0.01.2025', () => {\n                cy.get('@input')\n                    .type('19012025')\n                    .should('have.value', '19.01.2025')\n                    .type('{moveToStart}')\n                    .should('have.prop', 'selectionStart', 0)\n                    .should('have.prop', 'selectionEnd', 0)\n                    .type('3')\n                    .should('have.value', '30.01.2025')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            it('31.1| => Type 3 => 31.1|', () => {\n                cy.get('@input')\n                    .type('311')\n                    .should('have.value', '31.1')\n                    .type('3')\n                    .should('have.value', '31.1')\n                    .should('have.prop', 'selectionStart', '31.1'.length)\n                    .should('have.prop', 'selectionEnd', '31.1'.length);\n            });\n        });\n    });\n\n    describe('[mode]=\"mm/dd/yyyy\"', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Date}/API?mode=mm%2Fdd%2Fyyyy&separator=/`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        describe('handles month value exceeding maximum', () => {\n            it('Type 13 => 01/3', () => {\n                cy.get('@input')\n                    .type('13')\n                    .should('have.value', '01/3')\n                    .should('have.prop', 'selectionStart', '01/3'.length)\n                    .should('have.prop', 'selectionEnd', '01/3'.length);\n            });\n        });\n    });\n\n    describe('[mode]=\"yyyy/mm/dd\"', () => {\n        beforeEach(() => {\n            cy.visit(\n                `/${DemoPath.Date}/API?mode=${encodeURIComponent('yyyy/mm/dd')}&separator=${encodeURIComponent('/')}`,\n            );\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        describe('if users enters two digits and its combination exceeds the first (and only first!) non-year date segment - pad the first digit with zero', () => {\n            it('2025 => Type 35 => 2025/03/05|', () => {\n                cy.get('@input')\n                    .type('202535')\n                    .should('have.value', '2025/03/05')\n                    .should('have.prop', 'selectionStart', '2025/03/05'.length)\n                    .should('have.prop', 'selectionEnd', '2025/03/05'.length);\n            });\n\n            it('2025/|09/30 => Type 1 => 2025/1|0/30', () => {\n                cy.get('@input')\n                    .type('2025930')\n                    .should('have.value', '2025/09/30')\n                    .type('{leftArrow}'.repeat('09/30'.length))\n                    .should('have.prop', 'selectionStart', '2025/'.length)\n                    .should('have.prop', 'selectionEnd', '2025/'.length)\n                    .type('1')\n                    .should('have.value', '2025/10/30')\n                    .should('have.prop', 'selectionStart', '2025/1'.length)\n                    .should('have.prop', 'selectionEnd', '2025/1'.length);\n            });\n\n            it('2025.01.3| => Type 5 => 2025.01.3|', () => {\n                cy.get('@input')\n                    .type('2025013')\n                    .should('have.value', '2025/01/3')\n                    .type('5')\n                    .should('have.value', '2025/01/3')\n                    .should('have.prop', 'selectionStart', '2025/01/3'.length)\n                    .should('have.prop', 'selectionEnd', '2025/01/3'.length);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date/date-separator.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Date', () => {\n    describe('Separator', () => {\n        describe('/', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Date}/API?separator=/`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it(' \"/\" => 31/12/2019', () => {\n                cy.get('@input')\n                    .type('3112')\n                    .should('have.value', '31/12')\n                    .should('have.prop', 'selectionStart', '31/12'.length)\n                    .should('have.prop', 'selectionEnd', '31/12'.length)\n                    .type('2019')\n                    .should('have.value', '31/12/2019')\n                    .should('have.prop', 'selectionStart', '31/12/2019'.length)\n                    .should('have.prop', 'selectionEnd', '31/12/2019'.length);\n            });\n\n            it('input \"/\" separator\"', () => {\n                cy.get('@input')\n                    .type('31/')\n                    .should('have.value', '31/')\n                    .should('have.prop', 'selectionStart', '31/'.length)\n                    .should('have.prop', 'selectionEnd', '31/'.length);\n            });\n\n            it('input separator \"/\" is not allowed', () => {\n                cy.get('@input')\n                    .type('3/')\n                    .should('have.value', '3')\n                    .should('have.prop', 'selectionStart', '3'.length)\n                    .should('have.prop', 'selectionEnd', '3'.length);\n            });\n\n            it('Input separator \"-\" in separator mode \"/\" is not allowed', () => {\n                cy.get('@input')\n                    .type('31-')\n                    .should('have.value', '31')\n                    .should('have.prop', 'selectionStart', '31'.length)\n                    .should('have.prop', 'selectionEnd', '31'.length);\n            });\n        });\n\n        describe('-', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Date}/API?separator=-`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it(' \"-\" => 31-12-2019', () => {\n                cy.get('@input')\n                    .type('3112')\n                    .should('have.value', '31-12')\n                    .should('have.prop', 'selectionStart', '31-12'.length)\n                    .should('have.prop', 'selectionEnd', '31-12'.length)\n                    .type('2019')\n                    .should('have.value', '31-12-2019')\n                    .should('have.prop', 'selectionStart', '31-12-2019'.length)\n                    .should('have.prop', 'selectionEnd', '31-12-2019'.length);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-range/date-range-basic.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\nimport {repeatKey} from '../../utils';\n\ndescribe('DateRange | Basic', () => {\n    beforeEach(() => {\n        cy.visit(`/${DemoPath.DateRange}/API?mode=dd%2Fmm%2Fyyyy`);\n        cy.get('#demo-content input').should('be.visible').first().focus().as('input');\n    });\n\n    describe('basic typing', () => {\n        const tests = [\n            // [Typed value, Masked value]\n            ['1', '1'],\n            ['18', '18'],\n            ['181', '18.1'],\n            ['1811', '18.11'],\n            ['18112', '18.11.2'],\n            ['18112016', '18.11.2016'],\n            ['181120162', '18.11.2016 – 2'],\n            ['1811201624', '18.11.2016 – 24'],\n            ['18112016240', '18.11.2016 – 24.0'],\n            ['181120162403', '18.11.2016 – 24.03'],\n            ['18112016240320', '18.11.2016 – 24.03.20'],\n            ['1811201624032020', '18.11.2016 – 24.03.2020'],\n        ] as const;\n\n        tests.forEach(([typedValue, maskedValue]) => {\n            it(`Type \"${typedValue}\" => \"${maskedValue}\"`, () => {\n                cy.get('@input')\n                    .type(typedValue)\n                    .should('have.value', maskedValue)\n                    .should('have.prop', 'selectionStart', maskedValue.length)\n                    .should('have.prop', 'selectionEnd', maskedValue.length);\n            });\n        });\n    });\n\n    describe('invalid dates cases', () => {\n        it('Empty input => Type \"9\" => \"09|\"', () => {\n            cy.get('@input')\n                .type('9')\n                .should('have.value', '09')\n                .should('have.prop', 'selectionStart', '09'.length)\n                .should('have.prop', 'selectionEnd', '09'.length);\n        });\n\n        it('12.12.2020| => Type \"4\" => 12.12.2020 - 04|', () => {\n            cy.get('@input')\n                .type('121220204')\n                .should('have.value', '12.12.2020 – 04')\n                .should('have.prop', 'selectionStart', '12.12.2020 – 04'.length)\n                .should('have.prop', 'selectionEnd', '12.12.2020 – 04'.length);\n        });\n\n        it('27| => type 2 => 27.02|', () => {\n            cy.get('@input')\n                .type('27')\n                .should('have.value', '27')\n                .should('have.prop', 'selectionStart', '27'.length)\n                .should('have.prop', 'selectionEnd', '27'.length)\n                .type('2')\n                .should('have.value', '27.02')\n                .should('have.prop', 'selectionStart', '27.02'.length)\n                .should('have.prop', 'selectionEnd', '27.02'.length);\n        });\n\n        it('12.12.2020 - 27| => type 3 => 12.12.2020 - 27.03|', () => {\n            cy.get('@input')\n                .type('1212202027')\n                .should('have.value', '12.12.2020 – 27')\n                .should('have.prop', 'selectionStart', '12.12.2020 – 27'.length)\n                .should('have.prop', 'selectionEnd', '12.12.2020 – 27'.length)\n                .type('3')\n                .should('have.value', '12.12.2020 – 27.03')\n                .should('have.prop', 'selectionStart', '12.12.2020 – 27.03'.length)\n                .should('have.prop', 'selectionEnd', '12.12.2020 – 27.03'.length);\n        });\n\n        it('3| => Type 7 => 03.07|', () => {\n            cy.get('@input')\n                .type('3')\n                .should('have.value', '3')\n                .should('have.prop', 'selectionStart', '3'.length)\n                .should('have.prop', 'selectionEnd', '3'.length)\n                .type('7')\n                .should('have.value', '03.07')\n                .should('have.prop', 'selectionStart', '03.07'.length)\n                .should('have.prop', 'selectionEnd', '03.07'.length);\n        });\n\n        it('12.12.2020 - 3| => Type 7 => 12.12.2020 - 03.07|', () => {\n            cy.get('@input')\n                .type('121220203')\n                .should('have.value', '12.12.2020 – 3')\n                .should('have.prop', 'selectionStart', '12.12.2020 – 3'.length)\n                .should('have.prop', 'selectionEnd', '12.12.2020 – 3'.length)\n                .type('7')\n                .should('have.value', '12.12.2020 – 03.07')\n                .should('have.prop', 'selectionStart', '12.12.2020 – 03.07'.length)\n                .should('have.prop', 'selectionEnd', '12.12.2020 – 03.07'.length);\n        });\n    });\n\n    describe('basic erasing (value = \"20.01.1990 - 31.12.2022\" & caret is placed after the last value)', () => {\n        beforeEach(() => {\n            cy.get('@input').type('2001199031122022');\n        });\n\n        const tests = [\n            // [How many times \"Backspace\"-key was pressed, caretPosition, Masked value]\n            [1, '20.01.1990 – 31.12.202'.length, '20.01.1990 – 31.12.202'],\n            [4, '20.01.1990 – 31.12'.length, '20.01.1990 – 31.12'],\n            [5, '20.01.1990 – 31.1'.length, '20.01.1990 – 31.1'],\n            [6, '20.01.1990 – 31'.length, '20.01.1990 – 31'],\n            [8, '20.01.1990'.length, '20.01.1990'],\n            [12, '20.01'.length, '20.01'],\n            [13, '20.0'.length, '20.0'],\n            [14, '20'.length, '20'],\n        ] as const;\n\n        tests.forEach(([n, caretIndex, maskedValue]) => {\n            it(`Backspace x${n} => \"${maskedValue}\"`, () => {\n                cy.get('@input')\n                    .type('{backspace}'.repeat(n))\n                    .should('have.value', maskedValue)\n                    .should('have.prop', 'selectionStart', caretIndex)\n                    .should('have.prop', 'selectionEnd', caretIndex);\n            });\n        });\n\n        it('Delete => no value change && no caret index change', () => {\n            cy.get('@input')\n                .type('{del}')\n                .should('have.value', '20.01.1990 – 31.12.2022')\n                .should('have.prop', 'selectionStart', '20.01.1990 – 31.12.2022'.length)\n                .should('have.prop', 'selectionEnd', '20.01.1990 – 31.12.2022'.length);\n        });\n\n        it('Type `deleteWordBackward` of `InputEvent` works', () => {\n            cy.get('@input')\n                .type('{ctrl+backspace}')\n                .should('have.value', '20.01.1990')\n                .should('have.prop', 'selectionStart', '20.01.1990'.length)\n                .should('have.prop', 'selectionEnd', '20.01.1990'.length)\n                .type('{ctrl+backspace}')\n                .should('have.value', '')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('Type `deleteSoftLineBackward` of `InputEvent` works', () => {\n            cy.get('@input')\n                .trigger('beforeinput', {inputType: 'deleteSoftLineBackward'})\n                .trigger('input', {inputType: 'deleteSoftLineBackward'})\n                .should('have.value', '')\n                .should('have.prop', 'selectionStart', ''.length)\n                .should('have.prop', 'selectionEnd', ''.length);\n        });\n\n        it('Type `deleteSoftLineForward` of `InputEvent` works', () => {\n            cy.get('@input')\n                .type('{moveToStart}')\n                .trigger('beforeinput', {inputType: 'deleteSoftLineForward'})\n                .trigger('input', {inputType: 'deleteSoftLineForward'})\n                .should('have.value', '')\n                .should('have.prop', 'selectionStart', ''.length)\n                .should('have.prop', 'selectionEnd', ''.length);\n        });\n\n        it('Type `deleteSoftLineBackward` in the middle works', () => {\n            cy.get('@input')\n                .type('{leftArrow}'.repeat(' 31.12.2022'.length))\n                .trigger('beforeinput', {inputType: 'deleteSoftLineBackward'})\n                .trigger('input', {inputType: 'deleteSoftLineBackward'})\n                .should('have.value', '01.01.0001 – 31.12.2022')\n                .should('have.prop', 'selectionStart', ''.length)\n                .should('have.prop', 'selectionEnd', ''.length);\n        });\n\n        it('Type `deleteSoftLineForward` in the middle works', () => {\n            cy.get('@input')\n                .type('{leftArrow}'.repeat(' 31.12.2022'.length))\n                .trigger('beforeinput', {inputType: 'deleteSoftLineForward'})\n                .trigger('input', {inputType: 'deleteSoftLineForward'})\n                .should('have.value', '20.01.1990')\n                .should('have.prop', 'selectionStart', '20.01.1990'.length)\n                .should('have.prop', 'selectionEnd', '20.01.1990'.length);\n        });\n    });\n\n    describe('Editing somewhere in the middle of a value (NOT the last character)', () => {\n        it('25.02.19|99 - 17.05.2000 => Backspace => 25.02.1|099 - 17.05.2000 => Type \"8\" => 25.02.18|99 - 17.05.2000', () => {\n            cy.get('@input')\n                .type('25.02.1999-17.05.2000')\n                .should('have.value', '25.02.1999 – 17.05.2000')\n                .type('{leftArrow}'.repeat('99 – 17.05.2000'.length))\n                .should('have.prop', 'selectionStart', '25.02.19'.length)\n                .should('have.prop', 'selectionEnd', '25.02.19'.length)\n                .type('{backspace}')\n                .should('have.value', '25.02.1099 – 17.05.2000')\n                .should('have.prop', 'selectionStart', '25.02.1'.length)\n                .should('have.prop', 'selectionEnd', '25.02.1'.length)\n                .type('8')\n                .should('have.value', '25.02.1899 – 17.05.2000')\n                .should('have.prop', 'selectionStart', '25.02.18'.length)\n                .should('have.prop', 'selectionEnd', '25.02.18'.length);\n        });\n\n        it('13.06.1736 - 14.09|.1821 => Backspace => 13.06.1736 - 14.0|1.1821 => Type \"3\" => 13.06.1736 - 14.03|.1821', () => {\n            cy.get('@input')\n                .type('13.06.1736-14.09.1821')\n                .should('have.value', '13.06.1736 – 14.09.1821')\n                .type('{leftArrow}'.repeat('.1821'.length))\n                .should('have.prop', 'selectionStart', '13.06.1736 - 14.09'.length)\n                .should('have.prop', 'selectionEnd', '13.06.1736 - 14.09'.length)\n                .type('{backspace}')\n                .should('have.value', '13.06.1736 – 14.01.1821')\n                .should('have.prop', 'selectionStart', '13.06.1736 - 14.0'.length)\n                .should('have.prop', 'selectionEnd', '13.06.1736 - 14.0'.length)\n                .type('3')\n                .should('have.value', '13.06.1736 – 14.03.1821')\n                .should('have.prop', 'selectionStart', '13.06.1736 - 14.03.'.length)\n                .should('have.prop', 'selectionEnd', '13.06.1736 - 14.03.'.length);\n        });\n\n        it('12|.01.2008 - 27.01.2020 => Backspace => 1|0.01.2008 - 27.01.2020 => Type \"1\" => 11|.01.2008 - 27.01.2020', () => {\n            cy.get('@input')\n                .type('12012008-27012020')\n                .should('have.value', '12.01.2008 – 27.01.2020')\n                .type('{leftArrow}'.repeat('.01.2008 - 27.01.2020'.length))\n                .should('have.prop', 'selectionStart', '12'.length)\n                .should('have.prop', 'selectionEnd', '12'.length)\n                .type('{backspace}')\n                .should('have.value', '10.01.2008 – 27.01.2020')\n                .should('have.prop', 'selectionStart', '1'.length)\n                .should('have.prop', 'selectionEnd', '1'.length)\n                .type('1')\n                .should('have.value', '11.01.2008 – 27.01.2020')\n                .should('have.prop', 'selectionStart', '11.'.length)\n                .should('have.prop', 'selectionEnd', '11.'.length);\n        });\n\n        it('12.01.2008 - 2|7.01.2020 => Backspace => 12.01.2008 - |07.01.2020 => Type \"1\" => 12.01.2008 - 1|7.01.2020', () => {\n            cy.get('@input')\n                .type('12012008-27012020')\n                .should('have.value', '12.01.2008 – 27.01.2020')\n                .type('{leftArrow}'.repeat('7.01.2020'.length))\n                .should('have.prop', 'selectionStart', '12.01.2008 - 2'.length)\n                .should('have.prop', 'selectionEnd', '12.01.2008 - 2'.length)\n                .type('{backspace}')\n                .should('have.value', '12.01.2008 – 07.01.2020')\n                .should('have.prop', 'selectionStart', '12.01.2008 - '.length)\n                .should('have.prop', 'selectionEnd', '12.01.2008 - '.length)\n                .type('1')\n                .should('have.value', '12.01.2008 – 17.01.2020')\n                .should('have.prop', 'selectionStart', '12.01.2008 - 1'.length)\n                .should('have.prop', 'selectionEnd', '12.01.2008 - 1'.length);\n        });\n\n        it('12.|12.2010 - 12.12.2020 => Type \"9\" => 12.09.|2010 - 12.12.2020', () => {\n            cy.get('@input')\n                .type('12122010-12122020')\n                .should('have.value', '12.12.2010 – 12.12.2020')\n                .type('{leftArrow}'.repeat('12.2010 - 12.12.2020'.length))\n                .should('have.prop', 'selectionStart', '12.'.length)\n                .should('have.prop', 'selectionEnd', '12.'.length)\n                .type('9')\n                .should('have.value', '12.09.2010 – 12.12.2020')\n                .should('have.prop', 'selectionStart', '12.09.'.length)\n                .should('have.prop', 'selectionEnd', '12.09.'.length);\n        });\n\n        it('12.12.2010 - 12.|12.2020 => Type \"9\" => 12.12.2010 - 12.09|.2020', () => {\n            cy.get('@input')\n                .type('12122010-12122020')\n                .should('have.value', '12.12.2010 – 12.12.2020')\n                .type('{leftArrow}'.repeat('12.2020'.length))\n                .should('have.prop', 'selectionStart', '12.12.2010 - 12.'.length)\n                .should('have.prop', 'selectionEnd', '12.12.2010 - 12.'.length)\n                .type('9')\n                .should('have.value', '12.12.2010 – 12.09.2020')\n                .should('have.prop', 'selectionStart', '12.12.2010 - 12.09.'.length)\n                .should('have.prop', 'selectionEnd', '12.12.2010 - 12.09.'.length);\n        });\n\n        it('|15.01.2012 - 15.01.2022 => Type \"3\" => 3|0.01.2012 - 15.01.2022', () => {\n            cy.get('@input')\n                .type('15012012-15012022')\n                .should('have.value', '15.01.2012 – 15.01.2022')\n                .type('{leftArrow}'.repeat('15.01.2012 - 15.01.2022'.length))\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0)\n                .type('3')\n                .should('have.value', '30.01.2012 – 15.01.2022')\n                .should('have.prop', 'selectionStart', '3'.length)\n                .should('have.prop', 'selectionEnd', '3'.length);\n        });\n\n        it('15.01.2012 - |15.01.2022 => Type \"3\" => 15.01.2012 - 3|0.01.2022', () => {\n            cy.get('@input')\n                .type('15012012-15012022')\n                .should('have.value', '15.01.2012 – 15.01.2022')\n                .type('{leftArrow}'.repeat('15.01.2022'.length))\n                .should('have.prop', 'selectionStart', '15.01.2012 - '.length)\n                .should('have.prop', 'selectionEnd', '15.01.2012 - '.length)\n                .type('3')\n                .should('have.value', '15.01.2012 – 30.01.2022')\n                .should('have.prop', 'selectionStart', '15.01.2012 - 3'.length)\n                .should('have.prop', 'selectionEnd', '15.01.2012 - 3'.length);\n        });\n    });\n\n    describe('Fixed values', () => {\n        it('Press Backspace after fixed value => no value change => move caret to the left', () => {\n            cy.get('@input')\n                .type('28032015-01042021')\n                .should('have.value', '28.03.2015 – 01.04.2021')\n                .type('{leftArrow}'.repeat(' 01.04.2021'.length))\n                .should('have.prop', 'selectionStart', '28.03.2015 -'.length)\n                .should('have.prop', 'selectionEnd', '28.03.2015 -'.length)\n                .type('{backspace}')\n                .should('have.value', '28.03.2015 – 01.04.2021')\n                .should('have.prop', 'selectionStart', '28.03.2015 '.length)\n                .should('have.prop', 'selectionEnd', '28.03.2015 '.length);\n        });\n\n        it('Press Delete after fixed value => no value change => move caret to the right', () => {\n            cy.get('@input')\n                .type('28032015-01042021')\n                .should('have.value', '28.03.2015 – 01.04.2021')\n                .type('{leftArrow}'.repeat('.04.2021'.length))\n                .should('have.prop', 'selectionStart', '28.03.2015 – 01'.length)\n                .should('have.prop', 'selectionEnd', '28.03.2015 – 01'.length)\n                .type('{del}')\n                .should('have.value', '28.03.2015 – 01.04.2021')\n                .should('have.prop', 'selectionStart', '28.03.2015 – 01.'.length)\n                .should('have.prop', 'selectionEnd', '28.03.2015 – 01.'.length);\n        });\n    });\n\n    describe('Text selection', () => {\n        describe('Select range and press Backspace / Delete', () => {\n            it(\n                '10.|12|.2005 - 16.12.2007 => Backspace => 10.|01.2005 - 16.12.2007',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('10122005-16122007')\n                        .should('have.value', '10.12.2005 – 16.12.2007')\n                        .type('{leftArrow}'.repeat('.2005 - 16.12.2007'.length))\n                        .realPress([\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '12'.length),\n                            'Backspace',\n                        ]);\n\n                    cy.get('@input')\n                        .should('have.value', '10.01.2005 – 16.12.2007')\n                        .should('have.prop', 'selectionStart', '10.'.length)\n                        .should('have.prop', 'selectionEnd', '10.'.length);\n                },\n            );\n\n            it(\n                '10.12.2005 - |16|.12.2007 => Backspace => 10.12.2005 - |01.12.2007',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('10122005-16122007')\n                        .should('have.value', '10.12.2005 – 16.12.2007')\n                        .type('{leftArrow}'.repeat('.12.2007'.length))\n                        .realPress([\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '16'.length),\n                            'Backspace',\n                        ]);\n\n                    cy.get('@input')\n                        .should('have.value', '10.12.2005 – 01.12.2007')\n                        .should('have.prop', 'selectionStart', '10.12.2005 - '.length)\n                        .should('have.prop', 'selectionEnd', '10.12.2005 - '.length);\n                },\n            );\n\n            it(\n                '1|1.1|1.2011 - 11.11.2025 => Delete => 10.0|1.2011 - 11.11.2025',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('11112011-11112025')\n                        .should('have.value', '11.11.2011 – 11.11.2025')\n                        .type('{leftArrow}'.repeat('1.2011 – 11.11.2025'.length))\n                        .realPress(['Shift', ...repeatKey('ArrowLeft', '1.1'.length)]);\n\n                    cy.get('@input')\n                        .type('{del}')\n                        .should('have.value', '10.01.2011 – 11.11.2025')\n                        .should('have.prop', 'selectionStart', '10.0'.length)\n                        .should('have.prop', 'selectionEnd', '10.0'.length);\n                },\n            );\n\n            it(\n                '11.11.2011 - 1|1.1|1.2025 => Delete => 11.11.2011 - 10.0|1.2025',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('11112011-11112025')\n                        .should('have.value', '11.11.2011 – 11.11.2025')\n                        .type('{leftArrow}'.repeat('1.2025'.length))\n                        .realPress(['Shift', ...repeatKey('ArrowLeft', '1.1'.length)]);\n\n                    cy.get('@input')\n                        .type('{del}')\n                        .should('have.value', '11.11.2011 – 10.01.2025')\n                        .should('have.prop', 'selectionStart', '11.11.2011 - 10.0'.length)\n                        .should('have.prop', 'selectionEnd', '11.11.2011 - 10.0'.length);\n                },\n            );\n        });\n\n        describe('Select range and press new digit', () => {\n            it(\n                '|12|.11.2022 (specifically do not completes value) => Press 3 => 3|0.11.2022',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('12112022')\n                        .type('{leftArrow}'.repeat('.11.2022'.length))\n                        .realPress(['Shift', ...repeatKey('ArrowLeft', '12'.length)]);\n\n                    cy.get('@input')\n                        .type('3')\n                        .should('have.value', '30.11.2022')\n                        .should('have.prop', 'selectionStart', '3'.length)\n                        .should('have.prop', 'selectionEnd', '3'.length);\n                },\n            );\n\n            it(\n                '01.01.2000 - |12|.11.2022 => Press 3 => 01.01.2000 - 3|0.11.2022',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('01012000-12112022')\n                        .type('{leftArrow}'.repeat('.11.2022'.length))\n                        .realPress(['Shift', ...repeatKey('ArrowLeft', '12'.length)]);\n\n                    cy.get('@input')\n                        .type('3')\n                        .should('have.value', '01.01.2000 – 30.11.2022')\n                        .should('have.prop', 'selectionStart', '01.01.2000 - 3'.length)\n                        .should('have.prop', 'selectionEnd', '01.01.2000 - 3'.length);\n                },\n            );\n        });\n    });\n\n    describe('The 2nd date is less than the 1st one', () => {\n        it('If caret is at the end, swap dates: 31.12.2023 – 01.01.202| => Type 0 => 01.01.2020 – 31.12.2023|', () => {\n            cy.get('@input')\n                .type('31.12.2023-01.01.202')\n                .should('have.value', '31.12.2023 – 01.01.202')\n                .should('have.prop', 'selectionStart', '31.12.2023 – 01.01.202'.length)\n                .should('have.prop', 'selectionEnd', '31.12.2023 – 01.01.202'.length)\n                .type('0')\n                .should('have.value', '01.01.2020 – 31.12.2023')\n                .should('have.prop', 'selectionStart', '01.01.2020 – 31.12.2023'.length)\n                .should('have.prop', 'selectionEnd', '01.01.2020 – 31.12.2023'.length);\n        });\n\n        it('If caret is NOT at the end, do NOT swap dates: 11.11.201|1 – 12.12.2012 => Type 5 => 11.11.2015 – |12.12.2012', () => {\n            cy.get('@input')\n                .type('11112011-12122012')\n                .should('have.value', '11.11.2011 – 12.12.2012')\n                .should('have.prop', 'selectionStart', '11.11.2011 – 12.12.2012'.length)\n                .should('have.prop', 'selectionEnd', '11.11.2011 – 12.12.2012'.length)\n                .type('{leftArrow}'.repeat('1 – 12.12.2012'.length))\n                .type('5')\n                .should('have.value', '11.11.2015 – 12.12.2012')\n                .should('have.prop', 'selectionStart', '11.11.2015 – '.length)\n                .should('have.prop', 'selectionEnd', '11.11.2015 – '.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-range/date-range-custom-range-separator.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('DateRange | CustomRangeSeparator', () => {\n    describe(' ~ ', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateRange}/API?rangeSeparator=~`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('14.12.1997~09.07.2015', () => {\n            cy.get('@input')\n                .type('14121997972015')\n                .should('have.value', '14.12.1997~09.07.2015');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-range/date-range-fullwidth-to-halfwidth.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('DateRange | Full width character parsing', () => {\n    beforeEach(() => {\n        cy.visit(`/${DemoPath.DateRange}/API?mode=yyyy%2Fmm%2Fdd&dateSeparator=%2F`);\n        cy.get('#demo-content input').should('be.visible').first().focus().as('input');\n    });\n\n    describe('basic typing', () => {\n        const tests = [\n            // [Typed value, Masked value]\n            ['２', '2'],\n            ['２０', '20'],\n            ['２０１', '201'],\n            ['２０１６', '2016'],\n            ['２０１６２', '2016/02'],\n            ['２０１６２２８', '2016/02/28'],\n            ['２０１６２２８２', '2016/02/28 – 2'],\n            ['２０１６２２８２０', '2016/02/28 – 20'],\n            ['２０１６２２８２０２０', '2016/02/28 – 2020'],\n            ['２０１６２２８２０２０４', '2016/02/28 – 2020/04'],\n            ['２０１６２２８２０２０４４', '2016/02/28 – 2020/04/04'],\n        ] as const;\n\n        tests.forEach(([typedValue, maskedValue]) => {\n            it(`Type \"${typedValue}\" => \"${maskedValue}\"`, () => {\n                cy.get('@input')\n                    .type(typedValue)\n                    .should('have.value', maskedValue)\n                    .should('have.prop', 'selectionStart', maskedValue.length)\n                    .should('have.prop', 'selectionEnd', maskedValue.length);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-range/date-range-min-max-length.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('DateRange | Min & Max Length', () => {\n    describe('[minLength]=\"3\"', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateRange}/API?minLength$=0`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('06.02.2023 - 06.02.202| => Type 3 => 06.02.2023 - 08.02.2023', () => {\n            cy.get('@input')\n                .type('06.02.2023 - 06.02.202')\n                .should('have.value', '06.02.2023 – 06.02.202')\n                .should('have.prop', 'selectionStart', '06.02.2023 – 06.02.202'.length)\n                .should('have.prop', 'selectionEnd', '06.02.2023 – 06.02.202'.length)\n                .type('3')\n                .should('have.value', '06.02.2023 – 08.02.2023')\n                .should('have.prop', 'selectionStart', '06.02.2023 – 08.02.2023'.length)\n                .should('have.prop', 'selectionEnd', '06.02.2023 – 08.02.2023'.length);\n        });\n\n        it('06.02.2023 - 07.02.202| => Type 3 => 06.02.2023 - 08.02.2023', () => {\n            cy.get('@input')\n                .type('06.02.2023 - 07.02.202')\n                .should('have.value', '06.02.2023 – 07.02.202')\n                .should('have.prop', 'selectionStart', '06.02.2023 – 07.02.202'.length)\n                .should('have.prop', 'selectionEnd', '06.02.2023 – 07.02.202'.length)\n                .type('3')\n                .should('have.value', '06.02.2023 – 08.02.2023')\n                .should('have.prop', 'selectionStart', '06.02.2023 – 08.02.2023'.length)\n                .should('have.prop', 'selectionEnd', '06.02.2023 – 08.02.2023'.length);\n        });\n\n        it('06.02.2023 - 08.02.2023 => valid input, no changes', () => {\n            cy.get('@input')\n                .type('06.02.2023 - 08.02.2023')\n                .should('have.value', '06.02.2023 – 08.02.2023')\n                .should('have.prop', 'selectionStart', '06.02.2023 – 08.02.2023'.length)\n                .should('have.prop', 'selectionEnd', '06.02.2023 – 08.02.2023'.length);\n        });\n\n        it('06.02.2023 - 09.02.2023 => valid input, no changes', () => {\n            cy.get('@input')\n                .type('06.02.2023 - 09.02.2023')\n                .should('have.value', '06.02.2023 – 09.02.2023')\n                .should('have.prop', 'selectionStart', '06.02.2023 – 09.02.2023'.length)\n                .should('have.prop', 'selectionEnd', '06.02.2023 – 09.02.2023'.length);\n        });\n    });\n\n    describe('[maxLength]=\"5\"', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateRange}/API?maxLength$=0`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('06.02.2023 - 09.02.2023 => valid input, no changes', () => {\n            cy.get('@input')\n                .type('06.02.2023 - 09.02.2023')\n                .should('have.value', '06.02.2023 – 09.02.2023')\n                .should('have.prop', 'selectionStart', '06.02.2023 – 09.02.2023'.length)\n                .should('have.prop', 'selectionEnd', '06.02.2023 – 09.02.2023'.length);\n        });\n\n        it('06.02.2023 - 10.02.2023 => valid input, no changes', () => {\n            cy.get('@input')\n                .type('06.02.2023-10.02.2023')\n                .should('have.value', '06.02.2023 – 10.02.2023')\n                .should('have.prop', 'selectionStart', '06.02.2023 – 10.02.2023'.length)\n                .should('have.prop', 'selectionEnd', '06.02.2023 – 10.02.2023'.length);\n        });\n\n        it('06.02.2023 - 11.02.202| => Type 3 => 06.02.2023 - 10.02.2023', () => {\n            cy.get('@input')\n                .type('06.02.2023 - 11.02.202')\n                .should('have.value', '06.02.2023 – 11.02.202')\n                .should('have.prop', 'selectionStart', '06.02.2023 – 11.02.202'.length)\n                .should('have.prop', 'selectionEnd', '06.02.2023 – 11.02.202'.length)\n                .type('3')\n                .should('have.value', '06.02.2023 – 10.02.2023')\n                .should('have.prop', 'selectionStart', '06.02.2023 – 10.02.2023'.length)\n                .should('have.prop', 'selectionEnd', '06.02.2023 – 10.02.2023'.length);\n        });\n\n        it('06.02.2023 - 10.03.202| => Type 3 => 06.02.2023 - 10.02.2023', () => {\n            cy.get('@input')\n                .type('06.02.2023 - 10.03.202')\n                .should('have.value', '06.02.2023 – 10.03.202')\n                .should('have.prop', 'selectionStart', '06.02.2023 – 10.03.202'.length)\n                .should('have.prop', 'selectionEnd', '06.02.2023 – 10.03.202'.length)\n                .type('3')\n                .should('have.value', '06.02.2023 – 10.02.2023')\n                .should('have.prop', 'selectionStart', '06.02.2023 – 10.02.2023'.length)\n                .should('have.prop', 'selectionEnd', '06.02.2023 – 10.02.2023'.length);\n        });\n    });\n\n    describe('[minLength]=\"3\" & [maxLength]=\"5\"', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateRange}/API?maxLength$=0&minLength$=0`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('01.01.2023 - 02.01.202| => Type 3 => 01.01.2023 - 03.01.2023', () => {\n            cy.get('@input')\n                .type('01.01.2023 - 02.01.202')\n                .should('have.value', '01.01.2023 – 02.01.202')\n                .should('have.prop', 'selectionStart', '01.01.2023 – 02.01.202'.length)\n                .should('have.prop', 'selectionEnd', '01.01.2023 – 02.01.202'.length)\n                .type('3')\n                .should('have.value', '01.01.2023 – 03.01.2023')\n                .should('have.prop', 'selectionStart', '01.01.2023 – 03.01.2023'.length)\n                .should('have.prop', 'selectionEnd', '01.01.2023 – 03.01.2023'.length);\n        });\n\n        it('01.01.2023 - 03.01.2023 => valid input, no changes', () => {\n            cy.get('@input')\n                .type('01.01.2023 - 03.01.2023')\n                .should('have.value', '01.01.2023 – 03.01.2023')\n                .should('have.prop', 'selectionStart', '01.01.2023 – 03.01.2023'.length)\n                .should('have.prop', 'selectionEnd', '01.01.2023 – 03.01.2023'.length);\n        });\n\n        it('01.01.2023 - 05.01.2023 => valid input, no changes', () => {\n            cy.get('@input')\n                .type('01.01.2023 - 05.01.2023')\n                .should('have.value', '01.01.2023 – 05.01.2023')\n                .should('have.prop', 'selectionStart', '01.01.2023 – 05.01.2023'.length)\n                .should('have.prop', 'selectionEnd', '01.01.2023 – 05.01.2023'.length);\n        });\n\n        it('01.01.2023 - 06.01.202| => Type 3 => 01.01.2023 - 05.01.2023', () => {\n            cy.get('@input')\n                .type('01.01.2023 - 06.01.202')\n                .should('have.value', '01.01.2023 – 06.01.202')\n                .should('have.prop', 'selectionStart', '01.01.2023 – 06.01.202'.length)\n                .should('have.prop', 'selectionEnd', '01.01.2023 – 06.01.202'.length)\n                .type('3')\n                .should('have.value', '01.01.2023 – 05.01.2023')\n                .should('have.prop', 'selectionStart', '01.01.2023 – 05.01.2023'.length)\n                .should('have.prop', 'selectionEnd', '01.01.2023 – 05.01.2023'.length);\n        });\n    });\n\n    describe('[minLength]={month: 1}', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateRange}/API?minLength$=2`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('01.03.2018 - 31.03.2018 => valid input', () => {\n            cy.get('@input')\n                .type('01.03.2018 - 31.03.2018')\n                .should('have.value', '01.03.2018 – 31.03.2018')\n                .should('have.prop', 'selectionStart', '01.03.2018 - 31.03.2018'.length)\n                .should('have.prop', 'selectionEnd', '01.03.2018 - 31.03.2018'.length);\n        });\n\n        it('02.03.2018 - 31.03.201| => Type 8 => 02.03.2018 - 01.04.2018', () => {\n            cy.get('@input')\n                .type('02.03.2018 - 31.03.201')\n                .should('have.value', '02.03.2018 – 31.03.201')\n                .should('have.prop', 'selectionStart', '02.03.2018 - 31.03.201'.length)\n                .should('have.prop', 'selectionEnd', '02.03.2018 - 31.03.201'.length)\n                .type('8')\n                .should('have.value', '02.03.2018 – 01.04.2018')\n                .should('have.prop', 'selectionStart', '02.03.2018 - 01.04.2018'.length)\n                .should('have.prop', 'selectionEnd', '02.03.2018 - 01.04.2018'.length);\n        });\n\n        it('18.02.2018 - 17.03.2018 => valid input', () => {\n            cy.get('@input')\n                .type('18.02.2018 - 17.03.2018')\n                .should('have.value', '18.02.2018 – 17.03.2018')\n                .should('have.prop', 'selectionStart', '18.02.2018 - 17.03.2018'.length)\n                .should('have.prop', 'selectionEnd', '18.02.2018 - 17.03.2018'.length);\n        });\n\n        it('19.02.2018 - 17.03.201| => Type 8 => 19.02.2018 - 18.03.2018', () => {\n            cy.get('@input')\n                .type('19.02.2018 - 17.03.201')\n                .should('have.value', '19.02.2018 – 17.03.201')\n                .should('have.prop', 'selectionStart', '19.02.2018 - 17.03.201'.length)\n                .should('have.prop', 'selectionEnd', '19.02.2018 - 17.03.201'.length)\n                .type('8')\n                .should('have.value', '19.02.2018 – 18.03.2018')\n                .should('have.prop', 'selectionStart', '19.02.2018 - 18.03.2018'.length)\n                .should('have.prop', 'selectionEnd', '19.02.2018 - 18.03.2018'.length);\n        });\n    });\n\n    describe('[minLength]={month: 1, day: 1}', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateRange}/API?minLength$=3`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('28.02.2018 - 31.03.2018 => valid input', () => {\n            cy.get('@input')\n                .type('28.02.2018 - 31.03.2018')\n                .should('have.value', '28.02.2018 – 31.03.2018')\n                .should('have.prop', 'selectionStart', '28.02.2018 - 31.03.2018'.length)\n                .should('have.prop', 'selectionEnd', '28.02.2018 - 31.03.2018'.length);\n        });\n\n        it('01.03.2018 - 31.03.201| => Type 8 => 01.03.2018 - 01.04.2018', () => {\n            cy.get('@input')\n                .type('01.03.2018 - 31.03.201')\n                .should('have.value', '01.03.2018 – 31.03.201')\n                .should('have.prop', 'selectionStart', '01.03.2018 - 31.03.201'.length)\n                .should('have.prop', 'selectionEnd', '01.03.2018 - 31.03.201'.length)\n                .type('8')\n                .should('have.value', '01.03.2018 – 01.04.2018')\n                .should('have.prop', 'selectionStart', '01.03.2018 - 01.04.2018'.length)\n                .should('have.prop', 'selectionEnd', '01.03.2018 - 01.04.2018'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-range/date-range-min-max.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('DateRange | Min & Max dates', () => {\n    describe('Max', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateRange}/API?max=2020-05-05`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('accepts date less than max value', () => {\n            cy.get('@input')\n                .type('18.12.2019-14.01.2020')\n                .should('have.value', '18.12.2019 – 14.01.2020')\n                .should('have.prop', 'selectionStart', '18.12.2019 – 14.01.2020'.length)\n                .should('have.prop', 'selectionEnd', '18.12.2019 – 14.01.2020'.length);\n        });\n\n        it('18.12.2019 - 14.01.202| => Type 5 => 18.12.2019-05.05.2020 (max value)', () => {\n            cy.get('@input')\n                .type('18.12.2019-14.01.202')\n                .should('have.value', '18.12.2019 – 14.01.202')\n                .should('have.prop', 'selectionStart', '18.12.2019 – 14.01.202'.length)\n                .should('have.prop', 'selectionEnd', '18.12.2019 – 14.01.202'.length)\n                .type('5')\n                .should('have.value', '18.12.2019 – 05.05.2020')\n                .should('have.prop', 'selectionStart', '18.12.2019 – 05.05.2020'.length)\n                .should('have.prop', 'selectionEnd', '18.12.2019 – 05.05.2020'.length);\n        });\n\n        it('18.12.2019 - 0|3.05.2020 => Type 7 => 18.12.2019 - 05|.05.2020 (max value)', () => {\n            cy.get('@input')\n                .type('18.12.2019-03.05.2020')\n                .type('{leftArrow}'.repeat('3.05.2020'.length))\n                .type('7')\n                .should('have.value', '18.12.2019 – 05.05.2020')\n                .should('have.prop', 'selectionStart', '18.12.2019 – 05.'.length)\n                .should('have.prop', 'selectionEnd', '18.12.2019 – 05.'.length);\n        });\n\n        it('18.12.2019 - 03.0|4.2020 => Type 7 => 18.12.2019 - 05.05|.2020 (max value)', () => {\n            cy.get('@input')\n                .type('18.12.2019-03.04.2020')\n                .type('{leftArrow}'.repeat('4.2020'.length))\n                .type('7')\n                .should('have.value', '18.12.2019 – 05.05.2020')\n                .should('have.prop', 'selectionStart', '18.12.2019 – 05.05.'.length)\n                .should('have.prop', 'selectionEnd', '18.12.2019 – 05.05.'.length);\n        });\n    });\n\n    describe('Min', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateRange}/API?min=1995-10-14`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('accepts date more than min value', () => {\n            cy.get('@input')\n                .type('13.04.2001-08.03.2002')\n                .should('have.value', '13.04.2001 – 08.03.2002')\n                .should('have.prop', 'selectionStart', '13.04.2001 – 08.03.2002'.length)\n                .should('have.prop', 'selectionEnd', '13.04.2001 – 08.03.2002'.length);\n        });\n\n        it('15.11.1997 - 15.12.199| => Type 3 => (min date validation + dates swap) => 14.10.1995 - 15.11.1997', () => {\n            cy.get('@input')\n                .type('15.11.1997-15.12.199')\n                .should('have.value', '15.11.1997 – 15.12.199')\n                .should('have.prop', 'selectionStart', '15.11.1997 – 15.12.199'.length)\n                .should('have.prop', 'selectionEnd', '15.11.1997 – 15.12.199'.length)\n                .type('3')\n                .should('have.value', '14.10.1995 – 15.11.1997')\n                .should('have.prop', 'selectionStart', '14.10.1995 – 15.11.1997'.length)\n                .should('have.prop', 'selectionEnd', '14.10.1995 – 15.11.1997'.length);\n        });\n\n        it('15.10.1995 - 1|7.10.1995 => Type 2 => 15.10.1995 - 14.|10.1995 (min)', () => {\n            cy.get('@input')\n                .type('15.10.1995-17.10.1995')\n                .type('{leftArrow}'.repeat('7.10.1995'.length))\n                .type('2')\n                .should('have.value', '15.10.1995 – 14.10.1995')\n                .should('have.prop', 'selectionStart', '15.10.1995 – 14.'.length)\n                .should('have.prop', 'selectionEnd', '15.10.1995 – 14.'.length);\n        });\n\n        it('15.10.1995 - 17.|10.1995 => Type 9 => 15.10.1995 - 14.10|.1995 (min)', () => {\n            cy.get('@input')\n                .type('15.10.1995-17.10.1995')\n                .type('{leftArrow}'.repeat('10.1995'.length))\n                .type('9')\n                .should('have.value', '15.10.1995 – 14.10.1995')\n                .should('have.prop', 'selectionStart', '15.10.1995 – 14.10.'.length)\n                .should('have.prop', 'selectionEnd', '15.10.1995 – 14.10.'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-range/date-range-mode.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('DateRange | Mode', () => {\n    describe('mm.dd.yyyy', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateRange}/API?mode=mm%2Fdd%2Fyyyy`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('12.31.2000 - 11.30.2019', () => {\n            cy.get('@input')\n                .type('1231200011302019')\n                .should('have.value', '12.31.2000 – 11.30.2019')\n                .should('have.prop', 'selectionStart', '12.31.2000 - 11.30.2019'.length)\n                .should('have.prop', 'selectionEnd', '12.31.2000 - 11.30.2019'.length);\n        });\n\n        it('Empty input => Type 3 => 03|', () => {\n            cy.get('@input')\n                .type('3')\n                .should('have.value', '03')\n                .should('have.prop', 'selectionStart', '03'.length)\n                .should('have.prop', 'selectionEnd', '03'.length);\n        });\n\n        it('12| => Type 3 => 12.3|', () => {\n            cy.get('@input')\n                .type('123')\n                .should('have.value', '12.3')\n                .should('have.prop', 'selectionStart', '12.3'.length)\n                .should('have.prop', 'selectionEnd', '12.3'.length);\n        });\n\n        it('12| => Type 4 => 12.04|', () => {\n            cy.get('@input')\n                .type('124')\n                .should('have.value', '12.04')\n                .should('have.prop', 'selectionStart', '12.04'.length)\n                .should('have.prop', 'selectionEnd', '12.04'.length);\n        });\n    });\n\n    describe('yyyy.mm.dd', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateRange}/API?mode=yyyy%2Fmm%2Fdd`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('2000.12.31 - 2019.11.30', () => {\n            cy.get('@input')\n                .type('2000123120191130')\n                .should('have.value', '2000.12.31 – 2019.11.30')\n                .should('have.prop', 'selectionStart', '2000.12.31 - 2019.11.30'.length)\n                .should('have.prop', 'selectionEnd', '2000.12.31 - 2019.11.30'.length);\n        });\n\n        it('2000| => Type 3 => 2000.03|', () => {\n            cy.get('@input')\n                .type('20003')\n                .should('have.value', '2000.03')\n                .should('have.prop', 'selectionStart', '2000.03'.length)\n                .should('have.prop', 'selectionEnd', '2000.03'.length);\n        });\n\n        it('2000.03| => Type 5 => 2000.03.05|', () => {\n            cy.get('@input')\n                .type('200035')\n                .should('have.value', '2000.03.05')\n                .should('have.prop', 'selectionStart', '2000.03.05'.length)\n                .should('have.prop', 'selectionEnd', '2000.03.05'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-range/date-range-segments-zero-padding.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('DateRange | Date segments zero padding (pads digits with zero if date segment exceeds its max possible value)', () => {\n    describe('[mode]=\"dd.mm.yyyy\"', () => {\n        const mode = encodeURIComponent('dd/mm/yyyy');\n        const FIRST_DATE = '01.01.2000';\n\n        beforeEach(() => {\n            cy.visit(\n                `/${DemoPath.DateRange}/API?mode=${mode}&dateSeparator=.&rangeSeparator=-`,\n            );\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .type('01012000')\n                .should('have.value', FIRST_DATE)\n                .as('input');\n        });\n\n        describe('if users enters two digits and its combination exceeds the first (and only first!) non-year date segment - pad the first digit with zero', () => {\n            it('{firstDate} => Type 35 => {firstDate}-03.05|', () => {\n                cy.get('@input')\n                    .type('35')\n                    .should('have.value', `${FIRST_DATE}-03.05`)\n                    .should('have.prop', 'selectionStart', `${FIRST_DATE}-03.05`.length)\n                    .should('have.prop', 'selectionEnd', `${FIRST_DATE}-03.05`.length);\n            });\n\n            it('{firstDate}-|19.01.2025 => Type 3 => {firstDate}-3|0.01.2025', () => {\n                cy.get('@input')\n                    .type('19012025')\n                    .should('have.value', `${FIRST_DATE}-19.01.2025`)\n                    .type('{leftArrow}'.repeat('19.01.2025'.length))\n                    .should('have.prop', 'selectionStart', `${FIRST_DATE}-`.length)\n                    .should('have.prop', 'selectionEnd', `${FIRST_DATE}-`.length)\n                    .type('3')\n                    .should('have.value', `${FIRST_DATE}-30.01.2025`)\n                    .should('have.prop', 'selectionStart', `${FIRST_DATE}-3`.length)\n                    .should('have.prop', 'selectionEnd', `${FIRST_DATE}-3`.length);\n            });\n\n            it('{firstDate}-31.1| => Type 3 => {firstDate}-31.1|', () => {\n                cy.get('@input')\n                    .type('311')\n                    .should('have.value', `${FIRST_DATE}-31.1`)\n                    .type('3')\n                    .should('have.value', `${FIRST_DATE}-31.1`)\n                    .should('have.prop', 'selectionStart', `${FIRST_DATE}-31.1`.length)\n                    .should('have.prop', 'selectionEnd', `${FIRST_DATE}-31.1`.length);\n            });\n        });\n    });\n\n    describe('[mode]=\"mm/dd/yyyy\"', () => {\n        const mode = encodeURIComponent('mm/dd/yyyy');\n        const dateSeparator = encodeURIComponent('/');\n        const FIRST_DATE = '01/01/2000';\n\n        beforeEach(() => {\n            cy.visit(\n                `/${DemoPath.DateRange}/API?mode=${mode}&dateSeparator=${dateSeparator}&rangeSeparator=-`,\n            );\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .type('01012000')\n                .should('have.value', FIRST_DATE)\n                .as('input');\n        });\n\n        describe('handles month value exceeding maximum', () => {\n            it('{firstDate} => Type 13 => {firstDate}-01/3', () => {\n                cy.get('@input')\n                    .type('13')\n                    .should('have.value', `${FIRST_DATE}-01/3`)\n                    .should('have.prop', 'selectionStart', `${FIRST_DATE}-01/3`.length)\n                    .should('have.prop', 'selectionEnd', `${FIRST_DATE}-01/3`.length);\n            });\n        });\n    });\n\n    describe('[mode]=\"yyyy/mm/dd\"', () => {\n        const mode = encodeURIComponent('yyyy/mm/dd');\n        const dateSeparator = encodeURIComponent('/');\n        const FIRST_DATE = '2000/01/01';\n\n        beforeEach(() => {\n            cy.visit(\n                `/${DemoPath.DateRange}/API?mode=${mode}&dateSeparator=${dateSeparator}&&rangeSeparator=-`,\n            );\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .type('20000101')\n                .should('have.value', FIRST_DATE)\n                .as('input');\n        });\n\n        describe('if users enters two digits and its combination exceeds the first (and only first!) non-year date segment - pad the first digit with zero', () => {\n            it('2025 => Type 35 => 2025/03/05|', () => {\n                cy.get('@input')\n                    .type('202535')\n                    .should('have.value', `${FIRST_DATE}-2025/03/05`)\n                    .should(\n                        'have.prop',\n                        'selectionStart',\n                        `${FIRST_DATE}-2025/03/05`.length,\n                    )\n                    .should(\n                        'have.prop',\n                        'selectionEnd',\n                        `${FIRST_DATE}-2025/03/05`.length,\n                    );\n            });\n\n            it('2025/|09/30 => Type 1 => 2025/1|0/30', () => {\n                cy.get('@input')\n                    .type('2025930')\n                    .should('have.value', `${FIRST_DATE}-2025/09/30`)\n                    .type('{leftArrow}'.repeat('09/30'.length))\n                    .should('have.prop', 'selectionStart', `${FIRST_DATE}-2025/`.length)\n                    .should('have.prop', 'selectionEnd', `${FIRST_DATE}-2025/`.length)\n                    .type('1')\n                    .should('have.value', `${FIRST_DATE}-2025/10/30`)\n                    .should('have.prop', 'selectionStart', `${FIRST_DATE}-2025/1`.length)\n                    .should('have.prop', 'selectionEnd', `${FIRST_DATE}-2025/1`.length);\n            });\n\n            it('2025.01.3| => Type 5 => 2025.01.3|', () => {\n                cy.get('@input')\n                    .type('2025013')\n                    .should('have.value', `${FIRST_DATE}-2025/01/3`)\n                    .type('5')\n                    .should('have.value', `${FIRST_DATE}-2025/01/3`)\n                    .should(\n                        'have.prop',\n                        'selectionStart',\n                        `${FIRST_DATE}-2025/01/3`.length,\n                    )\n                    .should(\n                        'have.prop',\n                        'selectionEnd',\n                        `${FIRST_DATE}-2025/01/3`.length,\n                    );\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-range/date-range-separator.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('DateRange | dateSeparator', () => {\n    describe('/', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateRange}/API?dateSeparator=/`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('14/12/1997 – 09/07/2015', () => {\n            cy.get('@input')\n                .type('14121997972015')\n                .should('have.value', '14/12/1997 – 09/07/2015');\n        });\n\n        it('rejects dot as separator', () => {\n            cy.get('@input')\n                .type('1412200011.')\n                .should('have.value', '14/12/2000 – 11')\n                .type('12.')\n                .should('have.value', '14/12/2000 – 11/12');\n        });\n    });\n\n    describe('-', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateRange}/API?dateSeparator=-`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('14-12-1997 – 09-07-2015', () => {\n            cy.get('@input')\n                .type('14121997972015')\n                .should('have.value', '14-12-1997 – 09-07-2015');\n        });\n\n        it('rejects dot as separator', () => {\n            cy.get('@input')\n                .type('14')\n                .should('have.value', '14')\n                .type('12.')\n                .should('have.value', '14-12');\n        });\n    });\n\n    describe('dates separator', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateRange}/API?dateSeparator=/`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('14/12/1997 – ', () => {\n            cy.get('@input')\n                .type('14121997-')\n                .should('have.value', '14/12/1997 – ')\n                .should('have.prop', 'selectionStart', '14/12/1997 – '.length)\n                .should('have.prop', 'selectionEnd', '14/12/1997 – '.length);\n        });\n\n        it('14/12/19 => \"-\" => 14/12/19', () => {\n            cy.get('@input')\n                .type('141219-')\n                .should('have.value', '14/12/19')\n                .should('have.prop', 'selectionStart', '14/12/19'.length)\n                .should('have.prop', 'selectionEnd', '14/12/19'.length);\n        });\n\n        it('type date with all separators ', () => {\n            cy.get('@input')\n                .type('14/12/1997-14/12/1997')\n                .should('have.value', '14/12/1997 – 14/12/1997')\n                .should('have.prop', 'selectionStart', '14/12/1997 – 14/12/1997'.length)\n                .should('have.prop', 'selectionEnd', '14/12/1997 – 14/12/1997'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-time/date-time-basic.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\nimport {repeatKey} from '../../utils';\n\ndescribe('DateTime | Basic', () => {\n    beforeEach(() => {\n        cy.visit(\n            `/${DemoPath.DateTime}/API?dateMode=dd%2Fmm%2Fyyyy&timeMode=HH:MM:SS.MSS`,\n        );\n        cy.get('#demo-content input').should('be.visible').first().focus().as('input');\n    });\n\n    describe('basic typing', () => {\n        const tests = [\n            // [Typed value, Masked value]\n            ['1', '1'],\n            ['18', '18'],\n            ['181', '18.1'],\n            ['1811', '18.11'],\n            ['18112', '18.11.2'],\n            ['18112016', '18.11.2016'],\n            ['181120162', '18.11.2016, 2'],\n            ['1811201623', '18.11.2016, 23'],\n            ['1811201623:', '18.11.2016, 23:'],\n            ['18112016231', '18.11.2016, 23:1'],\n            ['181120162315', '18.11.2016, 23:15'],\n            ['181120162315:', '18.11.2016, 23:15:'],\n            ['1811201623152', '18.11.2016, 23:15:2'],\n            ['18112016231522', '18.11.2016, 23:15:22'],\n            ['18112016231522.', '18.11.2016, 23:15:22.'],\n            ['18112016231522123', '18.11.2016, 23:15:22.123'],\n            ['0', '0'],\n            ['00', '0'],\n        ] as const;\n\n        tests.forEach(([typedValue, maskedValue]) => {\n            it(`Type \"${typedValue}\" => \"${maskedValue}\"`, () => {\n                cy.get('@input')\n                    .type(typedValue)\n                    .should('have.value', maskedValue)\n                    .should('have.prop', 'selectionStart', maskedValue.length)\n                    .should('have.prop', 'selectionEnd', maskedValue.length);\n            });\n        });\n    });\n\n    describe('invalid date cases', () => {\n        it('Empty input => Type \"9\" => \"09|\"', () => {\n            cy.get('@input')\n                .type('9')\n                .should('have.value', '09')\n                .should('have.prop', 'selectionStart', '09'.length)\n                .should('have.prop', 'selectionEnd', '09'.length);\n        });\n\n        it('27| => type 2 => 27.02|', () => {\n            cy.get('@input')\n                .type('27')\n                .should('have.value', '27')\n                .should('have.prop', 'selectionStart', '27'.length)\n                .should('have.prop', 'selectionEnd', '27'.length)\n                .type('2')\n                .should('have.value', '27.02')\n                .should('have.prop', 'selectionStart', '27.02'.length)\n                .should('have.prop', 'selectionEnd', '27.02'.length);\n        });\n\n        it('3| => Type 7 => 03.07|', () => {\n            cy.get('@input')\n                .type('3')\n                .should('have.value', '3')\n                .should('have.prop', 'selectionStart', '3'.length)\n                .should('have.prop', 'selectionEnd', '3'.length)\n                .type('7')\n                .should('have.value', '03.07')\n                .should('have.prop', 'selectionStart', '03.07'.length)\n                .should('have.prop', 'selectionEnd', '03.07'.length);\n        });\n    });\n\n    describe('invalid time cases', () => {\n        beforeEach(() => {\n            cy.get('@input').type('10102020');\n        });\n        it('\"10.10.2020\" => Type \"9\" => \"10.10.2020, 09|\"', () => {\n            cy.get('@input')\n                .type('9')\n                .should('have.value', '10.10.2020, 09')\n                .should('have.prop', 'selectionStart', '10.10.2020, 09'.length)\n                .should('have.prop', 'selectionEnd', '10.10.2020, 09'.length);\n        });\n\n        it('\"10.10.2020, 10\" => Type \"9\" => \"10.10.2020, 10:09|\"', () => {\n            cy.get('@input')\n                .type('10')\n                .should('have.value', '10.10.2020, 10')\n                .should('have.prop', 'selectionStart', '10.10.2020, 10'.length)\n                .should('have.prop', 'selectionEnd', '10.10.2020, 10'.length)\n                .type('9')\n                .should('have.value', '10.10.2020, 10:09')\n                .should('have.prop', 'selectionStart', '10.10.2020, 10:09'.length)\n                .should('have.prop', 'selectionEnd', '10.10.2020, 10:09'.length);\n        });\n\n        it('\"10.10.2020, 2\" => Type \"7\" => no value changes', () => {\n            cy.get('@input')\n                .type('2')\n                .should('have.value', '10.10.2020, 2')\n                .should('have.prop', 'selectionStart', '10.10.2020, 2'.length)\n                .should('have.prop', 'selectionEnd', '10.10.2020, 2'.length)\n                .type('7')\n                .should('have.value', '10.10.2020, 2')\n                .should('have.prop', 'selectionStart', '10.10.2020, 2'.length)\n                .should('have.prop', 'selectionEnd', '10.10.2020, 2'.length);\n        });\n    });\n\n    describe('basic erasing (value = \"20.01.1990, 15:40:20\" & caret is placed after the last value)', () => {\n        beforeEach(() => {\n            cy.get('@input').type('20011990154020');\n        });\n\n        const tests = [\n            // [How many times \"Backspace\"-key was pressed, caretPosition, Masked value]\n            [1, '20.01.1990, 15:40:2'.length, '20.01.1990, 15:40:2'],\n            [4, '20.01.1990, 15'.length, '20.01.1990, 15'],\n            [5, '20.01.1990, 1'.length, '20.01.1990, 1'],\n            [6, '20.01.1990'.length, '20.01.1990'],\n            [8, '20.01.19'.length, '20.01.19'],\n            [12, '20'.length, '20'],\n            [13, '2'.length, '2'],\n        ] as const;\n\n        tests.forEach(([n, caretIndex, maskedValue]) => {\n            it(`Backspace x${n} => \"${maskedValue}\"`, () => {\n                cy.get('@input')\n                    .type('{backspace}'.repeat(n))\n                    .should('have.value', maskedValue)\n                    .should('have.prop', 'selectionStart', caretIndex)\n                    .should('have.prop', 'selectionEnd', caretIndex);\n            });\n        });\n\n        it('Delete => no value change && no caret index change', () => {\n            cy.get('@input')\n                .type('{del}')\n                .should('have.value', '20.01.1990, 15:40:20')\n                .should('have.prop', 'selectionStart', '20.01.1990, 15:40:20'.length)\n                .should('have.prop', 'selectionEnd', '20.01.1990, 15:40:20'.length);\n        });\n\n        it('Type `deleteWordBackward` of `InputEvent` works', () => {\n            cy.get('@input')\n                .type('{ctrl+backspace}')\n                .should('have.value', '20.01.1990')\n                .should('have.prop', 'selectionStart', '20.01.1990'.length)\n                .should('have.prop', 'selectionEnd', '20.01.1990'.length)\n                .type('{ctrl+backspace}')\n                .should('have.value', '')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n    });\n\n    describe('Editing somewhere in the middle of a value (NOT the last character)', () => {\n        it('25.02.19|99, 15:35 => Backspace => 25.02.1|099, 15:35 => Type \"8\" => 25.02.18|99, 15:35', () => {\n            cy.get('@input')\n                .type('250219991535')\n                .should('have.value', '25.02.1999, 15:35')\n                .type('{leftArrow}'.repeat('99, 15:35'.length))\n                .should('have.prop', 'selectionStart', '25.02.19'.length)\n                .should('have.prop', 'selectionEnd', '25.02.19'.length)\n                .type('{backspace}')\n                .should('have.value', '25.02.1099, 15:35')\n                .should('have.prop', 'selectionStart', '25.02.1'.length)\n                .should('have.prop', 'selectionEnd', '25.02.1'.length)\n                .type('8')\n                .should('have.value', '25.02.1899, 15:35')\n                .should('have.prop', 'selectionStart', '25.02.18'.length)\n                .should('have.prop', 'selectionEnd', '25.02.18'.length);\n        });\n\n        it('13.06.1736, 15:05|:20 => Backspace => 13.06.1736, 15:0|0:20 => Type \"3\" => 13.06.1736, 15:03:20', () => {\n            cy.get('@input')\n                .type('13061736150520')\n                .should('have.value', '13.06.1736, 15:05:20')\n                .type('{leftArrow}'.repeat(':20'.length))\n                .should('have.prop', 'selectionStart', '13.06.1736, 15:05'.length)\n                .should('have.prop', 'selectionEnd', '13.06.1736, 15:05'.length)\n                .type('{backspace}')\n                .should('have.value', '13.06.1736, 15:00:20')\n                .should('have.prop', 'selectionStart', '13.06.1736, 15:0'.length)\n                .should('have.prop', 'selectionEnd', '13.06.1736, 15:0'.length)\n                .type('3')\n                .should('have.value', '13.06.1736, 15:03:20')\n                .should('have.prop', 'selectionStart', '13.06.1736, 15:03:'.length)\n                .should('have.prop', 'selectionEnd', '13.06.1736, 15:03:'.length);\n        });\n\n        it('12.|12.2010, 12:30 => Type \"9\" => 12.09.|2010, 12:30', () => {\n            cy.get('@input')\n                .type('121220101230')\n                .should('have.value', '12.12.2010, 12:30')\n                .type('{leftArrow}'.repeat('12.2010, 12:30'.length))\n                .should('have.prop', 'selectionStart', '12.'.length)\n                .should('have.prop', 'selectionEnd', '12.'.length)\n                .type('9')\n                .should('have.value', '12.09.2010, 12:30')\n                .should('have.prop', 'selectionStart', '12.09.'.length)\n                .should('have.prop', 'selectionEnd', '12.09.'.length);\n        });\n\n        it('12.12.2010, |12:30 => Type \"9\" => 12.12.2010, 09|:30', () => {\n            cy.get('@input')\n                .type('121220101230')\n                .should('have.value', '12.12.2010, 12:30')\n                .type('{leftArrow}'.repeat('12:30'.length))\n                .should('have.prop', 'selectionStart', '12.12.2010, '.length)\n                .should('have.prop', 'selectionEnd', '12.12.2010, '.length)\n                .type('9')\n                .should('have.value', '12.12.2010, 09:30')\n                .should('have.prop', 'selectionStart', '12.12.2010, 09:'.length)\n                .should('have.prop', 'selectionEnd', '12.12.2010, 09:'.length);\n        });\n    });\n\n    describe('Text selection', () => {\n        describe('Select range and press Backspace / Delete', () => {\n            it(\n                '10.|12|.2005, 12:30 => Backspace => 10.|01.2005, 12:30',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('101220051230')\n                        .should('have.value', '10.12.2005, 12:30')\n                        .type('{leftArrow}'.repeat('.2005, 12:30'.length))\n                        .realPress([\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '12'.length),\n                            'Backspace',\n                        ]);\n\n                    cy.get('@input')\n                        .should('have.value', '10.01.2005, 12:30')\n                        .should('have.prop', 'selectionStart', '10.'.length)\n                        .should('have.prop', 'selectionEnd', '10.'.length);\n                },\n            );\n\n            it(\n                '10.12.2005, |12|:30 => Backspace => 10.12.2005, |00:30',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('101220051230')\n                        .should('have.value', '10.12.2005, 12:30')\n                        .type('{leftArrow}'.repeat(':30'.length))\n                        .realPress([\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '12'.length),\n                            'Backspace',\n                        ]);\n\n                    cy.get('@input')\n                        .should('have.value', '10.12.2005, 00:30')\n                        .should('have.prop', 'selectionStart', '10.12.2005, '.length)\n                        .should('have.prop', 'selectionEnd', '10.12.2005, '.length);\n                },\n            );\n\n            it(\n                '1|1.1|1.2011, 12:30 => Delete => 10.0|1.2011, 12:30',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('111120111230')\n                        .should('have.value', '11.11.2011, 12:30')\n                        .type('{leftArrow}'.repeat('1.2011, 12:30'.length))\n                        .realPress(['Shift', ...repeatKey('ArrowLeft', '1.1'.length)]);\n\n                    cy.get('@input')\n                        .type('{del}')\n                        .should('have.value', '10.01.2011, 12:30')\n                        .should('have.prop', 'selectionStart', '10.0'.length)\n                        .should('have.prop', 'selectionEnd', '10.0'.length);\n                },\n            );\n\n            it(\n                '11.11.2011, 1|2:3|0 => Delete => 11.11.2011, 10:0|0',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('111120111230')\n                        .should('have.value', '11.11.2011, 12:30')\n                        .type('{leftArrow}'.repeat('0'.length))\n                        .realPress(['Shift', ...repeatKey('ArrowLeft', '2.3'.length)]);\n\n                    cy.get('@input')\n                        .type('{del}')\n                        .should('have.value', '11.11.2011, 10:00')\n                        .should('have.prop', 'selectionStart', '11.11.2011, 10:0'.length)\n                        .should('have.prop', 'selectionEnd', '11.11.2011, 10:0'.length);\n                },\n            );\n        });\n\n        describe('Select range and press new digit', () => {\n            it(\n                '|12|.11.2022 (specifically do not completes value) => Press 3 => 3|0.11.2022',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('12112022')\n                        .type('{leftArrow}'.repeat('.11.2022'.length))\n                        .realPress(['Shift', ...repeatKey('ArrowLeft', '12'.length)]);\n\n                    cy.get('@input')\n                        .type('3')\n                        .should('have.value', '30.11.2022')\n                        .should('have.prop', 'selectionStart', '3'.length)\n                        .should('have.prop', 'selectionEnd', '3'.length);\n                },\n            );\n\n            it(\n                '01.01.2000, |12|:30 => Press 2 => 01.01.2000, 2|0:30',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('010120001230')\n                        .type('{leftArrow}'.repeat(':30'.length))\n                        .realPress(['Shift', ...repeatKey('ArrowLeft', '12'.length)]);\n\n                    cy.get('@input')\n                        .type('2')\n                        .should('have.value', '01.01.2000, 20:30')\n                        .should('have.prop', 'selectionStart', '01.01.2000, 2'.length)\n                        .should('have.prop', 'selectionEnd', '01.01.2000, 2'.length);\n                },\n            );\n\n            it(\n                '01.01.2000, 1|2|:30 => Press 5 => 01.01.2000, 15:|30',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input')\n                        .type('010120001230')\n                        .type('{leftArrow}'.repeat(':30'.length))\n                        .realPress(['Shift', 'ArrowLeft']);\n\n                    cy.get('@input')\n                        .type('5')\n                        .should('have.value', '01.01.2000, 15:30')\n                        .should('have.prop', 'selectionStart', '01.01.2000, 15:'.length)\n                        .should('have.prop', 'selectionEnd', '01.01.2000, 15:'.length);\n                },\n            );\n        });\n    });\n\n    describe('Paste', () => {\n        it('value without segment separators', () => {\n            cy.get('@input')\n                .paste('02112018, 16:20')\n                .should('have.value', '02.11.2018, 16:20')\n                .should('have.prop', 'selectionStart', '02.11.2018, 16:20'.length)\n                .should('have.prop', 'selectionEnd', '02.11.2018, 16:20'.length);\n        });\n\n        it('value without separator between date and time', () => {\n            cy.get('@input')\n                .paste('02.11.201816:20')\n                .should('have.value', '02.11.2018, 16:20')\n                .should('have.prop', 'selectionStart', '02.11.2018, 16:20'.length)\n                .should('have.prop', 'selectionEnd', '02.11.2018, 16:20'.length);\n        });\n\n        it('value with incomplete separator between date and time', () => {\n            cy.get('@input')\n                .paste('02.11.2018,16:20')\n                .should('have.value', '02.11.2018, 16:20')\n                .should('have.prop', 'selectionStart', '02.11.2018, 16:20'.length)\n                .should('have.prop', 'selectionEnd', '02.11.2018, 16:20'.length);\n        });\n\n        it('value without any separators', () => {\n            cy.get('@input')\n                .paste('021120181620')\n                .should('have.value', '02.11.2018, 16:20')\n                .should('have.prop', 'selectionStart', '02.11.2018, 16:20'.length)\n                .should('have.prop', 'selectionEnd', '02.11.2018, 16:20'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-time/date-time-date-time-separator.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\nimport type {MaskitoTimeMode} from '@maskito/kit';\n\ndescribe('DateTime | dateTimeSeparator', () => {\n    const dateTimeSeparators = [':', ';_', '_-_', '_at_'];\n\n    dateTimeSeparators.forEach((dateTimeSeparator) => {\n        const testCases: Array<{\n            typedDigits: string;\n            formattedDate: string;\n            formattedValue: string;\n            timeMode: MaskitoTimeMode;\n        }> = [\n            {\n                typedDigits: '522004341',\n                formattedValue: `05.02.2004${dateTimeSeparator}03:41`,\n                formattedDate: '05.02.2004',\n                timeMode: 'HH:MM',\n            },\n            {\n                typedDigits: '233123434111',\n                formattedValue: `23.03.1234${dateTimeSeparator}03:41:11`,\n                formattedDate: '23.03.1234',\n                timeMode: 'HH:MM:SS',\n            },\n            {\n                typedDigits: '69200734111111',\n                formattedValue: `06.09.2007${dateTimeSeparator}03:41:11.111`,\n                formattedDate: '06.09.2007',\n                timeMode: 'HH:MM:SS.MSS',\n            },\n        ];\n\n        describe(`correctly applies \"${dateTimeSeparator}\" as dateTimeSeparator`, () => {\n            testCases.forEach(\n                ({typedDigits, formattedDate, formattedValue, timeMode}) => {\n                    const timeDigitsCount = timeMode.replaceAll(/[:.]/g, '').length;\n\n                    beforeEach(() => {\n                        cy.visit(\n                            `/${DemoPath.DateTime}/API?dateTimeSeparator=${encodeURIComponent(dateTimeSeparator)}&timeMode=${encodeURIComponent(timeMode)}`,\n                        );\n                        cy.get('#demo-content input')\n                            .should('be.visible')\n                            .first()\n                            .focus()\n                            .as('input');\n                    });\n\n                    it(`${typedDigits} => ${formattedValue} => {backspace} * ${timeDigitsCount} => ${formattedDate}`, () => {\n                        cy.get('@input')\n                            .type(typedDigits)\n                            .should('have.value', formattedValue)\n                            .type('{backspace}'.repeat(timeDigitsCount))\n                            .should('have.value', formattedDate);\n                    });\n                },\n            );\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-time/date-time-fullwidth-to-halfwidth.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('DateTime | Full width character parsing', () => {\n    beforeEach(() => {\n        cy.visit(\n            `/${DemoPath.DateTime}/API?dateMode=yyyy%2Fmm%2Fdd&timeMode=HH:MM:SS&dateSeparator=%2F`,\n        );\n        cy.get('#demo-content input').should('be.visible').first().focus().as('input');\n    });\n\n    describe('basic typing', () => {\n        const tests = [\n            // [Typed value, Masked value]\n            ['２', '2'],\n            ['２０', '20'],\n            ['２０１', '201'],\n            ['２０１６', '2016'],\n            ['２０１６２', '2016/02'],\n            ['２０１６２２８', '2016/02/28'],\n            ['２０１６２２８３', '2016/02/28, 03'],\n            ['２０１６２２８３３０', '2016/02/28, 03:30'],\n            ['２０１６２２８３３０４', '2016/02/28, 03:30:4'],\n            ['２０１６２２８３３０４５', '2016/02/28, 03:30:45'],\n        ] as const;\n\n        tests.forEach(([typedValue, maskedValue]) => {\n            it(`Type \"${typedValue}\" => \"${maskedValue}\"`, () => {\n                cy.get('@input')\n                    .type(typedValue)\n                    .should('have.value', maskedValue)\n                    .should('have.prop', 'selectionStart', maskedValue.length)\n                    .should('have.prop', 'selectionEnd', maskedValue.length);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-time/date-time-meridiem.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nimport {range, withCaretLabel} from '../../utils';\n\ndescribe('DateTime | time modes with meridiem', () => {\n    describe('HH:MM AA', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateTime}/API?timeMode=HH:MM%20AA`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('textfield');\n        });\n\n        describe('basic text insertion works', () => {\n            it('Empty textfield => Type 1234AM => 12:34 AM', () => {\n                cy.get('@textfield')\n                    .type('9920001234AM')\n                    .should('have.value', '09.09.2000, 12:34 AM')\n                    .should('have.prop', 'selectionStart', '09.09.2000, 12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '09.09.2000, 12:34 AM'.length);\n            });\n\n            it('<any date>12:34| => Type lowercase `a` => <any date>12:34 AM', () => {\n                cy.get('@textfield')\n                    .type('01.01.20001234a')\n                    .should('have.value', '01.01.2000, 12:34 AM')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34 AM'.length);\n            });\n\n            it('<any date>12:34| => Type uppercase `A` => <any date>12:34 AM', () => {\n                cy.get('@textfield')\n                    .type('01.01.20001234A')\n                    .should('have.value', '01.01.2000, 12:34 AM')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34 AM'.length);\n            });\n\n            it('<any date>12:34| => Type lowercase `p` => <any date>12:34 PM', () => {\n                cy.get('@textfield')\n                    .type('01.01.20001234p')\n                    .should('have.value', '01.01.2000, 12:34 PM')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34 PM'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34 PM'.length);\n            });\n\n            it('<any date>12:34| => Type uppercase `P` => <any date>12:34 PM', () => {\n                cy.get('@textfield')\n                    .type('01.01.20001234P')\n                    .should('have.value', '01.01.2000, 12:34 PM')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34 PM'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34 PM'.length);\n            });\n\n            it('<any date>12:34| => Type lowercase `m` => <any date>12:34|', () => {\n                cy.get('@textfield')\n                    .type('01.01.20001234m')\n                    .should('have.value', '01.01.2000, 12:34')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34'.length);\n            });\n\n            it('<any date>12:34| => Type uppercase `M` => <any date>12:34|', () => {\n                cy.get('@textfield')\n                    .type('01.01.20001234M')\n                    .should('have.value', '01.01.2000, 12:34')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34'.length);\n            });\n        });\n\n        describe('deletion of any meridiem characters deletes all meridiem character', () => {\n            [\n                {caretIndex: '01.01.2000, 12:34 AM'.length, action: '{backspace}'},\n                {caretIndex: '01.01.2000, 12:34 A'.length, action: '{backspace}'},\n                {caretIndex: '01.01.2000, 12:34 '.length, action: '{del}'},\n                {caretIndex: '01.01.2000, 12:34 A'.length, action: '{del}'},\n            ].forEach(({caretIndex, action}) => {\n                const initialValue = '01.01.2000, 12:34 AM';\n\n                it(`${withCaretLabel(initialValue, caretIndex)} => ${action} => 12:34|`, () => {\n                    cy.get('@textfield')\n                        .type('01.01.2000 1234a')\n                        .should('have.value', initialValue)\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat(caretIndex))\n                        .type(action)\n                        .should('have.value', '01.01.2000, 12:34')\n                        .should('have.prop', 'selectionStart', '01.01.2000, 12:34'.length)\n                        .should('have.prop', 'selectionEnd', '01.01.2000, 12:34'.length);\n                });\n            });\n        });\n\n        describe('type new meridiem value when textfield already has another one', () => {\n            it('<any date>12:34 AM| => Type P => <any date>12:34 PM|', () => {\n                cy.get('@textfield')\n                    .type('01.01.2000 1234a')\n                    .should('have.value', '01.01.2000, 12:34 AM')\n                    .type('{moveToEnd}')\n                    .type('p')\n                    .should('have.value', '01.01.2000, 12:34 PM')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34 PM'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34 PM'.length);\n            });\n\n            it('<any date>12:34 A|M => Type P => <any date>12:34 PM|', () => {\n                cy.get('@textfield')\n                    .type('01.01.2000 1234a')\n                    .should('have.value', '01.01.2000, 12:34 AM')\n                    .type('{moveToEnd}{leftArrow}')\n                    .type('p')\n                    .should('have.value', '01.01.2000, 12:34 PM')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34 PM'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34 PM'.length);\n            });\n\n            it('<any date>12:34 |AM => Type P => <any date>12:34 PM|', () => {\n                cy.get('@textfield')\n                    .type('01.01.2000 1234a')\n                    .should('have.value', '01.01.2000, 12:34 AM')\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat(2))\n                    .type('p')\n                    .should('have.value', '01.01.2000, 12:34 PM')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34 PM'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34 PM'.length);\n            });\n\n            it('<any date>12:34| AM => Type P => <any date>12:34 PM|', () => {\n                cy.get('@textfield')\n                    .type('01.01.2000 1234a')\n                    .should('have.value', '01.01.2000, 12:34 AM')\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat(' AM'.length))\n                    .type('p')\n                    .should('have.value', '01.01.2000, 12:34 PM')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34 PM'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34 PM'.length);\n            });\n\n            it('<any date>12:34 PM| => Type A => <any date>12:34 AM|', () => {\n                cy.get('@textfield')\n                    .type('01.01.2000 1234p')\n                    .should('have.value', '01.01.2000, 12:34 PM')\n                    .type('{moveToEnd}')\n                    .type('a')\n                    .should('have.value', '01.01.2000, 12:34 AM')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34 AM'.length);\n            });\n\n            it('<any date>12:34 P|M => Type A => <any date>12:34 AM|', () => {\n                cy.get('@textfield')\n                    .type('01.01.2000 1234p')\n                    .should('have.value', '01.01.2000, 12:34 PM')\n                    .type('{moveToEnd}{leftArrow}')\n                    .type('A')\n                    .should('have.value', '01.01.2000, 12:34 AM')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34 AM'.length);\n            });\n\n            it('<any date>12:34 |PM => Type A => <any date>12:34 AM|', () => {\n                cy.get('@textfield')\n                    .type('01.01.2000 1234p')\n                    .should('have.value', '01.01.2000, 12:34 PM')\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat(2))\n                    .type('a')\n                    .should('have.value', '01.01.2000, 12:34 AM')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34 AM'.length);\n            });\n\n            it('<any date>12:34| PM => Type A => <any date>12:34 AM|', () => {\n                cy.get('@textfield')\n                    .type('01.01.2000 1234p')\n                    .should('have.value', '01.01.2000, 12:34 PM')\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat(' PM'.length))\n                    .type('a')\n                    .should('have.value', '01.01.2000, 12:34 AM')\n                    .should('have.prop', 'selectionStart', '01.01.2000, 12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '01.01.2000, 12:34 AM'.length);\n            });\n        });\n\n        describe('hour segment bounds', () => {\n            const beforeTimeValue = '01.01.2000, ';\n\n            beforeEach(() => {\n                cy.get('@textfield')\n                    .type('01.01.2000 ')\n                    .should('have.value', beforeTimeValue);\n            });\n\n            it('cannot be less than 01 (rejects zero as the 2nd hour segment)', () => {\n                cy.get('@textfield')\n                    .type('00')\n                    .should('have.value', `${beforeTimeValue}0`)\n                    .should('have.prop', 'selectionStart', beforeTimeValue.length + 1)\n                    .should('have.prop', 'selectionEnd', beforeTimeValue.length + 1);\n            });\n\n            it('can be 1 (as the 1st digit segment)', () => {\n                cy.get('@textfield')\n                    .type('1')\n                    .should('have.value', `${beforeTimeValue}1`)\n                    .should('have.prop', 'selectionStart', beforeTimeValue.length + 1)\n                    .should('have.prop', 'selectionEnd', beforeTimeValue.length + 1);\n            });\n\n            describe('automatically pads with zero', () => {\n                range(2, 9).forEach((x) => {\n                    it(`on attempt to enter ${x} as the first hour segment`, () => {\n                        cy.get('@textfield')\n                            .type(String(x))\n                            .should('have.value', `${beforeTimeValue}0${x}`)\n                            .should(\n                                'have.prop',\n                                'selectionStart',\n                                beforeTimeValue.length + 2,\n                            )\n                            .should(\n                                'have.prop',\n                                'selectionEnd',\n                                beforeTimeValue.length + 2,\n                            );\n                    });\n                });\n            });\n\n            range(10, 12).forEach((x) => {\n                const value = String(x);\n\n                it(`can be ${x}`, () => {\n                    cy.get('@textfield')\n                        .type(value)\n                        .should('have.value', `${beforeTimeValue}${value}`)\n                        .should('have.prop', 'selectionStart', beforeTimeValue.length + 2)\n                        .should('have.prop', 'selectionEnd', beforeTimeValue.length + 2);\n                });\n            });\n\n            describe('rejects insertion', () => {\n                range(13, 19).forEach((x) => {\n                    it(`on attempt to enter ${x} as the last hour segment`, () => {\n                        cy.get('@textfield')\n                            .type(String(x))\n                            .should('have.value', `${beforeTimeValue}1`)\n                            .should(\n                                'have.prop',\n                                'selectionStart',\n                                beforeTimeValue.length + 1,\n                            )\n                            .should(\n                                'have.prop',\n                                'selectionEnd',\n                                beforeTimeValue.length + 1,\n                            );\n                    });\n                });\n            });\n        });\n\n        describe('toggle meridiem value on ArrowUp / ArrowDown', () => {\n            describe('Initial value === \"<any date>12:34 |\"', () => {\n                const beforeMeridiemValue = '01.01.2000, 12:34 ';\n\n                beforeEach(() => {\n                    cy.get('@textfield')\n                        .type('01.01.2000 1234 ')\n                        .should('have.value', beforeMeridiemValue);\n                });\n\n                it('↑ --- 12:34 |AM', () => {\n                    cy.get('@textfield')\n                        .type('{upArrow}')\n                        .should('have.value', `${beforeMeridiemValue}AM`)\n                        .should('have.prop', 'selectionStart', beforeMeridiemValue.length)\n                        .should('have.prop', 'selectionEnd', beforeMeridiemValue.length);\n                });\n\n                it('↓ --- 12:34 |PM', () => {\n                    cy.get('@textfield')\n                        .type('{downArrow}')\n                        .should('have.value', `${beforeMeridiemValue}PM`)\n                        .should('have.prop', 'selectionStart', beforeMeridiemValue.length)\n                        .should('have.prop', 'selectionEnd', beforeMeridiemValue.length);\n                });\n            });\n\n            describe('Initial value === \"<anyDate>12:34 AM\"', () => {\n                const beforeTimeValue = '01.01.2000, ';\n                const initialValue = `${beforeTimeValue}12:34 AM`;\n                const toggledValue = `${beforeTimeValue}12:34 PM`;\n\n                beforeEach(() => {\n                    cy.get('@textfield')\n                        .type('01.01.2000 1234a')\n                        .should('have.value', initialValue)\n                        .type('{moveToStart}');\n                });\n\n                [\n                    `${beforeTimeValue}12:34 `.length,\n                    `${beforeTimeValue}12:34 A`.length,\n                    `${beforeTimeValue}12:34 AM`.length,\n                ].forEach((initialCaretIndex) => {\n                    const initialValueWithCaretLabel = withCaretLabel(\n                        initialValue,\n                        initialCaretIndex,\n                    );\n                    const toggledValueWithCaretLabel = withCaretLabel(\n                        toggledValue,\n                        initialCaretIndex,\n                    );\n\n                    it(`${initialValueWithCaretLabel} --- ↑ --- ${toggledValueWithCaretLabel}`, () => {\n                        cy.get('@textfield')\n                            .type('{rightArrow}'.repeat(initialCaretIndex))\n                            .type('{upArrow}')\n                            .should('have.value', toggledValue)\n                            .should('have.prop', 'selectionStart', initialCaretIndex)\n                            .should('have.prop', 'selectionEnd', initialCaretIndex);\n                    });\n\n                    it(`${initialValueWithCaretLabel} --- ↓ --- ${toggledValueWithCaretLabel}`, () => {\n                        cy.get('@textfield')\n                            .type('{rightArrow}'.repeat(initialCaretIndex))\n                            .type('{downArrow}')\n                            .should('have.value', toggledValue)\n                            .should('have.prop', 'selectionStart', initialCaretIndex)\n                            .should('have.prop', 'selectionEnd', initialCaretIndex);\n                    });\n                });\n            });\n\n            describe('Initial value === \"01:01 PM\"', () => {\n                const beforeTimeValue = '01.01.2000, ';\n                const initialValue = `${beforeTimeValue}01:01 PM`;\n                const toggledValue = `${beforeTimeValue}01:01 AM`;\n\n                beforeEach(() => {\n                    cy.get('@textfield')\n                        .type('01.01.2000 0101p')\n                        .should('have.value', initialValue)\n                        .type('{moveToStart}');\n                });\n\n                [\n                    `${beforeTimeValue}01:01 `.length,\n                    `${beforeTimeValue}01:01 P`.length,\n                    `${beforeTimeValue}01:01 PM`.length,\n                ].forEach((initialCaretIndex) => {\n                    const initialValueWithCaretLabel = withCaretLabel(\n                        initialValue,\n                        initialCaretIndex,\n                    );\n                    const toggledValueWithCaretLabel = withCaretLabel(\n                        toggledValue,\n                        initialCaretIndex,\n                    );\n\n                    it(`${initialValueWithCaretLabel} --- ↑ --- ${toggledValueWithCaretLabel}`, () => {\n                        cy.get('@textfield')\n                            .type('{rightArrow}'.repeat(initialCaretIndex))\n                            .type('{upArrow}')\n                            .should('have.value', toggledValue)\n                            .should('have.prop', 'selectionStart', initialCaretIndex)\n                            .should('have.prop', 'selectionEnd', initialCaretIndex);\n                    });\n\n                    it(`${initialValueWithCaretLabel} --- ↓ --- ${toggledValueWithCaretLabel}`, () => {\n                        cy.get('@textfield')\n                            .type('{rightArrow}'.repeat(initialCaretIndex))\n                            .type('{downArrow}')\n                            .should('have.value', toggledValue)\n                            .should('have.prop', 'selectionStart', initialCaretIndex)\n                            .should('have.prop', 'selectionEnd', initialCaretIndex);\n                    });\n                });\n            });\n\n            describe('do nothing when caret is put after any time segment', () => {\n                it('Empty time part --- ↑↓ --- Empty time part', () => {\n                    cy.get('@textfield')\n                        .type('01.01.2000 ')\n                        .should('have.value', '01.01.2000, ')\n                        .type('{upArrow}')\n                        .should('have.value', '01.01.2000, ')\n                        .type('{downArrow}')\n                        .should('have.value', '01.01.2000, ');\n                });\n\n                ['1', '12', '12:', '12:3', '12:34'].forEach((textfieldValue) => {\n                    it(`${textfieldValue} --- ↑↓ --- ${textfieldValue}`, () => {\n                        cy.get('@textfield')\n                            .type(`01.01.2000 ${textfieldValue}`)\n                            .should('have.value', `01.01.2000, ${textfieldValue}`)\n                            .type('{upArrow}')\n                            .should('have.value', `01.01.2000, ${textfieldValue}`)\n                            .type('{downArrow}')\n                            .should('have.value', `01.01.2000, ${textfieldValue}`);\n                    });\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-time/date-time-min-max.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('DateTime | Min & Max dates', () => {\n    describe('Max', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateTime}/API?max=2020-05-05T12:20`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('accepts date less than max value', () => {\n            cy.get('@input')\n                .type('18.12.2019,23:50')\n                .should('have.value', '18.12.2019, 23:50')\n                .should('have.prop', 'selectionStart', '18.12.2019, 23:50'.length)\n                .should('have.prop', 'selectionEnd', '18.12.2019, 23:50'.length);\n        });\n\n        it('05.05.2020, 12:2| => Type 5 => 05.05.2020, 12:20 (max value)', () => {\n            cy.get('@input')\n                .type('05.05.2020,12:2')\n                .should('have.value', '05.05.2020, 12:2')\n                .should('have.prop', 'selectionStart', '05.05.2020, 12:2'.length)\n                .should('have.prop', 'selectionEnd', '05.05.2020, 12:2'.length)\n                .type('5')\n                .should('have.value', '05.05.2020, 12:20')\n                .should('have.prop', 'selectionStart', '05.05.2020, 12:20'.length)\n                .should('have.prop', 'selectionEnd', '05.05.2020, 12:20'.length);\n        });\n\n        it('18.12.20|19, 12:20 => Type 2 => 05.05.202|0, 12:20 (max value)', () => {\n            cy.get('@input')\n                .type('18.12.2019,12:20')\n                .type('{leftArrow}'.repeat('19, 12:20'.length))\n                .type('2')\n                .should('have.value', '05.05.2020, 12:20')\n                .should('have.prop', 'selectionStart', '05.05.202'.length)\n                .should('have.prop', 'selectionEnd', '05.05.202'.length);\n        });\n\n        describe('Correct value after date only input', () => {\n            it('06.06.202| => Type 5 => 05.05.2020| (max value)', () => {\n                cy.get('@input')\n                    .type('06.06.202')\n                    .should('have.value', '06.06.202')\n                    .should('have.prop', 'selectionStart', '06.06.202'.length)\n                    .should('have.prop', 'selectionEnd', '06.06.202'.length)\n                    .type('5')\n                    .should('have.value', '05.05.2020')\n                    .should('have.prop', 'selectionStart', '05.05.2020'.length)\n                    .should('have.prop', 'selectionEnd', '05.05.2020'.length);\n            });\n\n            it('0|1.05.2020 => Type 9 => 05|.05.2020 (max value)', () => {\n                cy.get('@input')\n                    .type('01052020')\n                    .type('{leftArrow}'.repeat('1.05.2020'.length))\n                    .type('9')\n                    .should('have.value', '05.05.2020')\n                    .should('have.prop', 'selectionStart', '05.'.length);\n            });\n        });\n    });\n\n    describe('Min', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateTime}/API?min=1995-10-14T15:32`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('accepts date more than min value', () => {\n            cy.get('@input')\n                .type('13.04.2001,11:23')\n                .should('have.value', '13.04.2001, 11:23')\n                .should('have.prop', 'selectionStart', '13.04.2001, 11:23'.length)\n                .should('have.prop', 'selectionEnd', '13.04.2001, 11:23'.length);\n        });\n\n        it('14.10.1995, 15:3| => Type 1 => 14.10.1995, 15:32 (min)', () => {\n            cy.get('@input')\n                .type('14.10.1995,15:3')\n                .should('have.value', '14.10.1995, 15:3')\n                .should('have.prop', 'selectionStart', '14.10.1995, 15:3'.length)\n                .should('have.prop', 'selectionEnd', '14.10.1995, 15:3'.length)\n                .type('1')\n                .should('have.value', '14.10.1995, 15:32')\n                .should('have.prop', 'selectionStart', '14.10.1995, 15:32'.length)\n                .should('have.prop', 'selectionEnd', '14.10.1995, 15:32'.length);\n        });\n\n        it('14.|10.1995, 10:20 => Type 9 => 14.10.|1995, 15:32 (min)', () => {\n            cy.get('@input')\n                .type('14.10.1995,10:20')\n                .type('{leftArrow}'.repeat('10.1995, 10:20'.length))\n                .type('9')\n                .should('have.value', '14.10.1995, 15:32')\n                .should('have.prop', 'selectionStart', '14.10.'.length)\n                .should('have.prop', 'selectionEnd', '14.10.'.length);\n        });\n\n        describe('Correct value after date only input', () => {\n            it('14.10.199 => Type 3 => 14.10.1995| (min)', () => {\n                cy.get('@input')\n                    .type('14.10.199')\n                    .should('have.value', '14.10.199')\n                    .should('have.prop', 'selectionStart', '14.10.199'.length)\n                    .should('have.prop', 'selectionEnd', '14.10.199'.length)\n                    .type('3')\n                    .should('have.value', '14.10.1995')\n                    .should('have.prop', 'selectionStart', '14.10.1995'.length)\n                    .should('have.prop', 'selectionEnd', '14.10.1995'.length);\n            });\n\n            it('14.10.199|5 => Type 3 => 14.10.1995| (min)', () => {\n                cy.get('@input')\n                    .type('14.10.1995')\n                    .type('{leftArrow}')\n                    .type('3')\n                    .should('have.value', '14.10.1995')\n                    .should('have.prop', 'selectionStart', '14.10.1995'.length);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-time/date-time-mode.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('DateTime | mode', () => {\n    describe('Date mode', () => {\n        describe('mm.dd.yyyy', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.DateTime}/API?dateMode=mm%2Fdd%2Fyyyy`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it('12.31.2000, 13:20', () => {\n                cy.get('@input')\n                    .type('123120001320')\n                    .should('have.value', '12.31.2000, 13:20')\n                    .should('have.prop', 'selectionStart', '12.31.2000, 13:20'.length)\n                    .should('have.prop', 'selectionEnd', '12.31.2000, 13:20'.length);\n            });\n\n            it('Empty input => Type 3 => 03|', () => {\n                cy.get('@input')\n                    .type('3')\n                    .should('have.value', '03')\n                    .should('have.prop', 'selectionStart', '03'.length)\n                    .should('have.prop', 'selectionEnd', '03'.length);\n            });\n\n            it('12| => Type 3 => 12.3|', () => {\n                cy.get('@input')\n                    .type('123')\n                    .should('have.value', '12.3')\n                    .should('have.prop', 'selectionStart', '12.3'.length)\n                    .should('have.prop', 'selectionEnd', '12.3'.length);\n            });\n\n            it('12| => Type 4 => 12.04|', () => {\n                cy.get('@input')\n                    .type('124')\n                    .should('have.value', '12.04')\n                    .should('have.prop', 'selectionStart', '12.04'.length)\n                    .should('have.prop', 'selectionEnd', '12.04'.length);\n            });\n        });\n\n        describe('yyyy.mm.dd', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.DateTime}/API?dateMode=yyyy%2Fmm%2Fdd`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it('2000.12.31, 12:20', () => {\n                cy.get('@input')\n                    .type('20001231,1220')\n                    .should('have.value', '2000.12.31, 12:20')\n                    .should('have.prop', 'selectionStart', '2000.12.31, 12:20'.length)\n                    .should('have.prop', 'selectionEnd', '2000.12.31, 12:20'.length);\n            });\n\n            it('2000| => Type 3 => 2000.03|', () => {\n                cy.get('@input')\n                    .type('20003')\n                    .should('have.value', '2000.03')\n                    .should('have.prop', 'selectionStart', '2000.03'.length)\n                    .should('have.prop', 'selectionEnd', '2000.03'.length);\n            });\n\n            it('2000.03| => Type 5 => 2000.03.05|', () => {\n                cy.get('@input')\n                    .type('200035')\n                    .should('have.value', '2000.03.05')\n                    .should('have.prop', 'selectionStart', '2000.03.05'.length)\n                    .should('have.prop', 'selectionEnd', '2000.03.05'.length);\n            });\n        });\n    });\n\n    describe('Time', () => {\n        describe('HH:MM', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.DateTime}/API?timeMode=HH:MM`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it('12.01.2000, 13:20 => type 12 => no value changes', () => {\n                cy.get('@input')\n                    .type('120120001320')\n                    .should('have.value', '12.01.2000, 13:20')\n                    .should('have.prop', 'selectionStart', '12.01.2000, 13:20'.length)\n                    .should('have.prop', 'selectionEnd', '12.01.2000, 13:20'.length)\n                    .type('12')\n                    .should('have.value', '12.01.2000, 13:20')\n                    .should('have.prop', 'selectionStart', '12.01.2000, 13:20'.length)\n                    .should('have.prop', 'selectionEnd', '12.01.2000, 13:20'.length);\n            });\n        });\n\n        describe('HH:MM:SS', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.DateTime}/API?timeMode=HH:MM:SS`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it('12.01.2000, 13:20:30 => type 12 => no value changes', () => {\n                cy.get('@input')\n                    .type('12012000132030')\n                    .should('have.value', '12.01.2000, 13:20:30')\n                    .should('have.prop', 'selectionStart', '12.01.2000, 13:20:30'.length)\n                    .should('have.prop', 'selectionEnd', '12.01.2000, 13:20:30'.length)\n                    .type('12')\n                    .should('have.value', '12.01.2000, 13:20:30')\n                    .should('have.prop', 'selectionStart', '12.01.2000, 13:20:30'.length)\n                    .should('have.prop', 'selectionEnd', '12.01.2000, 13:20:30'.length);\n            });\n        });\n\n        describe('HH:MM:SS.MSS', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.DateTime}/API?timeMode=HH:MM:SS.MSS`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n            });\n\n            it('12.01.2000, 13:20:30.123', () => {\n                cy.get('@input')\n                    .type('12012000132030123')\n                    .should('have.value', '12.01.2000, 13:20:30.123')\n                    .should(\n                        'have.prop',\n                        'selectionStart',\n                        '12.01.2000, 13:20:30.123'.length,\n                    )\n                    .should(\n                        'have.prop',\n                        'selectionEnd',\n                        '12.01.2000, 13:20:30.123'.length,\n                    );\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-time/date-time-separator.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('DateTime | Separator', () => {\n    describe('/', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateTime}/API?dateSeparator=/`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('14/12/1997', () => {\n            cy.get('@input').type('14121997').should('have.value', '14/12/1997');\n        });\n\n        it('rejects dot as separator', () => {\n            cy.get('@input')\n                .type('1412')\n                .should('have.value', '14/12')\n                .type('2000')\n                .should('have.value', '14/12/2000');\n        });\n\n        it('accepts date segment separators typed by user', () => {\n            cy.get('@input')\n                .type('24')\n                .should('have.value', '24')\n                .type('/')\n                .should('have.value', '24/')\n                .should('have.prop', 'selectionStart', '24/'.length)\n                .should('have.prop', 'selectionEnd', '24/'.length)\n                .type('05/')\n                .should('have.value', '24/05/')\n                .should('have.prop', 'selectionStart', '24/05/'.length)\n                .should('have.prop', 'selectionEnd', '24/05/'.length);\n        });\n    });\n\n    describe('-', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.DateTime}/API?dateSeparator=-`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        it('14-12-1997', () => {\n            cy.get('@input').type('14121997').should('have.value', '14-12-1997');\n        });\n\n        it('rejects dot as separator', () => {\n            cy.get('@input')\n                .type('14')\n                .should('have.value', '14')\n                .type('12')\n                .should('have.value', '14-12');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/date-time/date-time-time-step.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from 'projects/demo-integrations/src/support/constants';\n\ndescribe('DateTime | timeStep', () => {\n    describe('yy/mm;HH:MM:SS.MSS', () => {\n        describe('timeStep = 1, initial state = 22.12;', () => {\n            beforeEach(() => {\n                cy.visit(\n                    `/${DemoPath.DateTime}/API?dateTimeSeparator=;&dateMode=yy%2Fmm&timeStep=1&timeMode=HH:MM:SS.MSS`,\n                );\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n\n                cy.get('@input')\n                    .type('2212;')\n                    .should('have.value', '22.12;')\n                    .should('have.a.prop', 'selectionStart', '22.12;'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;'.length);\n            });\n\n            it('decrements hours segment by pressing ArrowDown at different places of the segment', () => {\n                cy.get('@input')\n                    .type('{downArrow}')\n                    .should('have.value', '22.12;23')\n                    .should('have.a.prop', 'selectionStart', '22.12;'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;'.length)\n                    .type('{rightArrow}{downArrow}')\n                    .should('have.value', '22.12;22')\n                    .should('have.a.prop', 'selectionStart', '22.12;2'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;2'.length)\n                    .type('{rightArrow}')\n                    .type('{upArrow}'.repeat(12))\n                    .should('have.value', '22.12;10')\n                    .should('have.a.prop', 'selectionStart', '22.12;10'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;10'.length);\n            });\n\n            it('increments/decrements minutes segment by pressing keyboard arrows at different places of the segment', () => {\n                cy.get('@input')\n                    .type('12:')\n                    .should('have.value', '22.12;12:')\n                    .should('have.a.prop', 'selectionStart', '22.12;12:'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;12:'.length)\n                    .type('{upArrow}')\n                    .should('have.value', '22.12;12:01')\n                    .should('have.a.prop', 'selectionStart', '22.12;12:'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;12:'.length)\n                    .type('{rightArrow}')\n                    .type('{upArrow}'.repeat(9))\n                    .should('have.value', '22.12;12:10')\n                    .should('have.a.prop', 'selectionStart', '22.12;12:1'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;12:1'.length)\n                    .type('{rightArrow}')\n                    .type('{downArrow}'.repeat(34))\n                    .should('have.value', '22.12;12:36')\n                    .should('have.a.prop', 'selectionStart', '22.12;12:36'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;12:36'.length);\n            });\n\n            it('changes seconds segment by pressing keyboard arrow up/down', () => {\n                cy.get('@input')\n                    .type('12:10:')\n                    .should('have.value', '22.12;12:10:')\n                    .should('have.a.prop', 'selectionStart', '22.12;12:10:'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;12:10:'.length)\n                    .type('{downArrow}'.repeat(6))\n                    .should('have.value', '22.12;12:10:54')\n                    .should('have.a.prop', 'selectionStart', '22.12;12:10:'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;12:10:'.length)\n                    .type('{rightArrow}{upArrow}'.repeat(2))\n                    .should('have.value', '22.12;12:10:56')\n                    .should('have.a.prop', 'selectionStart', '22.12;12:10:56'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;12:10:56'.length);\n            });\n\n            it('changes milliseconds segment by pressing keyboard arrow up/down', () => {\n                cy.get('@input')\n                    .type('213212.')\n                    .should('have.value', '22.12;21:32:12.')\n                    .should('have.a.prop', 'selectionStart', '22.12;21:32:12.'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;21:32:12.'.length)\n                    .type('{upArrow}{rightArrow}'.repeat(3))\n                    .type('{downArrow}')\n                    .should('have.value', '22.12;21:32:12.002')\n                    .should('have.a.prop', 'selectionStart', '22.12;21:32:12.002'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;21:32:12.002'.length);\n            });\n\n            it('type 213212. => 22.12;21:32:12.| => type ({downArrow}{rightArrow}) * 3 + {downArrow} => 22.12:21:32:12.995|', () => {\n                cy.get('@input')\n                    .type('213212.')\n                    .should('have.value', '22.12;21:32:12.')\n                    .should('have.a.prop', 'selectionStart', '22.12;21:32:12.'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;21:32:12.'.length)\n                    .type('{downArrow}{rightArrow}'.repeat(3))\n                    .type('{downArrow}')\n                    .should('have.value', '22.12;21:32:12.996')\n                    .should('have.a.prop', 'selectionStart', '22.12;21:32:12.996'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;21:32:12.996'.length);\n            });\n\n            it('should affect only time segments', () => {\n                cy.get('@input')\n                    .type('123456111')\n                    .should('have.value', '22.12;12:34:56.111')\n                    .should('have.a.prop', 'selectionStart', '22.12;12:34:56.111'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;12:34:56.111'.length)\n                    .type('{upArrow}{leftArrow}'.repeat('22.12;12:34:56.111'.length))\n                    .should('have.value', '22.12;15:37:59.115')\n                    .should('have.a.prop', 'selectionStart', 0)\n                    .should('have.a.prop', 'selectionEnd', 0);\n            });\n        });\n\n        describe('timeStep = 0 (disabled time stepping)', () => {\n            beforeEach(() => {\n                cy.visit(\n                    `/${DemoPath.DateTime}/API?dateTimeSeparator=;&dateMode=yy%2Fmm&timeStep=0&timeMode=HH:MM:SS.MSS`,\n                );\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .as('input');\n\n                cy.get('@input')\n                    .type('1202123456000')\n                    .should('have.value', '12.02;12:34:56.000')\n                    .should('have.a.prop', 'selectionStart', '12.02;12:34:56.000'.length)\n                    .should('have.a.prop', 'selectionEnd', '12.02;12:34:56.000'.length);\n            });\n\n            it('should be disabled', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                cy.get('@input').realPress('ArrowUp');\n\n                cy.get('@input')\n                    .should('have.a.prop', 'selectionStart', 0)\n                    .should('have.a.prop', 'selectionEnd', 0)\n                    .realPress('ArrowDown');\n\n                cy.get('@input')\n                    .should('have.a.prop', 'selectionStart', '12.02;12:34:56.000'.length)\n                    .should('have.a.prop', 'selectionEnd', '12.02;12:34:56.000'.length);\n            });\n        });\n    });\n    describe('yy/mm;HH:MM AA', () => {\n        describe('timeStep = 1, initial state = 22/12;', () => {\n            beforeEach(() => {\n                cy.visit(\n                    `/${DemoPath.DateTime}/API?dateTimeSeparator=;&dateMode=yy%2Fmm&timeStep=1&timeMode=HH:MM%20AA`,\n                );\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .clear()\n                    .as('input');\n\n                cy.get('@input')\n                    .type('2212;')\n                    .should('have.value', '22.12;')\n                    .should('have.a.prop', 'selectionStart', '22.12;'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;'.length);\n            });\n\n            it('wraps hours correctly when pressing up at hour 12', () => {\n                cy.get('@input')\n                    .type('1234p')\n                    .should('have.value', '22.12;12:34 PM')\n                    .should('have.a.prop', 'selectionStart', '22.12;12:34 PM'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;12:34 PM'.length)\n                    .type('{moveToStart}')\n                    .type('{rightArrow}'.repeat('22.12;'.length))\n                    .type('{upArrow}'.repeat(2))\n                    .should('have.a.prop', 'selectionStart', '22.12;'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;'.length)\n                    .should('have.value', '22.12;02:34 PM')\n                    .type('{downArrow}'.repeat(4))\n                    .should('have.a.prop', 'selectionStart', '22.12;'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;'.length)\n                    .should('have.value', '22.12;10:34 PM');\n            });\n\n            it('increments and decrements minutes in AM/PM mode correctly', () => {\n                cy.get('@input')\n                    .type('1234a')\n                    .should('have.value', '22.12;12:34 AM')\n                    .should('have.a.prop', 'selectionStart', '22.12;12:34 AM'.length)\n                    .should('have.a.prop', 'selectionEnd', '22.12;12:34 AM'.length)\n                    .type('{leftArrow}'.repeat(3))\n                    .type('{upArrow}'.repeat(2))\n                    .should('have.value', '22.12;12:36 AM')\n                    .type('{downArrow}'.repeat(3))\n                    .should('have.value', '22.12;12:33 AM');\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-basic.cy.ts",
    "content": "import {openNumberPage} from './utils';\n\ndescribe('Number | Basic', () => {\n    beforeEach(() => {\n        openNumberPage('thousandSeparator=_&maximumFractionDigits=2');\n    });\n\n    describe('Invalid characters', () => {\n        it('rejects redundant spaces', () => {\n            cy.get('@input')\n                .type('1 2  3   4    5')\n                .should('have.value', '12_345')\n                .should('have.prop', 'selectionStart', '12_345'.length)\n                .should('have.prop', 'selectionEnd', '12_345'.length);\n        });\n\n        it('rejects lowercase latin letters', () => {\n            cy.get('@input')\n                .type('123abcdefghijklmnopqrstuvwxyz456')\n                .should('have.value', '123_456')\n                .should('have.prop', 'selectionStart', '123_456'.length)\n                .should('have.prop', 'selectionEnd', '123_456'.length);\n        });\n\n        it('rejects uppercase latin letters', () => {\n            cy.get('@input')\n                .type('123ABCDEFGHIJKLMNOPQRSTUVWXYZ456')\n                .should('have.value', '123_456')\n                .should('have.prop', 'selectionStart', '123_456'.length)\n                .should('have.prop', 'selectionEnd', '123_456'.length);\n        });\n\n        it('rejects lowercase cyrillic letters', () => {\n            cy.get('@input')\n                .type('123авгдеёжзийклмнопрстуфхцчшщъыьэя456') // without \"б\" and \"ю\"\n                .should('have.value', '123_456')\n                .should('have.prop', 'selectionStart', '123_456'.length)\n                .should('have.prop', 'selectionEnd', '123_456'.length);\n        });\n\n        it('rejects uppercase cyrillic letters', () => {\n            cy.get('@input')\n                .type('123АВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЯ456')\n                .should('have.value', '123_456')\n                .should('have.prop', 'selectionStart', '123_456'.length)\n                .should('have.prop', 'selectionEnd', '123_456'.length);\n        });\n\n        it('rejects punctuation', () => {\n            cy.get('@input')\n                .type('123!\"№%:;()456') // without dot and comma\n                .should('have.value', '123_456')\n                .should('have.prop', 'selectionStart', '123_456'.length)\n                .should('have.prop', 'selectionEnd', '123_456'.length);\n        });\n    });\n\n    describe('minus sign', () => {\n        it('can type minus sign', () => {\n            cy.get('@input')\n                .type('−')\n                .should('have.value', '−')\n                .should('have.prop', 'selectionStart', '−'.length)\n                .should('have.prop', 'selectionEnd', '−'.length);\n        });\n\n        it('replaces hyphen with minus sign', () => {\n            cy.get('@input')\n                .type('-')\n                .should('have.value', '−')\n                .should('have.prop', 'selectionStart', '−'.length)\n                .should('have.prop', 'selectionEnd', '−'.length);\n        });\n\n        it('replaces en-dash with minus sign', () => {\n            cy.get('@input')\n                .type('–')\n                .should('have.value', '−')\n                .should('have.prop', 'selectionStart', '−'.length)\n                .should('have.prop', 'selectionEnd', '−'.length);\n        });\n\n        it('replaces em-dash with minus sign', () => {\n            cy.get('@input')\n                .type('—')\n                .should('have.value', '−')\n                .should('have.prop', 'selectionStart', '−'.length)\n                .should('have.prop', 'selectionEnd', '−'.length);\n        });\n\n        it('can type \"−111\" (minus)', () => {\n            cy.get('@input')\n                .type('−111')\n                .should('have.value', '−111')\n                .should('have.prop', 'selectionStart', '−111'.length)\n                .should('have.prop', 'selectionEnd', '−111'.length);\n        });\n\n        it('can type \"-3333\" (hyphen)', () => {\n            cy.get('@input')\n                .type('-3333')\n                .should('have.value', '−3_333')\n                .should('have.prop', 'selectionStart', '−3_333'.length)\n                .should('have.prop', 'selectionEnd', '−3_333'.length);\n        });\n\n        it('can type \"–123.45\" (en-dash)', () => {\n            cy.get('@input')\n                .type('–123.45')\n                .should('have.value', '−123.45')\n                .should('have.prop', 'selectionStart', '−123.45'.length)\n                .should('have.prop', 'selectionEnd', '−123.45'.length);\n        });\n\n        it('can type \"—0,12\" (em-dash)', () => {\n            cy.get('@input')\n                .type('—0,12')\n                .should('have.value', '−0.12')\n                .should('have.prop', 'selectionStart', '−0.12'.length)\n                .should('have.prop', 'selectionEnd', '−0.12'.length);\n        });\n    });\n\n    it('\"Backspace\"-key does nothing when cursor at the START of element', () => {\n        cy.get('@input')\n            .type('111')\n            .type('{moveToStart}')\n            .type('{backspace}')\n            .should('have.value', '111')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 0)\n            .type('{backspace}'.repeat(4))\n            .should('have.value', '111')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 0);\n    });\n\n    it('\"Delete\"-key does nothing when cursor at the END of element', () => {\n        cy.get('@input')\n            .type('111')\n            .type('{moveToEnd}')\n            .type('{del}')\n            .should('have.value', '111')\n            .should('have.prop', 'selectionStart', '111'.length)\n            .should('have.prop', 'selectionEnd', '111'.length)\n            .type('{del}'.repeat(4))\n            .should('have.value', '111')\n            .should('have.prop', 'selectionStart', '111'.length)\n            .should('have.prop', 'selectionEnd', '111'.length);\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-bigint.cy.ts",
    "content": "import {CHAR_MINUS} from 'projects/kit/src/lib/constants';\n\nimport {openNumberPage} from './utils';\n\ndescribe('Number | BigInt', () => {\n    describe('huge integers', () => {\n        beforeEach(() => {\n            openNumberPage();\n        });\n\n        const expectedValue =\n            '900 719 925 474 099 190 071 992 547 409 919 007 199 254 740 991';\n\n        it('types String(Number.MAX_SAFE_INTEGER).repeat(3)', () => {\n            cy.get('@input')\n                .type(String(Number.MAX_SAFE_INTEGER).repeat(3))\n                .should('have.value', expectedValue)\n                .should('have.prop', 'selectionStart', expectedValue.length)\n                .should('have.prop', 'selectionEnd', expectedValue.length);\n        });\n\n        it('types minus + String(Number.MAX_SAFE_INTEGER).repeat(3)', () => {\n            cy.get('@input')\n                .type(`-${String(Number.MAX_SAFE_INTEGER).repeat(3)}`)\n                .should('have.value', `${CHAR_MINUS}${expectedValue}`)\n                .should('have.prop', 'selectionStart', 1 + expectedValue.length)\n                .should('have.prop', 'selectionEnd', 1 + expectedValue.length);\n        });\n    });\n\n    describe('huge decimals', () => {\n        beforeEach(() => {\n            openNumberPage('maximumFractionDigits=Infinity&thousandSeparator=_');\n        });\n\n        it('types String(Number.MAX_SAFE_INTEGER).repeat(2) + \".123456789\"', () => {\n            const expectedValue = '90_071_992_547_409_919_007_199_254_740_991.123456789';\n\n            cy.get('@input')\n                .type(`${String(Number.MAX_SAFE_INTEGER).repeat(2)},123456789`)\n                .should('have.value', expectedValue)\n                .should('have.prop', 'selectionStart', expectedValue.length)\n                .should('have.prop', 'selectionEnd', expectedValue.length);\n        });\n\n        it('types minus + String(Number.MAX_SAFE_INTEGER).repeat(2) + \".123456789\"', () => {\n            const expectedValue = `${CHAR_MINUS}90_071_992_547_409_919_007_199_254_740_991.123456789`;\n\n            cy.get('@input')\n                .type(`-${String(Number.MAX_SAFE_INTEGER).repeat(2)}.123456789`)\n                .should('have.value', expectedValue)\n                .should('have.prop', 'selectionStart', expectedValue.length)\n                .should('have.prop', 'selectionEnd', expectedValue.length);\n        });\n\n        it('types pseudo decimal separator + decimal part String(Number.MAX_SAFE_INTEGER).repeat(3)', () => {\n            const expectedValue = '0.900719925474099190071992547409919007199254740991';\n\n            cy.get('@input')\n                .type(`,${String(Number.MAX_SAFE_INTEGER).repeat(3)}`)\n                .should('have.value', expectedValue)\n                .should('have.prop', 'selectionStart', expectedValue.length)\n                .should('have.prop', 'selectionEnd', expectedValue.length);\n        });\n    });\n\n    describe('affixes', () => {\n        it('types String(Number.MAX_SAFE_INTEGER).repeat(3) with prefix=$', () => {\n            openNumberPage('prefix=$&thousandSeparator=.');\n\n            const expectedValue =\n                '$900.719.925.474.099.190.071.992.547.409.919.007.199.254.740.991';\n\n            cy.get('@input')\n                .type(String(Number.MAX_SAFE_INTEGER).repeat(3))\n                .should('have.value', expectedValue)\n                .should('have.prop', 'selectionStart', expectedValue.length)\n                .should('have.prop', 'selectionEnd', expectedValue.length);\n        });\n\n        it('types String(Number.MAX_SAFE_INTEGER).repeat(3) with postfix=%', () => {\n            openNumberPage(`postfix=${encodeURIComponent('%')}&thousandSeparator=,`);\n\n            const expectedValue = `${CHAR_MINUS}900,719,925,474,099,190,071,992,547,409,919,007,199,254,740,991%`;\n\n            cy.get('@input')\n                .type(`${CHAR_MINUS}${String(Number.MAX_SAFE_INTEGER).repeat(3)}`)\n                .should('have.value', expectedValue)\n                .should('have.prop', 'selectionStart', expectedValue.length - 1)\n                .should('have.prop', 'selectionEnd', expectedValue.length - 1);\n        });\n\n        it('types hyphen + String(Number.MAX_SAFE_INTEGER).repeat(3) when prefix=\"$\" & negativePattern=\"minusFirst\"', () => {\n            openNumberPage('prefix=$&negativePattern=minusFirst&&thousandSeparator=_');\n\n            const expectedValue = `${CHAR_MINUS}$900_719_925_474_099_190_071_992_547_409_919_007_199_254_740_991`;\n\n            cy.get('@input')\n                .type(`-${String(Number.MAX_SAFE_INTEGER).repeat(3)}`)\n                .should('have.value', expectedValue)\n                .should('have.prop', 'selectionStart', expectedValue.length)\n                .should('have.prop', 'selectionEnd', expectedValue.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-decimal-separator.cy.ts",
    "content": "import {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\nimport {repeatKey} from '../../utils';\nimport {openNumberPage} from './utils';\n\ndescribe('Number | Decimal separator (symbol used to separate the integer part from the fractional part)', () => {\n    describe('Decimal separator is a comma (default)', () => {\n        beforeEach(() => {\n            openNumberPage(\n                'decimalSeparator=,&thousandSeparator=_&maximumFractionDigits=2',\n            );\n        });\n\n        it('accepts comma (as the last character)', () => {\n            cy.get('@input')\n                .type('123,')\n                .should('have.value', '123,')\n                .should('have.prop', 'selectionStart', '123,'.length)\n                .should('have.prop', 'selectionEnd', '123,'.length);\n        });\n\n        it('accepts comma (in the middle)', () => {\n            cy.get('@input')\n                .type('42')\n                .type('{leftArrow}')\n                .type(',')\n                .should('have.value', '4,2')\n                .should('have.prop', 'selectionStart', '4,'.length)\n                .should('have.prop', 'selectionEnd', '4,'.length);\n        });\n\n        it('accepts dot (as the last character) and transforms it to comma', () => {\n            cy.get('@input')\n                .type('123.')\n                .should('have.value', '123,')\n                .should('have.prop', 'selectionStart', '123,'.length)\n                .should('have.prop', 'selectionEnd', '123,'.length);\n        });\n\n        it('accepts dot (in the middle) and transforms it to comma', () => {\n            cy.get('@input')\n                .type('42')\n                .type('{leftArrow}')\n                .type('.')\n                .should('have.value', '4,2')\n                .should('have.prop', 'selectionStart', '4,'.length)\n                .should('have.prop', 'selectionEnd', '4,'.length);\n        });\n\n        it('accepts \"б\" (as the last character) and transforms it to comma', () => {\n            cy.get('@input')\n                .type('123б')\n                .should('have.value', '123,')\n                .should('have.prop', 'selectionStart', '123,'.length)\n                .should('have.prop', 'selectionEnd', '123,'.length);\n        });\n\n        it('accepts \"б\" (in the middle) and transforms it to comma', () => {\n            cy.get('@input')\n                .type('42')\n                .type('{leftArrow}')\n                .type('б')\n                .should('have.value', '4,2')\n                .should('have.prop', 'selectionStart', '4,'.length)\n                .should('have.prop', 'selectionEnd', '4,'.length);\n        });\n\n        it('accepts \"Ю\" (as the last character) and transforms it to comma', () => {\n            cy.get('@input')\n                .type('123Ю')\n                .should('have.value', '123,')\n                .should('have.prop', 'selectionStart', '123,'.length)\n                .should('have.prop', 'selectionEnd', '123,'.length);\n        });\n\n        it('accepts \"Ю\" (in the middle) and transforms it to comma', () => {\n            cy.get('@input')\n                .type('42')\n                .type('{leftArrow}')\n                .type('Ю')\n                .should('have.value', '4,2')\n                .should('have.prop', 'selectionStart', '4,'.length)\n                .should('have.prop', 'selectionEnd', '4,'.length);\n        });\n    });\n\n    describe('Decimal separator is a dot', () => {\n        beforeEach(() => {\n            openNumberPage(\n                'decimalSeparator=.&maximumFractionDigits=2&decimalPseudoSeparators$=2',\n            );\n        });\n\n        it('accepts dot (as the last character)', () => {\n            cy.get('@input')\n                .type('123.')\n                .should('have.value', '123.')\n                .should('have.prop', 'selectionStart', '123.'.length)\n                .should('have.prop', 'selectionEnd', '123.'.length);\n        });\n\n        it('accepts dot (in the middle)', () => {\n            cy.get('@input')\n                .type('42')\n                .type('{leftArrow}')\n                .type('.')\n                .should('have.value', '4.2')\n                .should('have.prop', 'selectionStart', '4.'.length)\n                .should('have.prop', 'selectionEnd', '4.'.length);\n        });\n\n        it('accepts comma (as the last character) and transforms it to dot', () => {\n            cy.get('@input')\n                .type('123,')\n                .should('have.value', '123.')\n                .should('have.prop', 'selectionStart', '123.'.length)\n                .should('have.prop', 'selectionEnd', '123.'.length);\n        });\n\n        it('accepts comma (in the middle) and transforms it to dot', () => {\n            cy.get('@input')\n                .type('42')\n                .type('{leftArrow}')\n                .type(',')\n                .should('have.value', '4.2')\n                .should('have.prop', 'selectionStart', '4.'.length)\n                .should('have.prop', 'selectionEnd', '4.'.length);\n        });\n\n        it('rejects invalid characters', () => {\n            cy.get('@input')\n                .type('123')\n                .type('{moveToStart}{rightArrow}')\n                .type('F')\n                .should('have.value', '123');\n        });\n    });\n\n    describe('Attempt to enter decimal separator when it already exists in text field', () => {\n        beforeEach(() => {\n            openNumberPage(\n                'decimalSeparator=,&thousandSeparator=_&maximumFractionDigits=2',\n            );\n        });\n\n        it('1|23,45 => Press comma (decimal separator) => 1|23,45 (no changes)', () => {\n            cy.get('@input')\n                .type('123,45')\n                .type('{moveToStart}{rightArrow}')\n                .type(',')\n                .should('have.value', '123,45')\n                .should('have.prop', 'selectionStart', '1'.length)\n                .should('have.prop', 'selectionEnd', '1'.length);\n        });\n\n        it('1|23,45 => Press point (pseudo decimal separator) => 1|23,45 (no changes)', () => {\n            cy.get('@input')\n                .type('123.45')\n                .type('{moveToStart}{rightArrow}')\n                .type('.')\n                .should('have.value', '123,45')\n                .should('have.prop', 'selectionStart', '1'.length)\n                .should('have.prop', 'selectionEnd', '1'.length);\n        });\n\n        it(\n            '1|23,4|5 => Type decimal separator => 1,5',\n            BROWSER_SUPPORTS_REAL_EVENTS,\n            () => {\n                cy.get('@input')\n                    .type('123,45')\n                    .realPress([\n                        'ArrowLeft',\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '23,4'.length),\n                    ]);\n\n                cy.get('@input')\n                    .type(',')\n                    .should('have.value', '1,5')\n                    .should('have.prop', 'selectionStart', '1,'.length)\n                    .should('have.prop', 'selectionEnd', '1,'.length);\n            },\n        );\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-decimal-zero-padding.cy.ts",
    "content": "import {openNumberPage} from './utils';\n\ndescribe('Number | decimalZeroPadding', () => {\n    describe('[minimumFractionDigits] === [maximumFractionDigits] === 4', () => {\n        beforeEach(() => {\n            openNumberPage(\n                'decimalSeparator=,&maximumFractionDigits=4&minimumFractionDigits=4',\n            );\n        });\n\n        it('Type 42 => 42,0000', () => {\n            cy.get('@input')\n                .type('42')\n                .should('have.value', '42,0000')\n                .should('have.prop', 'selectionStart', '42'.length)\n                .should('have.prop', 'selectionEnd', '42'.length);\n        });\n\n        it('Type , => 0,0000', () => {\n            cy.get('@input')\n                .type(',')\n                .should('have.value', '0,0000')\n                .should('have.prop', 'selectionStart', '0,'.length)\n                .should('have.prop', 'selectionEnd', '0,'.length);\n        });\n\n        it('Type 42,27 => 42,2700', () => {\n            cy.get('@input')\n                .type('42,27')\n                .should('have.value', '42,2700')\n                .should('have.prop', 'selectionStart', '42,27'.length)\n                .should('have.prop', 'selectionEnd', '42,27'.length);\n        });\n\n        it('Integer part has `overwriteMode: shift`', () => {\n            cy.get('@input')\n                .type('42,27')\n                .type('{leftArrow}'.repeat('2,27'.length))\n                .type('55')\n                .should('have.value', '4 552,2700')\n                .should('have.prop', 'selectionStart', '4 55'.length)\n                .should('have.prop', 'selectionEnd', '4 55'.length);\n        });\n\n        it('Decimal part has `overwriteMode: replace`', () => {\n            cy.get('@input')\n                .type('42,27')\n                .type('{leftArrow}'.repeat('27'.length))\n                .type('55')\n                .should('have.value', '42,5500')\n                .should('have.prop', 'selectionStart', '42,55'.length)\n                .should('have.prop', 'selectionEnd', '42,55'.length);\n        });\n\n        it('42,|2700 => Backspace => 42|,2700', () => {\n            cy.get('@input')\n                .type('42,27')\n                .type('{leftArrow}'.repeat('27'.length))\n                .type('{backspace}')\n                .should('have.value', '42,2700')\n                .should('have.prop', 'selectionStart', '42'.length)\n                .should('have.prop', 'selectionEnd', '42'.length);\n        });\n\n        it('42|,2700 => Delete => 42,|2700', () => {\n            cy.get('@input')\n                .type('42,27')\n                .type('{leftArrow}'.repeat(',27'.length))\n                .type('{del}')\n                .should('have.value', '42,2700')\n                .should('have.prop', 'selectionStart', '42,'.length)\n                .should('have.prop', 'selectionEnd', '42,'.length);\n        });\n\n        it('0|,4242 => Backspace => |,4242 => ,4242| => Backspace x4 => ,|0000', () => {\n            cy.get('@input')\n                .type('0,4242')\n                .type('{moveToStart}{rightArrow}')\n                .type('{backspace}')\n                .should('have.value', ',4242')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0)\n                .type('{moveToEnd}')\n                .type('{backspace}'.repeat(2))\n                .should('have.value', ',4200')\n                .should('have.prop', 'selectionStart', ',42'.length)\n                .should('have.prop', 'selectionEnd', ',42'.length)\n                .type('{backspace}'.repeat(2))\n                .should('have.value', ',0000')\n                .should('have.prop', 'selectionStart', ','.length)\n                .should('have.prop', 'selectionEnd', ','.length);\n        });\n\n        describe('Extra decimal separator insertion', () => {\n            it('42,|2700 => Type , => 42,|2700', () => {\n                cy.get('@input')\n                    .type('42,27')\n                    .type('{leftArrow}'.repeat('27'.length))\n                    .should('have.value', '42,2700')\n                    .should('have.prop', 'selectionStart', '42,'.length)\n                    .should('have.prop', 'selectionEnd', '42,'.length)\n                    .type(',')\n                    .should('have.value', '42,2700')\n                    .should('have.prop', 'selectionStart', '42,'.length)\n                    .should('have.prop', 'selectionEnd', '42,'.length);\n            });\n\n            it('42|,2700 => Type , => 42,|2700', () => {\n                cy.get('@input')\n                    .type('42,27')\n                    .type('{leftArrow}'.repeat(',27'.length))\n                    .should('have.value', '42,2700')\n                    .should('have.prop', 'selectionStart', '42'.length)\n                    .should('have.prop', 'selectionEnd', '42'.length)\n                    .type(',')\n                    .should('have.value', '42,2700')\n                    .should('have.prop', 'selectionStart', '42,'.length)\n                    .should('have.prop', 'selectionEnd', '42,'.length);\n            });\n\n            it('42,2|700 => Type , => 42,2|700', () => {\n                cy.get('@input')\n                    .type('42,27')\n                    .type('{leftArrow}')\n                    .should('have.value', '42,2700')\n                    .should('have.prop', 'selectionStart', '42,2'.length)\n                    .should('have.prop', 'selectionEnd', '42,2'.length)\n                    .type(',')\n                    .should('have.value', '42,2700')\n                    .should('have.prop', 'selectionStart', '42,2'.length)\n                    .should('have.prop', 'selectionEnd', '42,2'.length);\n            });\n\n            it('9|9,1234 => Type , => 9|9,1234 (no changes)', () => {\n                cy.get('@input')\n                    .type('99,1234')\n                    .type('{moveToStart}{rightArrow}')\n                    .should('have.value', '99,1234')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1)\n                    .type(',')\n                    .should('have.value', '99,1234')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n        });\n\n        describe('Move caret when user tries to delete non-removable zeroes in decimal part', () => {\n            beforeEach(() => {\n                cy.get('@input').type(',').should('have.value', '0,0000');\n            });\n\n            describe('Via `Backspace` button', () => {\n                it('0,0000| => Backspace => 0,000|0', () => {\n                    cy.get('@input')\n                        .type('{moveToEnd}{backspace}')\n                        .should('have.value', '0,0000')\n                        .should('have.prop', 'selectionStart', '0,000'.length)\n                        .should('have.prop', 'selectionEnd', '0,000'.length);\n                });\n\n                it('0,000|0 => Backspace => 0,00|00', () => {\n                    cy.get('@input')\n                        .type('{moveToEnd}{leftArrow}{backspace}')\n                        .should('have.value', '0,0000')\n                        .should('have.prop', 'selectionStart', '0,00'.length)\n                        .should('have.prop', 'selectionEnd', '0,00'.length);\n                });\n\n                it('0,00|00 => Backspace => 0,0|000', () => {\n                    cy.get('@input')\n                        .type('{moveToEnd}')\n                        .type('{leftArrow}'.repeat(2))\n                        .type('{backspace}')\n                        .should('have.value', '0,0000')\n                        .should('have.prop', 'selectionStart', '0,0'.length)\n                        .should('have.prop', 'selectionEnd', '0,0'.length);\n                });\n\n                it('0,0|000 => Backspace => 0,|0000', () => {\n                    cy.get('@input')\n                        .type('{moveToEnd}')\n                        .type('{leftArrow}'.repeat(3))\n                        .type('{backspace}')\n                        .should('have.value', '0,0000')\n                        .should('have.prop', 'selectionStart', '0,'.length)\n                        .should('have.prop', 'selectionEnd', '0,'.length);\n                });\n            });\n\n            describe('Via `Delete` button', () => {\n                it('0,|0000 => Delete => 0,0|000', () => {\n                    cy.get('@input')\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat('0,'.length))\n                        .type('{del}')\n                        .should('have.value', '0,0000')\n                        .should('have.prop', 'selectionStart', '0,0'.length)\n                        .should('have.prop', 'selectionEnd', '0,0'.length);\n                });\n\n                it('0,0|000 => Delete => 0,00|00', () => {\n                    cy.get('@input')\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat('0,0'.length))\n                        .type('{del}')\n                        .should('have.value', '0,0000')\n                        .should('have.prop', 'selectionStart', '0,00'.length)\n                        .should('have.prop', 'selectionEnd', '0,00'.length);\n                });\n\n                it('0,00|00 => Delete => 0,000|0', () => {\n                    cy.get('@input')\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat('0,00'.length))\n                        .type('{del}')\n                        .should('have.value', '0,0000')\n                        .should('have.prop', 'selectionStart', '0,000'.length)\n                        .should('have.prop', 'selectionEnd', '0,000'.length);\n                });\n\n                it('0,000|0 => Delete => 0,0000|', () => {\n                    cy.get('@input')\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat('0,000'.length))\n                        .type('{del}')\n                        .should('have.value', '0,0000')\n                        .should('have.prop', 'selectionStart', '0,0000'.length)\n                        .should('have.prop', 'selectionEnd', '0,0000'.length);\n                });\n            });\n        });\n    });\n\n    describe('[minimumFractionDigits] === 3; [maximumFractionDigits] === 5', () => {\n        beforeEach(() => {\n            openNumberPage('minimumFractionDigits=3&maximumFractionDigits=5');\n        });\n\n        it('Type 42 => 42.000', () => {\n            cy.get('@input')\n                .type('42')\n                .should('have.value', '42.000')\n                .should('have.prop', 'selectionStart', '42'.length)\n                .should('have.prop', 'selectionEnd', '42'.length);\n        });\n\n        it('Type 42 => 42.000 => Move cursor to the end => Type 12', () => {\n            cy.get('@input')\n                .type('42')\n                .should('have.value', '42.000')\n                .type('{moveToEnd}')\n                .type('45')\n                .should('have.prop', 'selectionStart', '42.00045'.length)\n                .should('have.prop', 'selectionEnd', '42.00045'.length);\n        });\n\n        it('Type , => 0.|000', () => {\n            cy.get('@input')\n                .type(',')\n                .should('have.value', '0.000')\n                .should('have.prop', 'selectionStart', '0.'.length)\n                .should('have.prop', 'selectionEnd', '0.'.length);\n        });\n\n        it('Type 42.27 => 42.270', () => {\n            cy.get('@input')\n                .type('42.27')\n                .should('have.value', '42.270')\n                .should('have.prop', 'selectionStart', '42.27'.length)\n                .should('have.prop', 'selectionEnd', '42.27'.length);\n        });\n\n        it('Integer part has `overwriteMode: shift`', () => {\n            cy.get('@input')\n                .type('42,27')\n                .type('{leftArrow}'.repeat('2.27'.length))\n                .type('55')\n                .should('have.value', '4 552.270')\n                .should('have.prop', 'selectionStart', '4 55'.length)\n                .should('have.prop', 'selectionEnd', '4 55'.length);\n        });\n\n        it('Decimal part has `overwriteMode: replace`', () => {\n            cy.get('@input')\n                .type('42,27')\n                .type('{leftArrow}'.repeat('27'.length))\n                .type('55')\n                .should('have.value', '42.550')\n                .should('have.prop', 'selectionStart', '42.55'.length)\n                .should('have.prop', 'selectionEnd', '42.55'.length);\n        });\n\n        it('42.|270 => Backspace => 42|.270', () => {\n            cy.get('@input')\n                .type('42.27')\n                .type('{leftArrow}'.repeat('27'.length))\n                .type('{backspace}')\n                .should('have.value', '42.270')\n                .should('have.prop', 'selectionStart', '42'.length)\n                .should('have.prop', 'selectionEnd', '42'.length);\n        });\n\n        it('42|.270 => Delete => 42.|270', () => {\n            cy.get('@input')\n                .type('42,27')\n                .type('{leftArrow}'.repeat('.27'.length))\n                .type('{del}')\n                .should('have.value', '42.270')\n                .should('have.prop', 'selectionStart', '42.'.length)\n                .should('have.prop', 'selectionEnd', '42.'.length);\n        });\n\n        it('0|.4242 => Backspace => |.4242 => .4242| => Backspace x4 => .|000', () => {\n            cy.get('@input')\n                .type('0.4242')\n                .type('{moveToStart}{rightArrow}')\n                .type('{backspace}')\n                .should('have.value', '.4242')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0)\n                .type('{moveToEnd}')\n                .type('{backspace}'.repeat(2))\n                .should('have.value', '.420')\n                .should('have.prop', 'selectionStart', '.42'.length)\n                .should('have.prop', 'selectionEnd', '.42'.length)\n                .type('{backspace}'.repeat(2))\n                .should('have.value', '.000')\n                .should('have.prop', 'selectionStart', '.'.length)\n                .should('have.prop', 'selectionEnd', '.'.length);\n        });\n\n        describe('Extra decimal separator insertion', () => {\n            it('42.|270 => Type . => 42.|270', () => {\n                cy.get('@input')\n                    .type('42.27')\n                    .type('{leftArrow}'.repeat('27'.length))\n                    .should('have.value', '42.270')\n                    .should('have.prop', 'selectionStart', '42.'.length)\n                    .should('have.prop', 'selectionEnd', '42.'.length)\n                    .type('.')\n                    .should('have.value', '42.270')\n                    .should('have.prop', 'selectionStart', '42.'.length)\n                    .should('have.prop', 'selectionEnd', '42.'.length);\n            });\n\n            it('42|.270 => Type . => 42.|270', () => {\n                cy.get('@input')\n                    .type('42.27')\n                    .type('{leftArrow}'.repeat('.27'.length))\n                    .should('have.value', '42.270')\n                    .should('have.prop', 'selectionStart', '42'.length)\n                    .should('have.prop', 'selectionEnd', '42'.length)\n                    .type('.')\n                    .should('have.value', '42.270')\n                    .should('have.prop', 'selectionStart', '42.'.length)\n                    .should('have.prop', 'selectionEnd', '42.'.length);\n            });\n\n            it('42.2|70 => Type , => 42.2|70', () => {\n                cy.get('@input')\n                    .type('42.27')\n                    .type('{leftArrow}')\n                    .should('have.value', '42.270')\n                    .should('have.prop', 'selectionStart', '42.2'.length)\n                    .should('have.prop', 'selectionEnd', '42.2'.length)\n                    .type(',')\n                    .should('have.value', '42.270')\n                    .should('have.prop', 'selectionStart', '42.2'.length)\n                    .should('have.prop', 'selectionEnd', '42.2'.length);\n            });\n\n            it('9|9.1234 => Type . => 9|9.1234 (no changes)', () => {\n                cy.get('@input')\n                    .type('99.1234')\n                    .type('{moveToStart}{rightArrow}')\n                    .should('have.value', '99.1234')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1)\n                    .type('.')\n                    .should('have.value', '99.1234')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n        });\n\n        describe('Move caret when user tries to delete non-removable zeroes in decimal part', () => {\n            beforeEach(() => {\n                cy.get('@input').type(',').should('have.value', '0.000');\n            });\n\n            describe('Via `Backspace` button', () => {\n                it('0.000| => Backspace => 0.00|0', () => {\n                    cy.get('@input')\n                        .type('{moveToEnd}{backspace}')\n                        .should('have.value', '0.000')\n                        .should('have.prop', 'selectionStart', '0.00'.length)\n                        .should('have.prop', 'selectionEnd', '0.00'.length);\n                });\n\n                it('0.00|0 => Backspace => 0.0|00', () => {\n                    cy.get('@input')\n                        .type('{moveToEnd}{leftArrow}{backspace}')\n                        .should('have.value', '0.000')\n                        .should('have.prop', 'selectionStart', '0.0'.length)\n                        .should('have.prop', 'selectionEnd', '0.0'.length);\n                });\n\n                it('0.0|00 => Backspace => 0.|000', () => {\n                    cy.get('@input')\n                        .type('{moveToEnd}')\n                        .type('{leftArrow}'.repeat(2))\n                        .type('{backspace}')\n                        .should('have.value', '0.000')\n                        .should('have.prop', 'selectionStart', '0.'.length)\n                        .should('have.prop', 'selectionEnd', '0.'.length);\n                });\n            });\n\n            describe('Via `Delete` button', () => {\n                it('0.|000 => Delete => 0.0|00', () => {\n                    cy.get('@input')\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat('0.'.length))\n                        .type('{del}')\n                        .should('have.value', '0.000')\n                        .should('have.prop', 'selectionStart', '0.0'.length)\n                        .should('have.prop', 'selectionEnd', '0.0'.length);\n                });\n\n                it('0.0|00 => Delete => 0.00|0', () => {\n                    cy.get('@input')\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat('0.0'.length))\n                        .type('{del}')\n                        .should('have.value', '0.000')\n                        .should('have.prop', 'selectionStart', '0.00'.length)\n                        .should('have.prop', 'selectionEnd', '0.00'.length);\n                });\n\n                it('0.00|0 => Delete => 0.000|', () => {\n                    cy.get('@input')\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat('0.00'.length))\n                        .type('{del}')\n                        .should('have.value', '0.000')\n                        .should('have.prop', 'selectionStart', '0.000'.length)\n                        .should('have.prop', 'selectionEnd', '0.000'.length);\n                });\n            });\n        });\n    });\n\n    describe('[decimalZeroPadding] is compatible with [postfix]', () => {\n        it('Type 42', () => {\n            openNumberPage(\n                'prefix=$&postfix=$&minimumFractionDigits=2&maximumFractionDigits=2',\n            );\n\n            cy.get('@input')\n                .type('42')\n                .should('have.value', '$42.00$')\n                .should('have.prop', 'selectionStart', '$42'.length)\n                .should('have.prop', 'selectionEnd', '$42'.length);\n        });\n\n        it('Type 42,24', () => {\n            openNumberPage(\n                'prefix=$&postfix=$&minimumFractionDigits=2&maximumFractionDigits=2',\n            );\n\n            cy.get('@input')\n                .type('42,24')\n                .should('have.value', '$42.24$')\n                .should('have.prop', 'selectionStart', '$42.24'.length)\n                .should('have.prop', 'selectionEnd', '$42.24'.length);\n        });\n\n        it('Type 42.24', () => {\n            openNumberPage(\n                'prefix=$&postfix=kg&minimumFractionDigits=2&maximumFractionDigits=2',\n            );\n\n            cy.get('@input')\n                .type('42.24')\n                .should('have.value', '$42.24kg')\n                .should('have.prop', 'selectionStart', '$42.24'.length)\n                .should('have.prop', 'selectionEnd', '$42.24'.length);\n        });\n    });\n\n    describe('conditions for empty textfield', () => {\n        beforeEach(() => {\n            openNumberPage(\n                'thousandSeparator=_&maximumFractionDigits=2&minimumFractionDigits=2&minusSign=-',\n            );\n        });\n\n        it('0.42| => Backspace x3 => 0|.00 => Backspace => Empty', () => {\n            cy.get('@input')\n                .type('0.42')\n                .should('have.value', '0.42')\n                .type('{backspace}'.repeat(3))\n                .should('have.value', '0.00')\n                .should('have.a.prop', 'selectionStart', 1)\n                .should('have.a.prop', 'selectionEnd', 1)\n                .type('{backspace}')\n                .should('have.value', '');\n        });\n\n        it('-.42| => Backspace x2 => -.|00  => Backspace => -', () => {\n            cy.get('@input')\n                .type('-0.42')\n                .should('have.value', '-0.42')\n                .type('{moveToStart}')\n                .type('{rightArrow}'.repeat(2))\n                .type('{backspace}')\n                .should('have.value', '-.42')\n                .type('{moveToEnd}')\n                .type('{backspace}')\n                .should('have.value', '-.40')\n                .type('{backspace}')\n                .should('have.value', '-.00')\n                .should('have.a.prop', 'selectionStart', 2)\n                .should('have.a.prop', 'selectionEnd', 2)\n                .type('{backspace}')\n                .should('have.value', '-')\n                .should('have.a.prop', 'selectionStart', 1)\n                .should('have.a.prop', 'selectionEnd', 1);\n        });\n\n        it('5|.00 => Backspace  => Empty', () => {\n            cy.get('@input')\n                .type('5')\n                .should('have.value', '5.00')\n                .should('have.a.prop', 'selectionStart', 1)\n                .should('have.a.prop', 'selectionEnd', 1)\n                .type('{backspace}')\n                .should('have.value', '');\n        });\n\n        it('-5|.00 => Backspace  => -', () => {\n            cy.get('@input')\n                .type('-5')\n                .should('have.value', '-5.00')\n                .should('have.a.prop', 'selectionStart', 2)\n                .should('have.a.prop', 'selectionEnd', 2)\n                .type('{backspace}')\n                .should('have.value', '-');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-examples.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Documentation page \"Number\"', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.Number);\n    });\n\n    describe('Example \"Postfix\"', () => {\n        beforeEach(() => {\n            cy.get('#postfix input').should('be.visible').first().as('input');\n        });\n\n        it('pads value without digits with zero on blur', () => {\n            cy.get('@input')\n                .focus()\n                .should('have.prop', 'selectionStart', '97'.length)\n                .should('have.prop', 'selectionEnd', '97'.length)\n                .clear()\n                .should('have.value', '%')\n                .blur()\n                .should('have.value', '0%');\n        });\n    });\n\n    describe('Example \"Thousand separator pattern\" (Japanese yen, manual)', () => {\n        beforeEach(() => {\n            cy.get('#thousand-separator-pattern input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .clear()\n                .as('input');\n        });\n\n        it('groups 8 digits in sets of 4: ¥1234,5678', () => {\n            cy.get('@input').type('12345678').should('have.value', '¥1234,5678');\n        });\n\n        it('groups 9 digits in sets of 4: ¥1,2345,6789', () => {\n            cy.get('@input').type('123456789').should('have.value', '¥1,2345,6789');\n        });\n\n        it('regroups after backspace: ¥1,2345,6789 => ¥1234,5678', () => {\n            cy.get('@input')\n                .type('123456789')\n                .type('{backspace}')\n                .should('have.value', '¥1234,5678');\n        });\n    });\n\n    describe('Example \"Thousand separator pattern uses Intl.NumberFormat\" (Indian, via Intl)', () => {\n        beforeEach(() => {\n            cy.get('#thousand-separator-pattern-intl input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .clear()\n                .as('input');\n        });\n\n        it('groups 7 digits as 2+2+3 from right: ₹12,34,567', () => {\n            cy.get('@input').type('1234567').should('have.value', '₹12,34,567');\n        });\n\n        it('regroups after backspace: ₹12,34,567 => ₹1,23,456', () => {\n            cy.get('@input')\n                .type('1234567')\n                .type('{backspace}')\n                .should('have.value', '₹1,23,456');\n        });\n\n        it('groups 9 digits as 3+2+2+2 from right: ₹12,34,56,789', () => {\n            cy.get('@input').type('123456789').should('have.value', '₹12,34,56,789');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-fullwidth-to-halfwidth.cy.ts",
    "content": "import {openNumberPage} from './utils';\n\ndescribe('Number | Accepts full width numbers used by JP, CN or others', () => {\n    beforeEach(() => {\n        openNumberPage('thousandSeparator=_&maximumFractionDigits=2');\n    });\n\n    describe('Invalid characters', () => {\n        it('accepts full width numbers', () => {\n            cy.get('@input')\n                .type('１ ２  ３   ４    ５')\n                .should('have.value', '12_345')\n                .should('have.prop', 'selectionStart', '12_345'.length)\n                .should('have.prop', 'selectionEnd', '12_345'.length);\n        });\n\n        it('accepts full width characters with minus', () => {\n            cy.get('@input')\n                .type('ー１２３４５６')\n                .should('have.value', '−123_456')\n                .should('have.prop', 'selectionStart', '−123_456'.length)\n                .should('have.prop', 'selectionEnd', '−123_456'.length);\n        });\n\n        it('rejects full width characters, not numbers', () => {\n            cy.get('@input')\n                .type('あいうえお１２３４５こんにちは')\n                .should('have.value', '12_345')\n                .should('have.prop', 'selectionStart', '12_345'.length)\n                .should('have.prop', 'selectionEnd', '12_345'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-max-validation.cy.ts",
    "content": "import {CHAR_MINUS} from 'projects/kit/src/lib/constants';\n\nimport {openNumberPage} from './utils';\n\ndescribe('Number | Max validation', () => {\n    describe('Max = 3', () => {\n        beforeEach(() => {\n            openNumberPage('max=3&maximumFractionDigits=4');\n        });\n\n        ['0', '1', '2', '3'].forEach((value) => {\n            it(`accepts ${value}`, () => {\n                cy.get('@input')\n                    .type(value)\n                    .should('have.value', value)\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n        });\n\n        ['4', '5', '6', '7', '8', '9'].forEach((value) => {\n            it(`rejects ${value} (replace it with max value)`, () => {\n                cy.get('@input')\n                    .type(value)\n                    .should('have.value', '3')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n        });\n\n        describe('accepts any decimal value (integer part is less than max one)', () => {\n            it('0,9999', () => {\n                cy.get('@input')\n                    .type(',9999')\n                    .should('have.value', '0.9999')\n                    .should('have.prop', 'selectionStart', '0.9999'.length)\n                    .should('have.prop', 'selectionEnd', '0.9999'.length);\n            });\n\n            it('2,777', () => {\n                cy.get('@input')\n                    .type('2,777')\n                    .should('have.value', '2.777')\n                    .should('have.prop', 'selectionStart', '2.777'.length)\n                    .should('have.prop', 'selectionEnd', '2.777'.length);\n            });\n        });\n\n        it('rejects decimal part is integer part is already equal to max', () => {\n            cy.get('@input')\n                .type('3,9')\n                .should('have.value', '3')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        it('|0 => Type 5 => 3|', () => {\n            cy.get('@input')\n                .type('0')\n                .type('{moveToStart}')\n                .should('have.value', '0')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0)\n                .type('5')\n                .should('have.value', '3')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n    });\n\n    describe('Max=777', () => {\n        beforeEach(() => {\n            openNumberPage('max=777');\n        });\n\n        ['5', '10', '77', '770', '776', '777'].forEach((value) => {\n            it(`accepts ${value}`, () => {\n                cy.get('@input')\n                    .type(value)\n                    .should('have.value', value)\n                    .should('have.prop', 'selectionStart', value.length)\n                    .should('have.prop', 'selectionEnd', value.length);\n            });\n        });\n\n        ['778', '779', '7777', '1000'].forEach((value) => {\n            it(`rejects ${value} (replace it with max value)`, () => {\n                cy.get('@input')\n                    .type(value)\n                    .should('have.value', '777')\n                    .should('have.prop', 'selectionStart', 3)\n                    .should('have.prop', 'selectionEnd', 3);\n            });\n        });\n\n        it('9|9 => Type 7 => 777|', () => {\n            cy.get('@input')\n                .type('99')\n                .type('{leftArrow}')\n                .should('have.value', '99')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1)\n                .type('0')\n                .should('have.value', '777')\n                .should('have.prop', 'selectionStart', '777'.length)\n                .should('have.prop', 'selectionEnd', '777'.length);\n        });\n    });\n\n    describe('Max = -5', () => {\n        beforeEach(() => {\n            openNumberPage('max=-5');\n        });\n\n        it('can type -42 (via keyboard, 1 character per keydown)', () => {\n            cy.get('@input')\n                .type('-4')\n                .should('have.value', '−4')\n                .should('have.prop', 'selectionStart', '−4'.length)\n                .should('have.prop', 'selectionEnd', '−4'.length)\n                .type('2')\n                .should('have.value', '−42')\n                .should('have.prop', 'selectionStart', '−42'.length)\n                .should('have.prop', 'selectionEnd', '−42'.length);\n        });\n\n        it('replaces -4 with -5 on blur', () => {\n            cy.get('@input')\n                .type('-4')\n                .wait(100) // to be sure that value is not changed even in case of some async validation\n                .should('have.value', '−4')\n                .should('have.prop', 'selectionStart', '−4'.length)\n                .should('have.prop', 'selectionEnd', '−4'.length)\n                .blur()\n                .should('have.value', '−5');\n        });\n\n        it('keeps -6 untouched on blur', () => {\n            cy.get('@input')\n                .type('-6')\n                .wait(100) // to be sure that value is not changed even in case of some async validation\n                .should('have.value', '−6')\n                .should('have.prop', 'selectionStart', '−6'.length)\n                .should('have.prop', 'selectionEnd', '−6'.length)\n                .blur()\n                .wait(100)\n                .should('have.value', '−6');\n        });\n    });\n\n    describe('postfix with digit (cm3) should not affect max validation', () => {\n        it(`types 1234567890123456 with max=${Number.MAX_SAFE_INTEGER} — NOT clamped to max`, () => {\n            openNumberPage(`postfix=cm3&max=${Number.MAX_SAFE_INTEGER}`);\n\n            const expectedValue = '1 234 567 890 123 456cm3';\n\n            cy.get('@input')\n                .type('1234567890123456')\n                .should('have.value', expectedValue)\n                .should(\n                    'have.prop',\n                    'selectionStart',\n                    expectedValue.length - 'cm3'.length,\n                )\n                .should('have.prop', 'selectionEnd', expectedValue.length - 'cm3'.length);\n        });\n\n        it('types value exceeding max — IS clamped to 999', () => {\n            openNumberPage('postfix=cm3&max=999');\n\n            cy.get('@input')\n                .type('12345')\n                .should('have.value', '999cm3')\n                .should('have.prop', 'selectionStart', '999'.length)\n                .should('have.prop', 'selectionEnd', '999'.length);\n        });\n\n        it(`types negative value within min with min=${Number.MIN_SAFE_INTEGER} — NOT clamped`, () => {\n            openNumberPage(`postfix=cm3&min=${Number.MIN_SAFE_INTEGER}`);\n\n            const expectedValue = `${CHAR_MINUS}1 234 567 890 123 456cm3`;\n\n            cy.get('@input')\n                .type('-1234567890123456')\n                .should('have.value', expectedValue)\n                .should(\n                    'have.prop',\n                    'selectionStart',\n                    expectedValue.length - 'cm3'.length,\n                )\n                .should('have.prop', 'selectionEnd', expectedValue.length - 'cm3'.length);\n        });\n    });\n\n    describe('prefix with digit (100x) should not affect max validation', () => {\n        it('types value within max — NOT clamped', () => {\n            openNumberPage('prefix=100x&max=999');\n\n            const expectedValue = '100x500';\n\n            cy.get('@input')\n                .type('500')\n                .should('have.value', expectedValue)\n                .should('have.prop', 'selectionStart', expectedValue.length)\n                .should('have.prop', 'selectionEnd', expectedValue.length);\n        });\n\n        it('types value exceeding max — IS clamped to 999', () => {\n            openNumberPage('prefix=100x&max=999');\n\n            cy.get('@input')\n                .type('1234')\n                .should('have.value', '100x999')\n                .should('have.prop', 'selectionStart', '100x999'.length)\n                .should('have.prop', 'selectionEnd', '100x999'.length);\n        });\n    });\n\n    describe('Max = -0.1', () => {\n        beforeEach(() => {\n            openNumberPage('maximumFractionDigits=2&minusSign=-&max=-0.1');\n        });\n\n        it('can type -0.5 (via keyboard, 1 character per keydown)', () => {\n            cy.get('@input')\n                .type('-.')\n                .should('have.value', '-0.')\n                .should('have.prop', 'selectionStart', '-0.'.length)\n                .should('have.prop', 'selectionEnd', '-0.'.length)\n                .type('5')\n                .should('have.value', '-0.5')\n                .should('have.prop', 'selectionStart', '-0.5'.length)\n                .should('have.prop', 'selectionEnd', '-0.5'.length);\n        });\n\n        it('keeps -0.10 untouched on blur', () => {\n            cy.get('@input')\n                .type('-0.10')\n                .should('have.value', '-0.10')\n                .blur()\n                .should('have.value', '-0.10');\n        });\n\n        it('replaces 0 with -0.1 on blur', () => {\n            cy.get('@input')\n                .type('0')\n                .should('have.value', '0')\n                .blur()\n                .should('have.value', '-0.1');\n        });\n\n        it('replaces 0.05 with -0.1 on blur', () => {\n            cy.get('@input')\n                .type('-0.05')\n                .should('have.value', '-0.05')\n                .blur()\n                .should('have.value', '-0.1');\n        });\n\n        it('allows to erase the last digit (even if new possible value is more than max) (-0.1| => Backspace => -0.|)', () => {\n            cy.get('@input')\n                .type('-0.1')\n                .should('have.value', '-0.1')\n                .should('have.prop', 'selectionStart', '-0.1'.length)\n                .should('have.prop', 'selectionEnd', '-0.1'.length)\n                .type('{backspace}')\n                .should('have.value', '-0.')\n                .type('{backspace}')\n                .should('have.value', '-0')\n                .type('{backspace}')\n                .should('have.value', '-')\n                .type('{backspace}')\n                .should('have.value', '');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-min-validation.cy.ts",
    "content": "import {openNumberPage} from './utils';\n\ndescribe('Number | Min validation', () => {\n    describe('Min = -100', () => {\n        beforeEach(() => {\n            openNumberPage('min=-100&maximumFractionDigits=4');\n        });\n\n        ['−1', '−10', '−42', '−100', '0', '1', '5', '99'].forEach((value) => {\n            it(`accepts ${value}`, () => {\n                cy.get('@input')\n                    .type(value)\n                    .should('have.value', value)\n                    .should('have.prop', 'selectionStart', value.length)\n                    .should('have.prop', 'selectionEnd', value.length);\n            });\n        });\n\n        ['-101', '-256', '-512'].forEach((value) => {\n            it(`rejects ${value} (replace it with min value)`, () => {\n                cy.get('@input')\n                    .type(value)\n                    .should('have.value', '−100')\n                    .should('have.prop', 'selectionStart', '−100'.length)\n                    .should('have.prop', 'selectionEnd', '−100'.length);\n            });\n        });\n\n        describe('accepts any decimal value (integer part is less than max one)', () => {\n            it('-99,9999', () => {\n                cy.get('@input')\n                    .type('−99,9999')\n                    .should('have.value', '−99.9999')\n                    .should('have.prop', 'selectionStart', '−99.9999'.length)\n                    .should('have.prop', 'selectionEnd', '−99.9999'.length);\n            });\n\n            it('-0,0500', () => {\n                cy.get('@input')\n                    .type('-0,0500')\n                    .should('have.value', '−0.0500')\n                    .should('have.prop', 'selectionStart', '−0.0500'.length)\n                    .should('have.prop', 'selectionEnd', '−0.0500'.length);\n            });\n        });\n\n        it('rejects decimal part is integer part is already equal to max', () => {\n            cy.get('@input')\n                .type('-100,0001')\n                .should('have.value', '−100')\n                .should('have.prop', 'selectionStart', '−100'.length)\n                .should('have.prop', 'selectionEnd', '−100'.length);\n        });\n\n        it('-|50 => Type 1 => -100|', () => {\n            cy.get('@input')\n                .type('-50')\n                .type('{moveToStart}{rightArrow}')\n                .should('have.value', '−50')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1)\n                .type('1')\n                .should('have.value', '−100')\n                .should('have.prop', 'selectionStart', '−100'.length)\n                .should('have.prop', 'selectionEnd', '−100'.length);\n        });\n    });\n\n    describe('Min = 5', () => {\n        beforeEach(() => {\n            openNumberPage('min=5&maximumFractionDigits=4');\n        });\n\n        it('can type 10 (via keyboard, 1 character per keydown)', () => {\n            cy.get('@input')\n                .type('1')\n                .should('have.value', '1')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1)\n                .type('0')\n                .should('have.value', '10')\n                .should('have.prop', 'selectionStart', '10'.length)\n                .should('have.prop', 'selectionEnd', '10'.length);\n        });\n\n        it('replaces 1 with 5 on blur', () => {\n            cy.get('@input')\n                .type('1')\n                .wait(100) // to be sure that value is not changed even in case of some async validation\n                .should('have.value', '1')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1)\n                .blur()\n                .should('have.value', '5');\n        });\n\n        it('keeps 6 untouched on blur', () => {\n            cy.get('@input')\n                .type('6')\n                .wait(100) // to be sure that value is not changed even in case of some async validation\n                .should('have.value', '6')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1)\n                .blur()\n                .wait(100)\n                .should('have.value', '6');\n        });\n    });\n\n    describe('Min = 0.1', () => {\n        beforeEach(() => {\n            openNumberPage('min=0.1&maximumFractionDigits=2');\n        });\n\n        it('can type 0.5 (via keyboard, 1 character per keydown)', () => {\n            cy.get('@input')\n                .type('.')\n                .should('have.value', '0.')\n                .should('have.prop', 'selectionStart', '0.'.length)\n                .should('have.prop', 'selectionEnd', '0.'.length)\n                .type('5')\n                .should('have.value', '0.5')\n                .should('have.prop', 'selectionStart', '0.5'.length)\n                .should('have.prop', 'selectionEnd', '0.5'.length);\n        });\n\n        it('keeps 0.10 untouched on blur', () => {\n            cy.get('@input')\n                .type('0.10')\n                .should('have.value', '0.10')\n                .blur()\n                .should('have.value', '0.10');\n        });\n\n        it('replaces 0 with 0.1 on blur', () => {\n            cy.get('@input')\n                .type('0')\n                .should('have.value', '0')\n                .blur()\n                .should('have.value', '0.1');\n        });\n\n        it('replaces 0.05 with 0.1 on blur', () => {\n            cy.get('@input')\n                .type('0.05')\n                .should('have.value', '0.05')\n                .blur()\n                .should('have.value', '0.1');\n        });\n\n        it('allows to erase the last digit (even if new possible value is less than min) (0.1| => Backspace => 0.|)', () => {\n            cy.get('@input')\n                .type('0.1')\n                .should('have.value', '0.1')\n                .should('have.prop', 'selectionStart', '0.1'.length)\n                .should('have.prop', 'selectionEnd', '0.1'.length)\n                .type('{backspace}')\n                .should('have.value', '0.')\n                .type('{backspace}')\n                .should('have.value', '0')\n                .type('{backspace}')\n                .should('have.value', '');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-minus-before-prefix.cy.ts",
    "content": "import {CHAR_EM_DASH, CHAR_EN_DASH, CHAR_JP_HYPHEN} from 'projects/kit/src/lib/constants';\n\nimport {openNumberPage} from './utils';\n\ndescribe('Number | Minus before prefix', () => {\n    describe('[prefix]=\"$\" & [minusSign]=\"-\" & negativePattern=\"minusFirst\"', () => {\n        beforeEach(() => {\n            openNumberPage(\n                'decimalSeparator=.&thousandSeparator=_&maximumFractionDigits=2&minusSign=-&prefix=$&negativePattern=minusFirst',\n            );\n        });\n\n        describe('adds minus even if caret is already placed after prefix', () => {\n            it('works with set minus sign', () => {\n                cy.get('@input')\n                    .focus()\n                    .should('have.value', '$')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1)\n                    .type('-')\n                    .should('have.value', '-$')\n                    .should('have.prop', 'selectionStart', '-$'.length)\n                    .should('have.prop', 'selectionEnd', '-$'.length);\n            });\n\n            it('$|100 => type \"-\" => -$|100 (caret stays before digits)', () => {\n                cy.get('@input')\n                    .type('100')\n                    .should('have.value', '$100')\n                    .type('{moveToStart}')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1)\n                    .type('-')\n                    .should('have.value', '-$100')\n                    .should('have.prop', 'selectionStart', '-$'.length)\n                    .should('have.prop', 'selectionEnd', '-$'.length);\n            });\n\n            describe('works with pseudo minuses', () => {\n                [CHAR_EN_DASH, CHAR_EM_DASH, CHAR_JP_HYPHEN].forEach((minus) => {\n                    it(minus, () => {\n                        cy.get('@input')\n                            .focus()\n                            .should('have.value', '$')\n                            .should('have.prop', 'selectionStart', 1)\n                            .should('have.prop', 'selectionEnd', 1)\n                            .type(minus)\n                            .should('have.value', '-$')\n                            .should('have.prop', 'selectionStart', '-$'.length)\n                            .should('have.prop', 'selectionEnd', '-$'.length);\n                    });\n                });\n            });\n        });\n\n        describe('thousand separators correctly works with leading minus + prefix', () => {\n            it('Type 123456789', () => {\n                cy.get('@input')\n                    .type('-123456789')\n                    .should('have.value', '-$123_456_789')\n                    .should('have.prop', 'selectionStart', '-$123_456_789'.length)\n                    .should('have.prop', 'selectionEnd', '-$123_456_789'.length);\n            });\n\n            it('$1_000_000| per day => Backspace => Backspace x2', () => {\n                cy.get('@input')\n                    .type('-123456789')\n                    .type('{backspace}')\n                    .should('have.value', '-$12_345_678')\n                    .should('have.prop', 'selectionStart', '-$12_345_678'.length)\n                    .should('have.prop', 'selectionEnd', '-$12_345_678'.length)\n                    .type('{backspace}'.repeat(2))\n                    .should('have.value', '-$123_456')\n                    .should('have.prop', 'selectionStart', '-$123_456'.length)\n                    .should('have.prop', 'selectionEnd', '-$123_456'.length)\n                    .type('{backspace}'.repeat(3))\n                    .should('have.value', '-$123')\n                    .should('have.prop', 'selectionStart', '-$123'.length)\n                    .should('have.prop', 'selectionEnd', '-$123'.length);\n            });\n\n            it('-$|1_234 => Del => -$|234', () => {\n                cy.get('@input')\n                    .type('-1234')\n                    .should('have.value', '-$1_234')\n                    .should('have.prop', 'selectionStart', '-$1_234'.length)\n                    .should('have.prop', 'selectionEnd', '-$1_234'.length)\n                    .type('{moveToStart}{del}')\n                    .should('have.value', '-$234')\n                    .should('have.prop', 'selectionStart', '-$'.length)\n                    .should('have.prop', 'selectionEnd', '-$'.length);\n            });\n\n            it('$1_2|34 => Backspace => $|234', () => {\n                cy.get('@input')\n                    .type('-1234')\n                    .should('have.value', '-$1_234')\n                    .should('have.prop', 'selectionStart', '-$1_234'.length)\n                    .should('have.prop', 'selectionEnd', '-$1_234'.length)\n                    .type('{leftArrow}'.repeat(2))\n                    .type('{backspace}')\n                    .should('have.value', '-$134')\n                    .should('have.prop', 'selectionStart', '-$1'.length)\n                    .should('have.prop', 'selectionEnd', '-$1'.length);\n            });\n        });\n\n        it('pads integer part with zero if user types decimal separator (for empty input)', () => {\n            cy.get('@input')\n                .type('-.')\n                .should('have.value', '-$0.')\n                .should('have.prop', 'selectionStart', '-$0.'.length)\n                .should('have.prop', 'selectionEnd', '-$0.'.length)\n                .type('42')\n                .should('have.value', '-$0.42')\n                .should('have.prop', 'selectionStart', '-$0.42'.length)\n                .should('have.prop', 'selectionEnd', '-$0.42'.length);\n        });\n\n        it('[maximumFractionDigits] works', () => {\n            cy.get('@input')\n                .type('-.12345678')\n                .should('have.value', '-$0.12')\n                .should('have.prop', 'selectionStart', '-$0.12'.length)\n                .should('have.prop', 'selectionEnd', '-$0.12'.length);\n        });\n\n        describe('it removes repeated leading zeroes for integer part on blur', () => {\n            it('Type -000005 => blur => -$5|', () => {\n                cy.get('@input')\n                    .type('-000005')\n                    .should('have.value', '-$000_005')\n                    .should('have.prop', 'selectionStart', '-$000_005'.length)\n                    .should('have.prop', 'selectionEnd', '-$000_005'.length)\n                    .blur()\n                    .should('have.value', '-$5');\n            });\n\n            it('-$0.|05 per day => Backspace => blur => $|5 per day', () => {\n                cy.get('@input')\n                    .type('-0.05')\n                    .type('{leftArrow}'.repeat('05'.length))\n                    .type('{backspace}')\n                    .should('have.value', '-$005')\n                    .should('have.prop', 'selectionStart', '-$0'.length)\n                    .should('have.prop', 'selectionEnd', '-$0'.length)\n                    .blur()\n                    .should('have.value', '-$5');\n            });\n        });\n\n        describe('minus sign is erasable but prefix is non-removable', () => {\n            it('Select all + Backspace', () => {\n                cy.get('@input')\n                    .type('-123')\n                    .type('{selectAll}{backspace}')\n                    .should('have.value', '-$')\n                    .should('have.prop', 'selectionStart', 2)\n                    .should('have.prop', 'selectionEnd', 2);\n            });\n\n            it('-$|42 => Backspace => $|42', () => {\n                cy.get('@input')\n                    .type('-42')\n                    .type('{moveToStart}')\n                    .should('have.value', '-$42')\n                    .should('have.prop', 'selectionStart', '-$'.length)\n                    .should('have.prop', 'selectionEnd', '-$'.length)\n                    .type('{backspace}')\n                    .should('have.value', '$42')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1)\n                    .type('{backspace}'.repeat(5))\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            it('$|42 => Backspace x5 => $|42', () => {\n                cy.get('@input')\n                    .type('42')\n                    .type('{moveToStart}')\n                    .should('have.value', '$42')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1)\n                    .type('{backspace}'.repeat(5))\n                    .should('have.value', '$42')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            it('$| => Backspace x5 => $|', () => {\n                cy.get('@input')\n                    .focus()\n                    .should('have.value', '$')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1)\n                    .type('{backspace}'.repeat(5))\n                    .should('have.value', '$')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            it('-$| => Backspace => $|', () => {\n                cy.get('@input')\n                    .type('-')\n                    .should('have.value', '-$')\n                    .should('have.prop', 'selectionStart', '-$'.length)\n                    .should('have.prop', 'selectionEnd', '-$'.length)\n                    .type('{backspace}')\n                    .should('have.value', '$')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n        });\n\n        it('-$|42 => ArrowLeft => -$|42', () => {\n            cy.get('@input')\n                .type('-42')\n                .type('{moveToStart}')\n                .type('{leftArrow}'.repeat(3))\n                .should('have.value', '-$42')\n                .should('have.prop', 'selectionStart', '-$'.length)\n                .should('have.prop', 'selectionEnd', '-$'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-minus-sign.cy.ts",
    "content": "import {\n    CHAR_EM_DASH,\n    CHAR_EN_DASH,\n    CHAR_HYPHEN,\n    CHAR_JP_HYPHEN,\n    CHAR_MINUS,\n} from 'projects/kit/src/lib/constants';\n\nimport {openNumberPage} from './utils';\n\ndescribe('Number | minus sign', () => {\n    const pseudoMinuses = [\n        {value: CHAR_HYPHEN, name: 'hyphen'},\n        {value: CHAR_EN_DASH, name: 'en-dash'},\n        {value: CHAR_EM_DASH, name: 'em-dash'},\n        {value: CHAR_JP_HYPHEN, name: 'japanese prolonged sound mark'},\n        {value: CHAR_MINUS, name: 'unicode minus sign'},\n    ];\n\n    describe('can use hyphen, all kind of dashes and minus interchangeably', () => {\n        const minuses = [\n            {\n                value: CHAR_HYPHEN,\n                name: 'hyphen',\n            },\n            {\n                value: CHAR_EN_DASH,\n                name: 'en-dash',\n            },\n            {\n                value: CHAR_EM_DASH,\n                name: 'em-dash',\n            },\n        ];\n\n        const numbers = ['321', '2_432'];\n\n        minuses.forEach((minus) => {\n            pseudoMinuses.forEach((pseudoMinus) => {\n                numbers.forEach((number) => {\n                    it(`transforms ${pseudoMinus.name} into ${minus.name}`, () => {\n                        openNumberPage(\n                            `maximumFractionDigits=Infinity&thousandSeparator=_&minusSign=${encodeURIComponent(minus.value)}`,\n                        );\n                        cy.get('@input')\n                            .type(`${pseudoMinus.value}${number}`)\n                            .should('have.value', `${minus.value}${number}`);\n                    });\n                });\n            });\n        });\n    });\n\n    describe('can use letters as minus sign', () => {\n        beforeEach(() => {\n            openNumberPage(\n                'maximumFractionDigits=Infinity&thousandSeparator=_&minusSign=i',\n            );\n        });\n\n        it('transforms i into i', () => {\n            cy.get('@input').type('i1234').should('have.value', 'i1_234');\n        });\n\n        pseudoMinuses.forEach((pseudoMinus) => {\n            it(`transforms ${pseudoMinus.name} into i`, () => {\n                cy.get('@input')\n                    .type(`${pseudoMinus.value}1234`)\n                    .should('have.value', 'i1_234');\n            });\n        });\n    });\n});\n\ndescribe('custom minus should work properly with min(max) value', () => {\n    [\n        {value: CHAR_HYPHEN, name: 'hyphen'},\n        {value: CHAR_EN_DASH, name: 'en-dash'},\n        {value: CHAR_EM_DASH, name: 'em-dash'},\n        {\n            value: CHAR_JP_HYPHEN,\n            name: 'japanese prolonged sound mark',\n        },\n        {value: CHAR_MINUS, name: 'unicode minus sign'},\n        {value: 'x', name: 'x'},\n    ].forEach((minus) => {\n        describe(`applies ${minus.name} properly`, () => {\n            beforeEach(() => {\n                openNumberPage(\n                    `min=-123&thousandSeparator=_&minusSign=${encodeURIComponent(minus.value)}`,\n                );\n            });\n\n            it(`-94 => ${minus.value}94`, () => {\n                cy.get('@input')\n                    .type(`${minus.value}94`)\n                    .should('have.value', `${minus.value}94`);\n            });\n\n            it(`-432 => ${minus.value}123`, () => {\n                cy.get('@input')\n                    .type(`${minus.value}432`)\n                    .should('have.value', `${minus.value}123`);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-precision.cy.ts",
    "content": "import {openNumberPage} from './utils';\n\ndescribe('Number | maximumFractionDigits', () => {\n    describe('forbids to type more fractional digits than `maximumFractionDigits` (it is equal to 4)', () => {\n        beforeEach(() => {\n            openNumberPage('decimalSeparator=,&maximumFractionDigits=4');\n        });\n\n        it('Empty input => Type 0,123456789 => 0,1234', () => {\n            cy.get('@input')\n                .type('0,1234')\n                .should('have.value', '0,1234')\n                .should('have.prop', 'selectionStart', '0,1234'.length)\n                .should('have.prop', 'selectionEnd', '0,1234'.length);\n        });\n\n        it('Empty input => Type 0,4242000000 => 0,4242', () => {\n            cy.get('@input')\n                .type('0,4242000000')\n                .should('have.value', '0,4242')\n                .should('have.prop', 'selectionStart', '0,4242'.length)\n                .should('have.prop', 'selectionEnd', '0,4242'.length);\n        });\n\n        it('Empty input => Type 0,42420000001 => 0,4242', () => {\n            cy.get('@input')\n                .type('0,42420000001')\n                .should('have.value', '0,4242')\n                .should('have.prop', 'selectionStart', '0,4242'.length)\n                .should('have.prop', 'selectionEnd', '0,4242'.length);\n        });\n\n        [',', '.', 'б', 'ю'].forEach((separator) => {\n            it(`123|456789 => Type ${separator} => 123,4567`, () => {\n                cy.get('@input')\n                    .type('123|456789')\n                    .type('{moveToStart}')\n                    .type('{rightArrow}'.repeat(3))\n                    .should('have.value', '123 456 789')\n                    .should('have.prop', 'selectionStart', '123'.length)\n                    .should('have.prop', 'selectionEnd', '123'.length)\n                    .type(separator)\n                    .should('have.value', '123,4567')\n                    .should('have.prop', 'selectionStart', '123,'.length)\n                    .should('have.prop', 'selectionEnd', '123,'.length);\n            });\n        });\n    });\n\n    describe('rejects decimal separator if `maximumFractionDigits` is equal to 0', () => {\n        it('empty input => Type \",\" => Empty input', () => {\n            openNumberPage('decimalSeparator=,&maximumFractionDigits=0');\n            cy.get('@input').type(',').should('have.value', '');\n        });\n\n        it('Type \"5,\" => \"5\"', () => {\n            openNumberPage('decimalSeparator=,&maximumFractionDigits=0');\n\n            cy.get('@input')\n                .type('5,')\n                .should('have.value', '5')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        describe('dont rejects thousand separator if it is equal to decimal separator (for maximumFractionDigits=0 value of decimal separator does not matter)', () => {\n            it('simple typing', () => {\n                openNumberPage(\n                    'maximumFractionDigits=0&thousandSeparator=.&decimalSeparator=.',\n                );\n\n                cy.get('@input')\n                    .type('1234')\n                    .should('have.value', '1.234')\n                    .should('have.prop', 'selectionStart', '1.234'.length)\n                    .should('have.prop', 'selectionEnd', '1.234'.length);\n            });\n\n            it('paste from clipboard', () => {\n                openNumberPage(\n                    'maximumFractionDigits=0&thousandSeparator=.&decimalSeparator=.',\n                );\n\n                cy.get('@input')\n                    .paste('1.234')\n                    .should('have.value', '1.234')\n                    .should('have.prop', 'selectionStart', '1.234'.length)\n                    .should('have.prop', 'selectionEnd', '1.234'.length);\n            });\n        });\n    });\n\n    describe('keeps untouched decimal part if `maximumFractionDigits: Infinity`', () => {\n        it('0,123456789', () => {\n            openNumberPage('decimalSeparator=,&maximumFractionDigits=Infinity');\n\n            cy.get('@input')\n                .type('0,123456789')\n                .should('have.value', '0,123456789')\n                .should('have.prop', 'selectionStart', '0,123456789'.length)\n                .should('have.prop', 'selectionEnd', '0,123456789'.length);\n        });\n\n        it('0,0000000001', () => {\n            openNumberPage('decimalSeparator=,&maximumFractionDigits=Infinity');\n\n            cy.get('@input')\n                .type('0,0000000001') // 1e-10\n                .should('have.value', '0,0000000001')\n                .should('have.prop', 'selectionStart', '0,0000000001'.length)\n                .should('have.prop', 'selectionEnd', '0,0000000001'.length)\n                .blur()\n                .wait(100) // to be sure that value is not changed even in case of some async validation\n                .should('have.value', '0,0000000001');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-prefix-postfix.cy.ts",
    "content": "import {\n    CHAR_EM_DASH,\n    CHAR_EN_DASH,\n    CHAR_HYPHEN,\n    CHAR_MINUS,\n} from 'projects/kit/src/lib/constants';\n\nimport {openNumberPage} from './utils';\n\ndescribe('Number | Prefix & Postfix', () => {\n    describe('[prefix]=\"$\" | [postfix]=\" per day\"', () => {\n        beforeEach(() => {\n            openNumberPage(\n                'decimalSeparator=.&thousandSeparator=_&maximumFractionDigits=2&prefix=$',\n            );\n\n            cy.get('tr')\n                .contains('[postfix]')\n                .parents('tr')\n                .find('input')\n                .type(' per day');\n\n            cy.get('@input')\n                .focus()\n                .should('have.value', '$ per day')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        describe('thousand separators correctly works with prefix', () => {\n            it('Type 1000000', () => {\n                cy.get('@input')\n                    .type('1000000')\n                    .should('have.value', '$1_000_000 per day')\n                    .should('have.prop', 'selectionStart', '$1_000_000'.length)\n                    .should('have.prop', 'selectionEnd', '$1_000_000'.length);\n            });\n\n            it('$1_000_000| per day => Backspace => Backspace x2', () => {\n                cy.get('@input')\n                    .type('1000000')\n                    .type('{backspace}')\n                    .should('have.value', '$100_000 per day')\n                    .should('have.prop', 'selectionStart', '$100_000'.length)\n                    .should('have.prop', 'selectionEnd', '$100_000'.length)\n                    .type('{backspace}'.repeat(2))\n                    .should('have.value', '$1_000 per day')\n                    .should('have.prop', 'selectionStart', '$1_000'.length)\n                    .should('have.prop', 'selectionEnd', '$1_000'.length)\n                    .type('{backspace}')\n                    .should('have.value', '$100 per day')\n                    .should('have.prop', 'selectionStart', '$100'.length)\n                    .should('have.prop', 'selectionEnd', '$100'.length);\n            });\n\n            it('$|1_234 per day => Del => $|234 per day', () => {\n                cy.get('@input')\n                    .type('1234')\n                    .should('have.value', '$1_234 per day')\n                    .should('have.prop', 'selectionStart', '$1_234'.length)\n                    .should('have.prop', 'selectionEnd', '$1_234'.length)\n                    .type('{moveToStart}{del}')\n                    .should('have.value', '$234 per day')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            it('$1_2|34 per day => Del => $|234 per day', () => {\n                cy.get('@input')\n                    .type('1234')\n                    .should('have.value', '$1_234 per day')\n                    .should('have.prop', 'selectionStart', '$1_234'.length)\n                    .should('have.prop', 'selectionEnd', '$1_234'.length)\n                    .type('{leftArrow}{leftArrow}{backspace}')\n                    .should('have.value', '$134 per day')\n                    .should('have.prop', 'selectionStart', '$1'.length)\n                    .should('have.prop', 'selectionEnd', '$1'.length);\n            });\n        });\n\n        it('pads integer part with zero if user types decimal separator (for empty input)', () => {\n            cy.get('@input')\n                .type('.45')\n                .should('have.value', '$0.45 per day')\n                .should('have.prop', 'selectionStart', '$0.45'.length)\n                .should('have.prop', 'selectionEnd', '$0.45'.length);\n        });\n\n        it('[maximumFractionDigits] works', () => {\n            cy.get('@input')\n                .type('.12345678')\n                .should('have.value', '$0.12 per day')\n                .should('have.prop', 'selectionStart', '$0.12'.length)\n                .should('have.prop', 'selectionEnd', '$0.12'.length);\n        });\n\n        describe('it removes repeated leading zeroes for integer part on blur', () => {\n            it('Type 000000 => blur => $0| per day', () => {\n                cy.get('@input')\n                    .type('000000')\n                    .should('have.value', '$000_000 per day')\n                    .should('have.prop', 'selectionStart', '$000_000'.length)\n                    .should('have.prop', 'selectionEnd', '$000_000'.length)\n                    .blur()\n                    .should('have.value', '$0 per day');\n            });\n\n            it('$0| per day => Type 5 => blur => $5| per day', () => {\n                cy.get('@input')\n                    .type('0')\n                    .should('have.value', '$0 per day')\n                    .should('have.prop', 'selectionStart', '$0'.length)\n                    .should('have.prop', 'selectionEnd', '$0'.length)\n                    .type('5')\n                    .should('have.value', '$05 per day')\n                    .should('have.prop', 'selectionStart', '$05'.length)\n                    .should('have.prop', 'selectionEnd', '$05'.length)\n                    .blur()\n                    .should('have.value', '$5 per day');\n            });\n\n            it('$0.|05 per day => Backspace => blur => $|5 per day', () => {\n                cy.get('@input')\n                    .type('0.05')\n                    .type('{leftArrow}'.repeat('05'.length))\n                    .type('{backspace}')\n                    .should('have.value', '$005 per day')\n                    .should('have.prop', 'selectionStart', '$0'.length)\n                    .should('have.prop', 'selectionEnd', '$0'.length)\n                    .blur()\n                    .should('have.value', '$5 per day');\n            });\n        });\n\n        describe('cannot erase prefix', () => {\n            it('Select all + Backspace', () => {\n                cy.get('@input')\n                    .type('{selectAll}{backspace}')\n                    .should('have.value', '$ per day')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            it('$|42 per day => Backspace => $|42 per day', () => {\n                cy.get('@input')\n                    .type('42')\n                    .type('{moveToStart}')\n                    .should('have.prop', 'selectionStart', '$'.length)\n                    .should('have.prop', 'selectionEnd', '$'.length)\n                    .type('{backspace}'.repeat(5))\n                    .should('have.value', '$42 per day')\n                    .should('have.prop', 'selectionStart', '$'.length)\n                    .should('have.prop', 'selectionEnd', '$'.length);\n            });\n        });\n\n        describe('cannot erase postfix', () => {\n            it('Select all + Delete', () => {\n                cy.get('@input')\n                    .type('{selectAll}{del}')\n                    .should('have.value', '$ per day')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            it('$42| per day => Delete x4 => $42| per day', () => {\n                cy.get('@input')\n                    .type('42')\n                    .type('{moveToEnd}')\n                    .should('have.prop', 'selectionStart', '$42'.length)\n                    .should('have.prop', 'selectionEnd', '$42'.length)\n                    .type('{del}'.repeat(4))\n                    .should('have.value', '$42 per day')\n                    .should('have.prop', 'selectionStart', '$42'.length)\n                    .should('have.prop', 'selectionEnd', '$42'.length);\n            });\n        });\n\n        describe('with maskitoCaretGuard', () => {\n            it('$|42 per day => ArrowLeft => $|42 per day', () => {\n                cy.get('@input')\n                    .type('42')\n                    .type('{moveToStart}')\n                    .type('{leftArrow}'.repeat(3))\n                    .should('have.value', '$42 per day')\n                    .should('have.prop', 'selectionStart', '$'.length)\n                    .should('have.prop', 'selectionEnd', '$'.length);\n            });\n        });\n    });\n\n    describe('prefix/postfix ends/starts with the same character', () => {\n        describe('[prefix]=\"$_\" | [postfix]=\"_per_day\" (with caret guard)', () => {\n            beforeEach(() => {\n                openNumberPage('prefix=$_&postfix=_per_day');\n\n                cy.get('@input')\n                    .should('have.value', '$__per_day')\n                    .should('have.prop', 'selectionStart', '$_'.length)\n                    .should('have.prop', 'selectionEnd', '$_'.length);\n            });\n\n            it('$_|_per_day => Type Backspace => $_|_per_day', () => {\n                cy.get('@input')\n                    .type('{backspace}')\n                    .should('have.value', '$__per_day')\n                    .should('have.prop', 'selectionStart', '$_'.length)\n                    .should('have.prop', 'selectionEnd', '$_'.length);\n            });\n\n            it('$_|_per_day => Type Delete => $_|_per_day', () => {\n                cy.get('@input')\n                    .type('{del}')\n                    .should('have.value', '$__per_day')\n                    .should('have.prop', 'selectionStart', '$_'.length)\n                    .should('have.prop', 'selectionEnd', '$_'.length);\n            });\n\n            it('$_|_per_day => Select all + Delete => $_|_per_day', () => {\n                cy.get('@input')\n                    .type('{selectAll}{del}')\n                    .should('have.value', '$__per_day')\n                    .should('have.prop', 'selectionStart', '$_'.length)\n                    .should('have.prop', 'selectionEnd', '$_'.length);\n            });\n        });\n    });\n\n    describe('prefix ends with the same character as postfix starts', () => {\n        const prefix = 'lbs.​'; // padded with zero-width space\n\n        beforeEach(() => {\n            openNumberPage('prefix=lbs.&maximumFractionDigits=2');\n\n            cy.get('@input')\n                .focus()\n                .should('have.value', prefix)\n                .should('have.prop', 'selectionStart', prefix.length)\n                .should('have.prop', 'selectionEnd', prefix.length);\n        });\n\n        it('lbs.| => Type Backspace (attempt to erase zero-width space) => lbs.|', () => {\n            cy.get('@input')\n                .type('{backspace}')\n                .should('have.value', prefix)\n                .should('have.prop', 'selectionStart', prefix.length)\n                .should('have.prop', 'selectionEnd', prefix.length);\n        });\n\n        it('lbs.| => Type 42 => lbs.42|', () => {\n            cy.get('@input')\n                .type('42')\n                .should('have.value', `${prefix}42`)\n                .should('have.prop', 'selectionStart', `${prefix}42`.length)\n                .should('have.prop', 'selectionEnd', `${prefix}42`.length);\n        });\n\n        it('lbs.| => Type .42 => lbs.0.42|', () => {\n            cy.get('@input')\n                .type('.42')\n                .should('have.value', `${prefix}0.42`)\n                .should('have.prop', 'selectionStart', `${prefix}0.42`.length)\n                .should('have.prop', 'selectionEnd', `${prefix}0.42`.length);\n        });\n\n        it('lbs.0|.42 => Backspace + Blur => lbs.0.42', () => {\n            cy.get('@input')\n                .type('0.42')\n                .type('{leftArrow}'.repeat('.42'.length))\n                .type('{backspace}')\n                .should('have.value', `${prefix}.42`)\n                .should('have.prop', 'selectionStart', prefix.length)\n                .should('have.prop', 'selectionEnd', prefix.length)\n                .blur()\n                .wait(100) // to be sure that value is not changed even in case of some async validation\n                .should('have.value', `${prefix}0.42`);\n        });\n    });\n\n    describe('non-erasable minus (as [prefix]) for [max] <= 0', () => {\n        beforeEach(() => {\n            openNumberPage(`prefix=${encodeURIComponent(CHAR_MINUS)}&max=0`);\n        });\n\n        it('shows minus sign on focus', () => {\n            cy.get('@input').focus().should('have.value', CHAR_MINUS);\n        });\n\n        it('hides minus sign on blur', () => {\n            cy.get('@input')\n                .focus()\n                .should('have.value', CHAR_MINUS)\n                .blur()\n                .should('have.value', '');\n        });\n\n        it('forbids to enter more minuses', () => {\n            cy.get('@input')\n                .focus()\n                .type(`${CHAR_MINUS}${CHAR_HYPHEN}${CHAR_EN_DASH}${CHAR_EM_DASH}`)\n                .should('have.value', CHAR_MINUS);\n        });\n\n        it('allows to enter 123 => Textfield value is -123', () => {\n            cy.get('@input').focus().type('123').should('have.value', `${CHAR_MINUS}123`);\n        });\n\n        it('Enter 123 and blur', () => {\n            cy.get('@input')\n                .focus()\n                .type('123')\n                .blur()\n                .should('have.value', `${CHAR_MINUS}123`);\n        });\n\n        describe('forbids all attempts to erase minus sign', () => {\n            it('Select all + Backspace', () => {\n                cy.get('@input')\n                    .focus()\n                    .type('{selectAll}{backspace}')\n                    .should('have.value', CHAR_MINUS)\n                    .type('123')\n                    .should('have.value', `${CHAR_MINUS}123`)\n                    .type('{selectAll}{backspace}')\n                    .should('have.value', CHAR_MINUS);\n            });\n\n            it('Select all + Delete', () => {\n                cy.get('@input')\n                    .focus()\n                    .type('{selectAll}{del}')\n                    .should('have.value', CHAR_MINUS)\n                    .type('123')\n                    .should('have.value', `${CHAR_MINUS}123`)\n                    .type('{selectAll}{del}')\n                    .should('have.value', CHAR_MINUS);\n            });\n\n            it('Backspace', () => {\n                cy.get('@input')\n                    .focus()\n                    .type('123')\n                    .should('have.value', `${CHAR_MINUS}123`)\n                    .type('{backspace}'.repeat(10))\n                    .should('have.value', CHAR_MINUS);\n            });\n        });\n\n        it('Impossible to move caret before minus sign', () => {\n            cy.get('@input')\n                .focus()\n                .should('have.value', CHAR_MINUS)\n                .type('{moveToStart}')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1)\n                .type('{leftArrow}')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n    });\n\n    describe('postfix consists of many characters `lbs_per_day`', () => {\n        it('Paste 100 + incomplete postfix', () => {\n            openNumberPage('postfix=lbs_per_day');\n\n            cy.get('@input')\n                .focus()\n                .should('have.value', 'lbs_per_day')\n                .paste('100lbs')\n                .should('have.value', '100lbs_per_day')\n                .should('have.prop', 'selectionStart', '100'.length)\n                .should('have.prop', 'selectionEnd', '100'.length);\n        });\n    });\n\n    describe('postfix starts with point and contains digits ([postfix]=\".000 km\" & [maximumFractionDigits]=\"0\")`', () => {\n        beforeEach(() => {\n            openNumberPage('postfix=.000%20km&maximumFractionDigits=0');\n        });\n\n        it('Adds postfix on focus', () => {\n            cy.get('@input')\n                .focus()\n                .should('have.value', '.000 km')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('Removes postfix on blur (for empty value)', () => {\n            cy.get('@input')\n                .focus()\n                .should('have.value', '.000 km')\n                .blur()\n                .should('have.value', '');\n        });\n\n        it('Type 1 => 1.000 km', () => {\n            cy.get('@input')\n                .focus()\n                .type('1')\n                .should('have.value', '1.000 km')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        it('Type 123 => 123.000 km', () => {\n            cy.get('@input')\n                .focus()\n                .type('123')\n                .should('have.value', '123.000 km')\n                .should('have.prop', 'selectionStart', 3)\n                .should('have.prop', 'selectionEnd', 3);\n        });\n\n        it('Type 123456789 => 123 456 789.000 km', () => {\n            cy.get('@input')\n                .focus()\n                .type('123456789')\n                .should('have.value', '123 456 789.000 km')\n                .should('have.prop', 'selectionStart', '123 456 789'.length)\n                .should('have.prop', 'selectionEnd', '123 456 789'.length);\n        });\n\n        it('Type 100 => 100.000 km', () => {\n            cy.get('@input')\n                .focus()\n                .type('100')\n                .should('have.value', '100.000 km')\n                .should('have.prop', 'selectionStart', '100'.length)\n                .should('have.prop', 'selectionEnd', '100'.length);\n        });\n\n        it('1|.000 km => Backspace => |.000 km', () => {\n            cy.get('@input')\n                .focus()\n                .type('1')\n                .should('have.value', '1.000 km')\n                .type('{backspace}')\n                .should('have.value', '.000 km')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('|1.000 km => Delete => |.000 km', () => {\n            cy.get('@input')\n                .focus()\n                .type('1')\n                .type('{moveToStart}')\n                .should('have.value', '1.000 km')\n                .type('{del}')\n                .should('have.value', '.000 km')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('123.000 km => select all + Delete => |.000 km', () => {\n            cy.get('@input')\n                .focus()\n                .type('123')\n                .should('have.value', '123.000 km')\n                .type('{selectAll}{del}')\n                .should('have.value', '.000 km')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('Allows to enter leading zeroes', () => {\n            cy.get('@input')\n                .focus()\n                .type('000')\n                .should('have.value', '000.000 km')\n                .should('have.prop', 'selectionStart', '000'.length)\n                .should('have.prop', 'selectionEnd', '000'.length);\n        });\n\n        it('Removes duplicated leading zeroes on blur', () => {\n            cy.get('@input')\n                .focus()\n                .type('000')\n                .should('have.value', '000.000 km')\n                .blur()\n                .should('have.value', '0.000 km');\n        });\n\n        it('Ignores typing decimal separator (.) at start', () => {\n            cy.get('@input')\n                .focus()\n                .should('have.value', '.000 km')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0)\n                .type('.')\n                .should('have.value', '.000 km')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('Non-digit characters are ignored', () => {\n            cy.get('@input')\n                .focus()\n                .type('abc!@#')\n                .should('have.value', '.000 km')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('123.000 km => Select all + Type 4 => 4.000 km', () => {\n            cy.get('@input')\n                .focus()\n                .type('123')\n                .should('have.value', '123.000 km')\n                .type('{selectAll}4')\n                .should('have.value', '4.000 km')\n                .should('have.prop', 'selectionStart', '4'.length)\n                .should('have.prop', 'selectionEnd', '4'.length);\n        });\n\n        it('Caret guard: cannot move caret into postfix', () => {\n            cy.get('@input')\n                .focus()\n                .type('123')\n                .should('have.value', '123.000 km')\n                .type('{moveToEnd}')\n                .should('have.prop', 'selectionStart', '123'.length)\n                .should('have.prop', 'selectionEnd', '123'.length)\n                .type('{rightArrow}'.repeat(5))\n                .should('have.prop', 'selectionStart', '123'.length)\n                .should('have.prop', 'selectionEnd', '123'.length);\n        });\n\n        it('|.000 km => Backspace keeps value and caret', () => {\n            cy.get('@input')\n                .focus()\n                .should('have.value', '.000 km')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0)\n                .type('{backspace}'.repeat(5))\n                .should('have.value', '.000 km')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('|.000 km => Delete keeps value and caret', () => {\n            cy.get('@input')\n                .focus()\n                .should('have.value', '.000 km')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0)\n                .type('{del}')\n                .should('have.value', '.000 km')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('Paste with leading text: abc123 => 123.000 km', () => {\n            cy.get('@input')\n                .focus()\n                .paste('abc123')\n                .should('have.value', '123.000 km')\n                .should('have.prop', 'selectionStart', '123'.length)\n                .should('have.prop', 'selectionEnd', '123'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-thousand-separator.cy.ts",
    "content": "import {openNumberPage} from './utils';\n\ndescribe('Number | thousandSeparator', () => {\n    describe('adds thousand separator after pressing new digit', () => {\n        beforeEach(() => {\n            openNumberPage('thousandSeparator=-');\n        });\n\n        const tests = [\n            // [Typed value, Masked value]\n            ['1', '1'],\n            ['10', '10'],\n            ['100', '100'],\n            ['1000', '1-000'],\n            ['10000', '10-000'],\n            ['100000', '100-000'],\n            ['1000000', '1-000-000'],\n        ] as const;\n\n        tests.forEach(([typedValue, maskedValue]) => {\n            it(`Type \"${typedValue}\" => \"${maskedValue}\"`, () => {\n                cy.get('@input')\n                    .type(typedValue)\n                    .should('have.value', maskedValue)\n                    .should('have.prop', 'selectionStart', maskedValue.length)\n                    .should('have.prop', 'selectionEnd', maskedValue.length);\n            });\n        });\n    });\n\n    describe('move thousand separator after deleting a digit (initial: 1-000-000)', () => {\n        beforeEach(() => {\n            openNumberPage('thousandSeparator=-');\n            cy.get('@input').type('1000000');\n        });\n\n        const tests = [\n            // [How many times \"Backspace\"-key was pressed, Masked value]\n            [1, '100-000'],\n            [2, '10-000'],\n            [3, '1-000'],\n            [4, '100'],\n            [5, '10'],\n            [6, '1'],\n        ] as const;\n\n        tests.forEach(([n, maskedValue]) => {\n            it(`Backspace x${n} => \"${maskedValue}\"`, () => {\n                cy.get('@input')\n                    .type('{backspace}'.repeat(n))\n                    .should('have.value', maskedValue)\n                    .should('have.prop', 'selectionStart', maskedValue.length)\n                    .should('have.prop', 'selectionEnd', maskedValue.length);\n            });\n        });\n    });\n\n    describe('Editing somewhere in the middle of a value (NOT the last character)', () => {\n        beforeEach(() => {\n            openNumberPage('thousandSeparator=-');\n        });\n\n        it('1-00|0-000 => Backspace => 10|0-000 => Type \"5\" => 1-05|0-000', () => {\n            cy.get('@input')\n                .type('1000000')\n                .type('{leftArrow}'.repeat('0-000'.length))\n                .should('have.prop', 'selectionStart', '1-00'.length)\n                .should('have.prop', 'selectionEnd', '1-00'.length)\n                .type('{backspace}')\n                .should('have.value', '100-000')\n                .should('have.prop', 'selectionStart', '10'.length)\n                .should('have.prop', 'selectionEnd', '10'.length)\n                .type('5')\n                .should('have.value', '1-050-000')\n                .should('have.prop', 'selectionStart', '1-05'.length)\n                .should('have.prop', 'selectionEnd', '1-05'.length);\n        });\n\n        it('1-000-|000 => Backspace => 1-000|-000', () => {\n            cy.get('@input')\n                .type('1000000')\n                .type('{leftArrow}'.repeat('000'.length))\n                .should('have.prop', 'selectionStart', '1-000-'.length)\n                .should('have.prop', 'selectionEnd', '1-000-'.length)\n                .type('{backspace}')\n                .should('have.value', '1-000-000')\n                .should('have.prop', 'selectionStart', '1-000'.length)\n                .should('have.prop', 'selectionEnd', '1-000'.length);\n        });\n\n        it('1-000|-000 => Delete => 1-000-|000', () => {\n            cy.get('@input')\n                .type('1000000')\n                .type('{leftArrow}'.repeat('-000'.length))\n                .should('have.prop', 'selectionStart', '1-000'.length)\n                .should('have.prop', 'selectionEnd', '1-000'.length)\n                .type('{del}')\n                .should('have.value', '1-000-000')\n                .should('have.prop', 'selectionStart', '1-000-'.length)\n                .should('have.prop', 'selectionEnd', '1-000-'.length);\n        });\n\n        it('100-|000 => Delete => 10-0|00 => Type 5 => 100-5|00', () => {\n            cy.get('@input')\n                .type('100000')\n                .type('{leftArrow}'.repeat('000'.length))\n                .should('have.prop', 'selectionStart', '100-'.length)\n                .should('have.prop', 'selectionEnd', '100-'.length)\n                .type('{del}')\n                .should('have.value', '10-000')\n                .should('have.prop', 'selectionStart', '10-0'.length)\n                .should('have.prop', 'selectionEnd', '10-0'.length)\n                .type('5')\n                .should('have.value', '100-500')\n                .should('have.prop', 'selectionStart', '100-5'.length)\n                .should('have.prop', 'selectionEnd', '100-5'.length);\n        });\n\n        it('100-|000 => Delete x3 => 100', () => {\n            cy.get('@input')\n                .type('100000')\n                .type('{leftArrow}'.repeat('000'.length))\n                .should('have.prop', 'selectionStart', '100-'.length)\n                .should('have.prop', 'selectionEnd', '100-'.length)\n                .type('{del}'.repeat(3))\n                .should('have.value', '100')\n                .should('have.prop', 'selectionStart', '100'.length)\n                .should('have.prop', 'selectionEnd', '100'.length);\n        });\n\n        it('1|-234-567 => Backspace => 234-567', () => {\n            cy.get('@input')\n                .type('1234567')\n                .type('{moveToStart}{rightArrow}')\n                .should('have.value', '1-234-567')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1)\n                .type('{backspace}')\n                .should('have.value', '234-567')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n    });\n\n    it('allows to set empty string as thousand separator', () => {\n        openNumberPage('thousandSeparator=-');\n\n        cy.get('tr').contains('[thousandSeparator]').parents('tr').find('input').clear();\n\n        cy.get('@input')\n            .type('1000000')\n            .should('have.value', '1000000')\n            .should('have.prop', 'selectionStart', '1000000'.length)\n            .should('have.prop', 'selectionEnd', '1000000'.length);\n    });\n\n    describe('prevent insertion of extra spaces (thousand separator is equal to non-breaking space) on invalid positions', () => {\n        beforeEach(() => openNumberPage());\n\n        it('paste value with extra leading and trailing spaces', () => {\n            cy.get('@input')\n                .paste('    123456    ')\n                .should('have.value', '123 456')\n                .should('have.prop', 'selectionStart', '123 456'.length)\n                .should('have.prop', 'selectionEnd', '123 456'.length);\n        });\n\n        it('|123 => Press space => |123', () => {\n            cy.get('@input')\n                .type('123')\n                .type('{moveToStart}')\n                .type(' ')\n                .should('have.value', '123')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('1|23 => Press space => 1|23', () => {\n            cy.get('@input')\n                .type('123')\n                .type('{moveToStart}')\n                .type('{rightArrow}')\n                .type(' ')\n                .should('have.value', '123')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        it('12|3 => Press space => 12|3', () => {\n            cy.get('@input')\n                .type('123')\n                .type('{moveToStart}')\n                .type('{rightArrow}'.repeat(2))\n                .type(' ')\n                .should('have.value', '123')\n                .should('have.prop', 'selectionStart', 2)\n                .should('have.prop', 'selectionEnd', 2);\n        });\n\n        it('123| => Press space => 123|', () => {\n            cy.get('@input')\n                .type('123')\n                .type('{moveToEnd}')\n                .type(' ')\n                .should('have.value', '123')\n                .should('have.prop', 'selectionStart', 3)\n                .should('have.prop', 'selectionEnd', 3);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/number-zero-integer-part.cy.ts",
    "content": "import {openNumberPage} from './utils';\n\ndescribe('Number | Zero integer part', () => {\n    describe('User types decimal separator when input is empty (decimalSeparator=\",\" && maximumFractionDigits=2)', () => {\n        describe('without prefix / postfix', () => {\n            beforeEach(() => {\n                openNumberPage(\n                    'thousandSeparator=_&decimalSeparator=,&maximumFractionDigits=2',\n                );\n            });\n\n            it('Empty input => Type \",\" (decimal separator) => value is equal \"0,\"', () => {\n                cy.get('@input')\n                    .type(',')\n                    .should('have.value', '0,')\n                    .should('have.prop', 'selectionStart', '0,'.length)\n                    .should('have.prop', 'selectionEnd', '0,'.length);\n            });\n\n            it('Input has only minus sign => Type \",\" (decimal separator) => value is equal \"-0,|\"', () => {\n                cy.get('@input')\n                    .type('-,')\n                    .should('have.value', '−0,')\n                    .should('have.prop', 'selectionStart', '−0,'.length)\n                    .should('have.prop', 'selectionEnd', '−0,'.length);\n            });\n\n            it('Empty input => Type \".\" (pseudo decimal separator) => value is equal \"0,\"', () => {\n                cy.get('@input')\n                    .type('.')\n                    .should('have.value', '0,')\n                    .should('have.prop', 'selectionStart', '0,'.length)\n                    .should('have.prop', 'selectionEnd', '0,'.length);\n            });\n\n            it('Input has only minus sign => Type \".\" (pseudo decimal separator) => value is equal \"-0,|\"', () => {\n                cy.get('@input')\n                    .type('-.')\n                    .should('have.value', '−0,')\n                    .should('have.prop', 'selectionStart', '−0,'.length)\n                    .should('have.prop', 'selectionEnd', '−0,'.length);\n            });\n\n            it('Empty input => Type \"ю\" (pseudo decimal separator) => value is equal \"0,\"', () => {\n                cy.get('@input')\n                    .type('ю')\n                    .should('have.value', '0,')\n                    .should('have.prop', 'selectionStart', '0,'.length)\n                    .should('have.prop', 'selectionEnd', '0,'.length);\n            });\n\n            it('Empty input => Type \"ю\" (pseudo decimal separator) => value is equal \"-0,\"', () => {\n                cy.get('@input')\n                    .type('-ю')\n                    .should('have.value', '−0,')\n                    .should('have.prop', 'selectionStart', '−0,'.length)\n                    .should('have.prop', 'selectionEnd', '−0,'.length);\n            });\n\n            it('Textfield with any value => Select all => Type decimal separator => value is equal \"0,\"', () => {\n                cy.get('@input')\n                    .type(',')\n                    .should('have.value', '0,')\n                    .type('{selectall}')\n                    .type(',')\n                    .should('have.value', '0,')\n                    .should('have.prop', 'selectionStart', '0,'.length)\n                    .should('have.prop', 'selectionEnd', '0,'.length);\n            });\n\n            it('Textfield with any value => Select all => Type \".\" (pseudo decimal separator) => value is equal \"0,\"', () => {\n                cy.get('@input')\n                    .type('1,23')\n                    .should('have.value', '1,23')\n                    .type('{selectall}')\n                    .type('.')\n                    .should('have.value', '0,')\n                    .should('have.prop', 'selectionStart', '0,'.length)\n                    .should('have.prop', 'selectionEnd', '0,'.length);\n            });\n        });\n\n        describe('With prefix ($) & postfix (%)', () => {\n            beforeEach(() => {\n                openNumberPage(\n                    'thousandSeparator=_&decimalSeparator=,&maximumFractionDigits=2&prefix=$&postfix=kg',\n                );\n\n                cy.get('@input')\n                    .focused()\n                    .should('have.value', '$kg')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            it('Empty value (only prefix & postfix) => Type \",\" (decimal separator) => value is equal \"0,\"', () => {\n                cy.get('@input')\n                    .type(',')\n                    .should('have.value', '$0,kg')\n                    .should('have.prop', 'selectionStart', '$0,'.length)\n                    .should('have.prop', 'selectionEnd', '$0,'.length);\n            });\n\n            it('Empty value (only prefix & postfix) => Type \".\" (pseudo decimal separator) => value is equal \"0,\"', () => {\n                cy.get('@input')\n                    .type('.')\n                    .should('have.value', '$0,kg')\n                    .should('have.prop', 'selectionStart', '$0,'.length)\n                    .should('have.prop', 'selectionEnd', '$0,'.length);\n            });\n\n            it('Textfield with any value => Select all => Type decimal separator => value is equal \"$0,kg\"', () => {\n                cy.get('@input')\n                    .type('1,23')\n                    .should('have.value', '$1,23kg')\n                    .type('{selectall}')\n                    .type(',')\n                    .should('have.value', '$0,kg')\n                    .should('have.prop', 'selectionStart', '$0,'.length)\n                    .should('have.prop', 'selectionEnd', '$0,'.length);\n            });\n\n            it('Textfield with any value => Select all => Type pseudo decimal separator => value is equal \"$0,kg\"', () => {\n                cy.get('@input')\n                    .type('1,23')\n                    .should('have.value', '$1,23kg')\n                    .type('{selectall}')\n                    .type('.')\n                    .should('have.value', '$0,kg')\n                    .should('have.prop', 'selectionStart', '$0,'.length)\n                    .should('have.prop', 'selectionEnd', '$0,'.length);\n            });\n        });\n    });\n\n    describe('value cannot contain many leading zeroes after blur event', () => {\n        it('maximumFractionDigits = 2 & positive number', () => {\n            openNumberPage('thousandSeparator=_&maximumFractionDigits=2');\n\n            cy.get('@input')\n                .type('0000000')\n                .should('have.value', '0_000_000')\n                .should('have.prop', 'selectionStart', '0_000_000'.length)\n                .should('have.prop', 'selectionEnd', '0_000_000'.length)\n                .blur()\n                .should('have.value', '0');\n        });\n\n        it('maximumFractionDigits = 2 & negative number', () => {\n            openNumberPage('thousandSeparator=_&maximumFractionDigits=2');\n\n            cy.get('@input')\n                .type('-00000006')\n                .should('have.value', '−00_000_006')\n                .should('have.prop', 'selectionStart', '−00_000_006'.length)\n                .should('have.prop', 'selectionEnd', '−00_000_006'.length)\n                .blur()\n                .should('have.value', '−6');\n        });\n\n        it('maximumFractionDigits = 0', () => {\n            openNumberPage('thousandSeparator=_&maximumFractionDigits=0');\n\n            cy.get('@input')\n                .type('0000000')\n                .should('have.value', '0_000_000')\n                .should('have.prop', 'selectionStart', '0_000_000'.length)\n                .should('have.prop', 'selectionEnd', '0_000_000'.length)\n                .blur()\n                .should('have.value', '0');\n        });\n\n        it('1|-000-000 => Backspace => blur => 0', () => {\n            openNumberPage('thousandSeparator=_&maximumFractionDigits=2');\n\n            cy.get('@input')\n                .type('1000000')\n                .should('have.value', '1_000_000')\n                .type('{moveToStart}{rightArrow}')\n                .type('{backspace}')\n                .should('have.value', '000_000')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0)\n                .blur()\n                .should('have.value', '0');\n        });\n\n        it('remove leading zeroes (on blur only!) when decimal separator is removed (positive number)', () => {\n            openNumberPage(\n                'thousandSeparator=_&decimalSeparator=,&maximumFractionDigits=5',\n            );\n\n            cy.get('@input')\n                .type('0,0005')\n                .type('{moveToStart}')\n                .type('{rightArrow}'.repeat('0,'.length))\n                .type('{backspace}')\n                .should('have.value', '00_005')\n                .should('have.prop', 'selectionStart', '0'.length)\n                .should('have.prop', 'selectionEnd', '0'.length)\n                .blur()\n                .should('have.value', '5');\n        });\n\n        it('remove leading zeroes (on blur only!) when decimal separator is removed (negative number)', () => {\n            openNumberPage(\n                'thousandSeparator=_&decimalSeparator=,&maximumFractionDigits=5',\n            );\n\n            cy.get('@input')\n                .type('-0,0005')\n                .type('{moveToStart}')\n                .type('{rightArrow}'.repeat('-0,'.length))\n                .type('{backspace}')\n                .should('have.value', '−00_005')\n                .should('have.prop', 'selectionStart', '-0'.length)\n                .should('have.prop', 'selectionEnd', '-0'.length)\n                .blur()\n                .should('have.value', '−5');\n        });\n    });\n\n    describe('pads empty integer part with zero on blur (if decimal part exists)', () => {\n        describe('Without prefix', () => {\n            it('Positive number & decimal separator is comma', () => {\n                openNumberPage('decimalSeparator=,&maximumFractionDigits=2');\n\n                cy.get('@input')\n                    .type('1,23')\n                    .type('{moveToStart}{rightArrow}{backspace}')\n                    .blur()\n                    .should('have.value', '0,23');\n\n                cy.get('@input')\n                    .parents('tui-input')\n                    .should('have.ngControlValue', '0,23');\n            });\n\n            it('Negative number & decimal separator is dot', () => {\n                openNumberPage('decimalSeparator=.&maximumFractionDigits=2');\n\n                cy.get('@input')\n                    .type('-1.23')\n                    .type('{leftArrow}'.repeat('.23'.length))\n                    .type('{backspace}')\n                    .blur()\n                    .should('have.value', '−0.23');\n\n                cy.get('@input')\n                    .parents('tui-input')\n                    .should('have.ngControlValue', '−0.23');\n            });\n        });\n\n        describe('With prefix', () => {\n            it('Positive number & decimal separator is dot', () => {\n                openNumberPage('decimalSeparator=.&maximumFractionDigits=2&prefix=$');\n\n                cy.get('@input')\n                    .type('1.23')\n                    .type('{leftArrow}'.repeat('.23'.length))\n                    .type('{backspace}')\n                    .blur()\n                    .should('have.value', '$0.23');\n\n                cy.get('@input')\n                    .parents('tui-input')\n                    .should('have.ngControlValue', '$0.23');\n            });\n\n            it('Negative number & decimal separator is comma', () => {\n                openNumberPage('decimalSeparator=,&prefix=>&maximumFractionDigits=2');\n\n                cy.get('@input')\n                    .type('-1,23')\n                    .type('{leftArrow}'.repeat(',23'.length))\n                    .type('{backspace}')\n                    .blur()\n                    .should('have.value', '>−0,23');\n\n                cy.get('@input')\n                    .parents('tui-input')\n                    .should('have.ngControlValue', '>−0,23');\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/number/utils.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nexport function openNumberPage(queryParams = ''): void {\n    cy.visit(`/${DemoPath.Number}/API?${queryParams}`);\n    cy.get('#demo-content input')\n        .should('be.visible')\n        .first()\n        .focus()\n        .clear()\n        .as('input');\n}\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/time/time-affixes.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Time | Prefix & Postfix', () => {\n    describe('[postfix]=\" left\" + WITH caret guard', () => {\n        beforeEach(() => {\n            cy.visit(DemoPath.Time);\n            cy.get('#affixes input')\n                .first()\n                .should('have.value', '05:00 left')\n                .focus()\n                .as('textfield');\n        });\n\n        it('basic typing works', () => {\n            cy.get('@textfield')\n                .type('{moveToStart}')\n                .type('1234')\n                .should('have.value', '12:34 left')\n                .should('have.prop', 'selectionStart', '12:34'.length)\n                .should('have.prop', 'selectionEnd', '12:34'.length);\n        });\n\n        it('replaces deleted character by zero', () => {\n            cy.get('@textfield')\n                .type('{moveToStart}')\n                .type('{rightArrow}'.repeat(2))\n                .should('have.value', '05:00 left')\n                .should('have.prop', 'selectionStart', '05'.length)\n                .should('have.prop', 'selectionEnd', '05'.length)\n                .type('{backspace}')\n                .should('have.value', '00:00 left')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        it('zero-padding for minute segment works', () => {\n            cy.get('@textfield')\n                .type('{moveToStart}')\n                .type('9')\n                .should('have.value', '09:00 left')\n                .should('have.prop', 'selectionStart', '09:'.length)\n                .should('have.prop', 'selectionEnd', '09:'.length);\n        });\n\n        it('zero-padding for second segment works', () => {\n            cy.get('@textfield')\n                .type('{moveToStart}')\n                .type('97')\n                .should('have.value', '09:07 left')\n                .should('have.prop', 'selectionStart', '09:07'.length)\n                .should('have.prop', 'selectionEnd', '09:07'.length);\n        });\n\n        it('replaceAll + delete => only non-removable postfix', () => {\n            cy.get('@textfield')\n                .type('{selectAll}{backspace}')\n                .should('have.value', ' left')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('move caret left on attempt to remove colon by Backspace', () => {\n            cy.get('@textfield')\n                .type('{moveToStart}')\n                .type('{rightArrow}'.repeat(3))\n                .should('have.value', '05:00 left')\n                .should('have.prop', 'selectionStart', '05:'.length)\n                .should('have.prop', 'selectionEnd', '05:'.length)\n                .type('{backspace}')\n                .should('have.value', '05:00 left')\n                .should('have.prop', 'selectionStart', '05'.length)\n                .should('have.prop', 'selectionEnd', '05'.length);\n        });\n\n        it('move caret right on attempt to remove colon by Delete', () => {\n            cy.get('@textfield')\n                .type('{moveToStart}')\n                .type('{rightArrow}'.repeat(2))\n                .should('have.value', '05:00 left')\n                .should('have.prop', 'selectionStart', '05'.length)\n                .should('have.prop', 'selectionEnd', '05'.length)\n                .type('{del}')\n                .should('have.value', '05:00 left')\n                .should('have.prop', 'selectionStart', '05:'.length)\n                .should('have.prop', 'selectionEnd', '05:'.length);\n        });\n\n        it('allows to delete last digit without zero placeholder', () => {\n            cy.get('@textfield')\n                .type('{moveToStart}')\n                .type('1234')\n                .should('have.value', '12:34 left')\n                .should('have.prop', 'selectionStart', '12:34'.length)\n                .should('have.prop', 'selectionEnd', '12:34'.length)\n                .type('{backspace}')\n                .should('have.value', '12:3 left')\n                .should('have.prop', 'selectionStart', '12:3'.length)\n                .should('have.prop', 'selectionEnd', '12:3'.length)\n                .type('{backspace}')\n                .should('have.value', '12 left')\n                .should('have.prop', 'selectionStart', '12'.length)\n                .should('have.prop', 'selectionEnd', '12'.length)\n                .type('{backspace}')\n                .should('have.value', '1 left')\n                .should('have.prop', 'selectionStart', '1'.length)\n                .should('have.prop', 'selectionEnd', '1'.length)\n                .type('{backspace}')\n                .should('have.value', ' left')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        describe('with maskitoAddOnFocusPlugin + maskitoRemoveOnBlurPlugin', () => {\n            it('removes postfix on blur if there are no more digits except postfix', () => {\n                cy.get('@textfield')\n                    .type('{selectAll}{backspace}')\n                    .should('have.value', ' left')\n                    .blur()\n                    .should('have.value', '');\n            });\n\n            it('adds postfix on focus for empty textfield', () => {\n                cy.get('@textfield')\n                    .type('{selectAll}{backspace}')\n                    .blur()\n                    .should('have.value', '')\n                    .focus()\n                    .should('have.value', ' left');\n            });\n        });\n    });\n\n    describe('[postfix]=\"left\" + WITHOUT caret guard', () => {\n        beforeEach(() => {\n            cy.visit(`${DemoPath.Time}/API?mode=HH:MM:SS&postfix=left`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .should('have.value', '')\n                .first()\n                .focus()\n                .as('textfield');\n        });\n\n        it('basic typing works', () => {\n            cy.get('@textfield')\n                .type('123456')\n                .should('have.value', '12:34:56left')\n                .should('have.prop', 'selectionStart', '12:34:56'.length)\n                .should('have.prop', 'selectionEnd', '12:34:56'.length);\n        });\n\n        it('replaces deleted character by zero', () => {\n            cy.get('@textfield')\n                .type('123456')\n                .type('{leftArrow}'.repeat(':34:56'.length))\n                .type('{backspace}')\n                .should('have.value', '10:34:56left')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        it('zero-padding for hour segment works', () => {\n            cy.get('@textfield')\n                .type('9')\n                .should('have.value', '09left')\n                .should('have.prop', 'selectionStart', '09'.length)\n                .should('have.prop', 'selectionEnd', '09'.length);\n        });\n\n        it('zero-padding for minutes segment works', () => {\n            cy.get('@textfield')\n                .type('97')\n                .should('have.value', '09:07left')\n                .should('have.prop', 'selectionStart', '09:07'.length)\n                .should('have.prop', 'selectionEnd', '09:07'.length);\n        });\n\n        it('zero-padding for seconds segment works', () => {\n            cy.get('@textfield')\n                .type('976')\n                .should('have.value', '09:07:06left')\n                .should('have.prop', 'selectionStart', '09:07:06'.length)\n                .should('have.prop', 'selectionEnd', '09:07:06'.length);\n        });\n\n        it('replaceAll + delete => only non-removable postfix', () => {\n            cy.get('@textfield')\n                .type('123456')\n                .should('have.value', '12:34:56left')\n                .type('{selectAll}{backspace}')\n                .should('have.value', 'left')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('move caret left on attempt to remove colon by Backspace', () => {\n            cy.get('@textfield')\n                .type('123456')\n                .type('{leftArrow}'.repeat('56'.length))\n                .should('have.prop', 'selectionStart', '12:34:'.length)\n                .should('have.prop', 'selectionEnd', '12:34:'.length)\n                .type('{backspace}')\n                .should('have.value', '12:34:56left')\n                .should('have.prop', 'selectionStart', '12:34'.length)\n                .should('have.prop', 'selectionEnd', '12:34'.length);\n        });\n\n        it('move caret right on attempt to remove colon by Delete', () => {\n            cy.get('@textfield')\n                .type('1234')\n                .type('{leftArrow}'.repeat(':34'.length))\n                .should('have.prop', 'selectionStart', '12'.length)\n                .should('have.prop', 'selectionEnd', '12'.length)\n                .type('{del}')\n                .should('have.value', '12:34left')\n                .should('have.prop', 'selectionStart', '12:'.length)\n                .should('have.prop', 'selectionEnd', '12:'.length);\n        });\n\n        it('allows to delete last digit without zero placeholder', () => {\n            cy.get('@textfield')\n                .type('123456')\n                .should('have.value', '12:34:56left')\n                .should('have.prop', 'selectionStart', '12:34:56'.length)\n                .should('have.prop', 'selectionEnd', '12:34:56'.length)\n                .type('{backspace}'.repeat(3))\n                .should('have.value', '12:3left')\n                .should('have.prop', 'selectionStart', '12:3'.length)\n                .should('have.prop', 'selectionEnd', '12:3'.length)\n                .type('{backspace}')\n                .should('have.value', '12left')\n                .should('have.prop', 'selectionStart', '12'.length)\n                .should('have.prop', 'selectionEnd', '12'.length)\n                .type('{backspace}')\n                .should('have.value', '1left')\n                .should('have.prop', 'selectionStart', '1'.length)\n                .should('have.prop', 'selectionEnd', '1'.length)\n                .type('{backspace}')\n                .should('have.value', 'left')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n    });\n\n    describe('[prefix]=\"Timeout\"', () => {\n        beforeEach(() => {\n            cy.visit(`${DemoPath.Time}/API?mode=HH:MM:SS.MSS&prefix=Timeout`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .should('have.value', '')\n                .first()\n                .focus()\n                .as('textfield');\n        });\n\n        it('basic typing works', () => {\n            cy.get('@textfield')\n                .type('123456789')\n                .should('have.value', 'Timeout12:34:56.789')\n                .should('have.prop', 'selectionStart', 'Timeout12:34:56.789'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout12:34:56.789'.length);\n        });\n\n        it('replaces deleted character by zero', () => {\n            cy.get('@textfield')\n                .type('1234')\n                .type('{leftArrow}'.repeat(':34'.length))\n                .type('{backspace}')\n                .should('have.value', 'Timeout10:34')\n                .should('have.prop', 'selectionStart', 'Timeout1'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout1'.length);\n        });\n\n        it('zero-padding for hour segment works', () => {\n            cy.get('@textfield')\n                .type('9')\n                .should('have.value', 'Timeout09')\n                .should('have.prop', 'selectionStart', 'Timeout09'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout09'.length);\n        });\n\n        it('zero-padding for minutes segment works', () => {\n            cy.get('@textfield')\n                .type('97')\n                .should('have.value', 'Timeout09:07')\n                .should('have.prop', 'selectionStart', 'Timeout09:07'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout09:07'.length);\n        });\n\n        it('zero-padding for second segment works', () => {\n            cy.get('@textfield')\n                .type('976')\n                .should('have.value', 'Timeout09:07:06')\n                .should('have.prop', 'selectionStart', 'Timeout09:07:06'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout09:07:06'.length);\n        });\n\n        it('replaceAll + delete => only non-removable postfix', () => {\n            cy.get('@textfield')\n                .type('123456789')\n                .should('have.value', 'Timeout12:34:56.789')\n                .type('{selectAll}{backspace}')\n                .should('have.value', 'Timeout')\n                .should('have.prop', 'selectionStart', 'Timeout'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout'.length);\n        });\n\n        it('move caret left on attempt to remove colon by Backspace', () => {\n            cy.get('@textfield')\n                .type('1234')\n                .type('{leftArrow}'.repeat('34'.length))\n                .should('have.prop', 'selectionStart', 'Timeout12:'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout12:'.length)\n                .type('{backspace}')\n                .should('have.value', 'Timeout12:34')\n                .should('have.prop', 'selectionStart', 'Timeout12'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout12'.length);\n        });\n\n        it('move caret right on attempt to remove colon by Delete', () => {\n            cy.get('@textfield')\n                .type('1234')\n                .type('{leftArrow}'.repeat(':34'.length))\n                .should('have.prop', 'selectionStart', 'Timeout12'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout12'.length)\n                .type('{del}')\n                .should('have.value', 'Timeout12:34')\n                .should('have.prop', 'selectionStart', 'Timeout12:'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout12:'.length);\n        });\n\n        it('allows to delete last digit without zero placeholder', () => {\n            cy.get('@textfield')\n                .type('123456789')\n                .should('have.value', 'Timeout12:34:56.789')\n                .should('have.prop', 'selectionStart', 'Timeout12:34:56.789'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout12:34:56.789'.length)\n                .type('{backspace}')\n                .should('have.value', 'Timeout12:34:56.78')\n                .should('have.prop', 'selectionStart', 'Timeout12:34:56.78'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout12:34:56.78'.length)\n                .type('{backspace}'.repeat(2))\n                .should('have.value', 'Timeout12:34:56')\n                .should('have.prop', 'selectionStart', 'Timeout12:34:56'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout12:34:56'.length)\n                .type('{backspace}'.repeat(3))\n                .should('have.value', 'Timeout12:3')\n                .should('have.prop', 'selectionStart', 'Timeout12:3'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout12:3'.length)\n                .type('{backspace}'.repeat(3))\n                .should('have.value', 'Timeout')\n                .should('have.prop', 'selectionStart', 'Timeout'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout'.length)\n                .type('{backspace}'.repeat(5))\n                .should('have.value', 'Timeout')\n                .should('have.prop', 'selectionStart', 'Timeout'.length)\n                .should('have.prop', 'selectionEnd', 'Timeout'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/time/time-basic.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\nimport {repeatKey} from '../../utils';\n\ndescribe('Time', () => {\n    describe('Basic', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Time}/API?mode=HH:MM`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        describe('basic typing (1 character per keydown)', () => {\n            const tests = [\n                // [Typed value, Masked value, caretIndex]\n                ['1', '1', 1],\n                ['12', '12', '12'.length],\n                ['12:', '12:', '12:'.length],\n                ['123', '12:3', '12:3'.length],\n                ['1234', '12:34', '12:34'.length],\n            ] as const;\n\n            tests.forEach(([typedValue, maskedValue, caretIndex]) => {\n                it(`Type \"${typedValue}\" => \"${maskedValue}\"`, () => {\n                    cy.get('@input')\n                        .type(typedValue)\n                        .should('have.value', maskedValue)\n                        .should('have.prop', 'selectionStart', caretIndex)\n                        .should('have.prop', 'selectionEnd', caretIndex);\n                });\n            });\n        });\n\n        describe('basic erasing (value = \"12:34\" & caret is placed after the last value)', () => {\n            beforeEach(() => {\n                cy.get('@input').type('1234');\n            });\n\n            const tests = [\n                // [How many times \"Backspace\"-key was pressed, caretPosition, Masked value]\n                [1, '12:3'.length, '12:3'],\n                [2, '12'.length, '12'],\n                [3, '1'.length, '1'],\n                [4, 0, ''],\n            ] as const;\n\n            tests.forEach(([n, caretIndex, maskedValue]) => {\n                it(`Backspace x${n} => \"${maskedValue}\"`, () => {\n                    cy.get('@input')\n                        .type('{backspace}'.repeat(n))\n                        .should('have.value', maskedValue)\n                        .should('have.prop', 'selectionStart', caretIndex)\n                        .should('have.prop', 'selectionEnd', caretIndex);\n                });\n            });\n\n            it('Delete => no value change && no caret index change', () => {\n                cy.get('@input')\n                    .type('{del}')\n                    .should('have.value', '12:34')\n                    .should('have.prop', 'selectionStart', '12:34'.length)\n                    .should('have.prop', 'selectionEnd', '12:34'.length);\n            });\n        });\n\n        describe('Editing somewhere in the middle of a value (NOT the last character)', () => {\n            it('12:3|4 => Backspace => 12:|04 => Type \"5\" => 12:5|4', () => {\n                cy.get('@input')\n                    .type('1234')\n                    .type('{leftArrow}')\n                    .should('have.prop', 'selectionStart', '12:3'.length)\n                    .should('have.prop', 'selectionEnd', '12:3'.length)\n                    .type('{backspace}')\n                    .should('have.value', '12:04')\n                    .should('have.prop', 'selectionStart', '12:'.length)\n                    .should('have.prop', 'selectionEnd', '12:'.length)\n                    .type('5')\n                    .should('have.value', '12:54')\n                    .should('have.prop', 'selectionStart', '12:5'.length)\n                    .should('have.prop', 'selectionEnd', '12:5'.length);\n            });\n\n            it('12|:34 => Backspace => 1|0:34 => Type \"1\" => 11:|34', () => {\n                cy.get('@input')\n                    .type('1234')\n                    .type('{leftArrow}'.repeat(':34'.length))\n                    .should('have.prop', 'selectionStart', '12'.length)\n                    .should('have.prop', 'selectionEnd', '12'.length)\n                    .type('{backspace}')\n                    .should('have.value', '10:34')\n                    .should('have.prop', 'selectionStart', '1'.length)\n                    .should('have.prop', 'selectionEnd', '1'.length)\n                    .type('1')\n                    .should('have.value', '11:34')\n                    .should('have.prop', 'selectionStart', '11:'.length)\n                    .should('have.prop', 'selectionEnd', '11:'.length);\n            });\n\n            it('1|2:34 => Backspace => |02:34 => Type \"2\" => 2|2:34', () => {\n                cy.get('@input')\n                    .type('1234')\n                    .type('{leftArrow}'.repeat('2:34'.length))\n                    .should('have.prop', 'selectionStart', '1'.length)\n                    .should('have.prop', 'selectionEnd', '1'.length)\n                    .type('{backspace}')\n                    .should('have.value', '02:34')\n                    .should('have.prop', 'selectionStart', 0)\n                    .should('have.prop', 'selectionEnd', 0)\n                    .type('2')\n                    .should('have.value', '22:34')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            it('12:|34 => Type \"9\" => 12:09|', () => {\n                cy.get('@input')\n                    .type('1234')\n                    .type('{leftArrow}'.repeat('34'.length))\n                    .should('have.prop', 'selectionStart', '12:'.length)\n                    .should('have.prop', 'selectionEnd', '12:'.length)\n                    .type('9')\n                    .should('have.value', '12:09')\n                    .should('have.prop', 'selectionStart', '12:09'.length)\n                    .should('have.prop', 'selectionEnd', '12:09'.length);\n            });\n\n            it('|19:45 => Type \"2\" => 2|0:45', () => {\n                cy.get('@input')\n                    .type('1945')\n                    .type('{moveToStart}')\n                    .should('have.prop', 'selectionStart', 0)\n                    .should('have.prop', 'selectionEnd', 0)\n                    .type('2')\n                    .should('have.value', '20:45')\n                    .should('have.prop', 'selectionStart', '2'.length)\n                    .should('have.prop', 'selectionEnd', '2'.length);\n            });\n        });\n\n        describe('Fixed values', () => {\n            it('Press Backspace after fixed value => no value change => move caret to the left', () => {\n                cy.get('@input')\n                    .type('2359')\n                    .type('{leftArrow}'.repeat('59'.length))\n                    .should('have.prop', 'selectionStart', '23:'.length)\n                    .should('have.prop', 'selectionEnd', '23:'.length)\n                    .type('{backspace}')\n                    .should('have.value', '23:59')\n                    .should('have.prop', 'selectionStart', '23'.length)\n                    .should('have.prop', 'selectionEnd', '23'.length);\n            });\n\n            it('Press Delete after fixed value => no value change => move caret to the right', () => {\n                cy.get('@input')\n                    .type('2359')\n                    .type('{leftArrow}'.repeat(':59'.length))\n                    .should('have.prop', 'selectionStart', '23'.length)\n                    .should('have.prop', 'selectionEnd', '23'.length)\n                    .type('{del}')\n                    .should('have.value', '23:59')\n                    .should('have.prop', 'selectionStart', '23:'.length)\n                    .should('have.prop', 'selectionEnd', '23:'.length);\n            });\n        });\n\n        describe('Text selection', () => {\n            describe('Select range and press Backspace', () => {\n                it('12:|34| => Backspace => 12', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('1234')\n                        .realPress([\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '34'.length),\n                            'Backspace',\n                        ]);\n\n                    cy.get('@input')\n                        .should('have.value', '12')\n                        .should('have.prop', 'selectionStart', '12'.length)\n                        .should('have.prop', 'selectionEnd', '12'.length);\n                });\n\n                it('1|2:3|4 => Backspace => 1|0:04', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('1234')\n                        .realPress([\n                            'ArrowLeft',\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '2:3'.length),\n                            'Backspace',\n                        ]);\n\n                    cy.get('@input')\n                        .should('have.value', '10:04')\n                        .should('have.prop', 'selectionStart', '1'.length)\n                        .should('have.prop', 'selectionEnd', '1'.length);\n                });\n\n                it('|12|:34 => Backspace => |00:34', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('1234')\n                        .realPress([\n                            ...repeatKey('ArrowLeft', ':34'.length),\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '12'.length),\n                            'Backspace',\n                        ]);\n\n                    cy.get('@input')\n                        .should('have.value', '00:34')\n                        .should('have.prop', 'selectionStart', 0)\n                        .should('have.prop', 'selectionEnd', 0);\n                });\n            });\n\n            describe('Select range and press \"Delete\"', () => {\n                it('23:|59| => Delete => 23', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('2359')\n                        .realPress(['Shift', ...repeatKey('ArrowLeft', '59'.length)]);\n\n                    cy.get('@input')\n                        .type('{del}')\n                        .should('have.value', '23')\n                        .should('have.prop', 'selectionStart', '23'.length)\n                        .should('have.prop', 'selectionEnd', '23'.length);\n                });\n\n                it('2|3:5|9 => Delete => 20:0|9', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('2359')\n                        .realPress([\n                            'ArrowLeft',\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '3:5'.length),\n                        ]);\n\n                    cy.get('@input')\n                        .type('{del}')\n                        .should('have.value', '20:09')\n                        .should('have.prop', 'selectionStart', '20:0'.length)\n                        .should('have.prop', 'selectionEnd', '20:0'.length);\n                });\n\n                it('|23|:59 => Delete => 00|:59', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('2359')\n                        .realPress([\n                            ...repeatKey('ArrowLeft', ':59'.length),\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '23'.length),\n                        ]);\n\n                    cy.get('@input')\n                        .type('{del}')\n                        .should('have.value', '00:59')\n                        .should('have.prop', 'selectionStart', '00'.length)\n                        .should('have.prop', 'selectionEnd', '00'.length);\n                });\n            });\n\n            describe('Select range and press new digit', () => {\n                it('11:|22| => Press 3 => 11:3|', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('1122')\n                        .realPress(['Shift', ...repeatKey('ArrowLeft', '22'.length)]);\n\n                    cy.get('@input')\n                        .type('3')\n                        .should('have.value', '11:3')\n                        .should('have.prop', 'selectionStart', '11:3'.length)\n                        .should('have.prop', 'selectionEnd', '11:3'.length);\n                });\n\n                it('1|1:2|2 => Press 3 => 13:|02', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('1122')\n                        .realPress([\n                            'ArrowLeft',\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '1:2'.length),\n                        ]);\n\n                    cy.get('@input')\n                        .type('3')\n                        .should('have.value', '13:02')\n                        .should('have.prop', 'selectionStart', '13:'.length)\n                        .should('have.prop', 'selectionEnd', '13:'.length);\n                });\n\n                it('|11|:33 => Press 2 => 2|0:33', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('1133')\n                        .realPress([\n                            ...repeatKey('ArrowLeft', ':33'.length),\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '11'.length),\n                        ]);\n\n                    cy.get('@input')\n                        .type('2')\n                        .should('have.value', '20:33')\n                        .should('have.prop', 'selectionStart', '2'.length)\n                        .should('have.prop', 'selectionEnd', '2'.length);\n                });\n\n                it('1|2|:34 => Press 9 => 19:|34', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('1234')\n                        .realPress([\n                            ...repeatKey('ArrowLeft', ':34'.length),\n                            'Shift',\n                            'ArrowLeft',\n                        ]);\n\n                    cy.get('@input')\n                        .type('9')\n                        .should('have.value', '19:34')\n                        .should('have.prop', 'selectionStart', '19:'.length)\n                        .should('have.prop', 'selectionEnd', '19:'.length);\n                });\n            });\n        });\n\n        describe('Undo', () => {\n            it('Select all + Delete => Ctrl + Z', () => {\n                cy.get('@input')\n                    .type('1743')\n                    .type('{selectall}{del}')\n                    .should('have.value', '')\n                    .type('{ctrl+z}')\n                    .should('have.value', '17:43');\n            });\n\n            it('11|:22 => Backspace (x2) => Ctrl + Z (x2)', () => {\n                cy.get('@input')\n                    .type('1122')\n                    .type('{leftArrow}'.repeat(':22'.length))\n                    .type('{backspace}{backspace}')\n                    .should('have.value', '00:22')\n                    .should('have.prop', 'selectionStart', 0)\n                    .should('have.prop', 'selectionEnd', 0)\n                    .type('{ctrl+z}')\n                    .should('have.value', '10:22')\n                    .should('have.prop', 'selectionStart', '1'.length)\n                    .should('have.prop', 'selectionEnd', '1'.length)\n                    .type('{ctrl+z}')\n                    .should('have.value', '11:22')\n                    .should('have.prop', 'selectionStart', '11'.length)\n                    .should('have.prop', 'selectionEnd', '11'.length);\n            });\n\n            it('12:|34 => Delete => Cmd + Z', () => {\n                cy.get('@input')\n                    .type('1234')\n                    .type('{leftArrow}{leftArrow}')\n                    .type('{del}')\n                    .should('have.value', '12:04')\n                    .should('have.prop', 'selectionStart', '12:0'.length)\n                    .should('have.prop', 'selectionEnd', '12:0'.length)\n                    .type('{cmd+z}')\n                    .should('have.value', '12:34')\n                    .should('have.prop', 'selectionStart', '12:'.length)\n                    .should('have.prop', 'selectionEnd', '12:'.length);\n            });\n        });\n\n        describe('Redo', () => {\n            it('Select all + Delete => Cmd + Z => Cmd + Shift + Z', () => {\n                cy.get('@input')\n                    .type('1743')\n                    .type('{selectall}{del}')\n                    .should('have.value', '')\n                    .type('{cmd+z}')\n                    .should('have.value', '17:43')\n                    .type('{cmd+shift+z}')\n                    .should('have.value', '');\n            });\n\n            it('11|:22 => Backspace (x2) => Ctrl + Z (x2) => Ctrl + Y (x2)', () => {\n                cy.get('@input')\n                    .type('1122')\n                    .type('{leftArrow}'.repeat(':22'.length))\n                    .type('{backspace}{backspace}')\n                    .type('{ctrl+z}')\n                    .type('{ctrl+z}')\n                    .type('{ctrl+y}')\n                    .type('{ctrl+y}')\n                    .should('have.value', '00:22')\n                    .should('have.prop', 'selectionStart', 0)\n                    .should('have.prop', 'selectionEnd', 0);\n            });\n\n            it('12:|34 => Delete => Cmd + Z => Ctrl + Y', () => {\n                cy.get('@input')\n                    .type('1234')\n                    .type('{leftArrow}{leftArrow}')\n                    .type('{del}')\n                    .type('{cmd+z}')\n                    .type('{cmd+shift+z}')\n                    .should('have.value', '12:04')\n                    .should('have.prop', 'selectionStart', '12:0'.length)\n                    .should('have.prop', 'selectionEnd', '12:0'.length);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/time/time-fullwidth-to-halfwidth.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Time', () => {\n    describe('Full width character parsing', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Time}/API?mode=HH:MM`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('input');\n        });\n\n        describe('basic typing (1 character per keydown)', () => {\n            const tests = [\n                // [Typed value, Masked value, caretIndex]\n                ['１', '1', 1],\n                ['１２', '12', '12'.length],\n                ['１２：', '12:', '12:'.length],\n                ['１２３', '12:3', '12:3'.length],\n                ['１２３４', '12:34', '12:34'.length],\n            ] as const;\n\n            tests.forEach(([typedValue, maskedValue, caretIndex]) => {\n                it(`Type \"${typedValue}\" => \"${maskedValue}\"`, () => {\n                    cy.get('@input')\n                        .type(typedValue)\n                        .should('have.value', maskedValue)\n                        .should('have.prop', 'selectionStart', caretIndex)\n                        .should('have.prop', 'selectionEnd', caretIndex);\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/time/time-meridiem.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nimport {range, withCaretLabel} from '../../utils';\n\ndescribe('Time | modes with meridiem', () => {\n    describe('HH:MM AA', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Time}/API?mode=HH:MM%20AA`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('textfield');\n        });\n\n        describe('basic text insertion works', () => {\n            it('Empty textfield => Type 1234AM => 12:34 AM', () => {\n                cy.get('@textfield')\n                    .type('1234AM')\n                    .should('have.value', '12:34 AM')\n                    .should('have.prop', 'selectionStart', '12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 AM'.length);\n            });\n\n            it('12:34| => Type lowercase `a` => 12:34 AM', () => {\n                cy.get('@textfield')\n                    .type('1234a')\n                    .should('have.value', '12:34 AM')\n                    .should('have.prop', 'selectionStart', '12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 AM'.length);\n            });\n\n            it('12:34| => Type uppercase `A` => 12:34 AM', () => {\n                cy.get('@textfield')\n                    .type('1234A')\n                    .should('have.value', '12:34 AM')\n                    .should('have.prop', 'selectionStart', '12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 AM'.length);\n            });\n\n            it('12:34| => Type lowercase `p` => 12:34 AM', () => {\n                cy.get('@textfield')\n                    .type('1234p')\n                    .should('have.value', '12:34 PM')\n                    .should('have.prop', 'selectionStart', '12:34 PM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 PM'.length);\n            });\n\n            it('12:34| => Type uppercase `P` => 12:34 AM', () => {\n                cy.get('@textfield')\n                    .type('1234P')\n                    .should('have.value', '12:34 PM')\n                    .should('have.prop', 'selectionStart', '12:34 PM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 PM'.length);\n            });\n\n            it('12:34| => Type lowercase `m` => 12:34', () => {\n                cy.get('@textfield')\n                    .type('1234m')\n                    .should('have.value', '12:34')\n                    .should('have.prop', 'selectionStart', '12:34'.length)\n                    .should('have.prop', 'selectionEnd', '12:34'.length);\n            });\n\n            it('12:34| => Type uppercase `M` => 12:34', () => {\n                cy.get('@textfield')\n                    .type('1234M')\n                    .should('have.value', '12:34')\n                    .should('have.prop', 'selectionStart', '12:34'.length)\n                    .should('have.prop', 'selectionEnd', '12:34'.length);\n            });\n        });\n\n        describe('deletion of any meridiem characters deletes all meridiem character', () => {\n            [\n                {caretIndex: '12:34 AM'.length, action: '{backspace}'},\n                {caretIndex: '12:34 A'.length, action: '{backspace}'},\n                {caretIndex: '12:34 '.length, action: '{del}'},\n                {caretIndex: '12:34 A'.length, action: '{del}'},\n            ].forEach(({caretIndex, action}) => {\n                const initialValue = '12:34 AM';\n\n                it(`${withCaretLabel(initialValue, caretIndex)} => ${action} => 12:34|`, () => {\n                    cy.get('@textfield')\n                        .type('1234a')\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat(caretIndex))\n                        .type(action)\n                        .should('have.value', '12:34')\n                        .should('have.prop', 'selectionStart', '12:34'.length)\n                        .should('have.prop', 'selectionEnd', '12:34'.length);\n                });\n            });\n        });\n\n        describe('type new meridiem value when textfield already has another one', () => {\n            it('12:34 AM| => Type P => 12:34 PM|', () => {\n                cy.get('@textfield')\n                    .type('1234a')\n                    .type('{moveToEnd}')\n                    .type('p')\n                    .should('have.value', '12:34 PM')\n                    .should('have.prop', 'selectionStart', '12:34 PM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 PM'.length);\n            });\n\n            it('12:34 A|M => Type P => 12:34 PM|', () => {\n                cy.get('@textfield')\n                    .type('1234a')\n                    .type('{moveToEnd}{leftArrow}')\n                    .type('p')\n                    .should('have.value', '12:34 PM')\n                    .should('have.prop', 'selectionStart', '12:34 PM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 PM'.length);\n            });\n\n            it('12:34 |AM => Type P => 12:34 PM|', () => {\n                cy.get('@textfield')\n                    .type('1234a')\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat(2))\n                    .type('p')\n                    .should('have.value', '12:34 PM')\n                    .should('have.prop', 'selectionStart', '12:34 PM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 PM'.length);\n            });\n\n            it('12:34| AM => Type P => 12:34 PM|', () => {\n                cy.get('@textfield')\n                    .type('1234a')\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat(' AM'.length))\n                    .type('p')\n                    .should('have.value', '12:34 PM')\n                    .should('have.prop', 'selectionStart', '12:34 PM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 PM'.length);\n            });\n\n            it('12:34 PM| => Type A => 12:34 AM|', () => {\n                cy.get('@textfield')\n                    .type('1234p')\n                    .type('{moveToEnd}')\n                    .type('a')\n                    .should('have.value', '12:34 AM')\n                    .should('have.prop', 'selectionStart', '12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 AM'.length);\n            });\n\n            it('12:34 P|M => Type A => 12:34 AM|', () => {\n                cy.get('@textfield')\n                    .type('1234p')\n                    .type('{moveToEnd}{leftArrow}')\n                    .type('A')\n                    .should('have.value', '12:34 AM')\n                    .should('have.prop', 'selectionStart', '12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 AM'.length);\n            });\n\n            it('12:34 |PM => Type A => 12:34 AM|', () => {\n                cy.get('@textfield')\n                    .type('1234p')\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat(2))\n                    .type('a')\n                    .should('have.value', '12:34 AM')\n                    .should('have.prop', 'selectionStart', '12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 AM'.length);\n            });\n\n            it('12:34| PM => Type A => 12:34 AM|', () => {\n                cy.get('@textfield')\n                    .type('1234p')\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat(' PM'.length))\n                    .type('a')\n                    .should('have.value', '12:34 AM')\n                    .should('have.prop', 'selectionStart', '12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 AM'.length);\n            });\n        });\n\n        describe('press any characters (except part of meridiem ones) when cursor is placed near already existing meridiem', () => {\n            beforeEach(() => {\n                cy.get('@textfield')\n                    .type('1234a')\n                    .should('have.value', '12:34 AM')\n                    .should('have.prop', 'selectionStart', '12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 AM'.length);\n            });\n\n            it('12:34 AM| => Press 1 => Nothing changed', () => {\n                cy.get('@textfield')\n                    .type('{moveToEnd}')\n                    .type('1')\n                    .should('have.value', '12:34 AM')\n                    .should('have.prop', 'selectionStart', '12:34 AM'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 AM'.length);\n            });\n\n            it('12:34 A|M => Press 1 => Nothing changed', () => {\n                cy.get('@textfield')\n                    .type('{moveToEnd}{leftArrow}')\n                    .type('1')\n                    .should('have.value', '12:34 AM')\n                    .should('have.prop', 'selectionStart', '12:34 A'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 A'.length);\n            });\n\n            it('12:34 A|M => Press T => Nothing changed', () => {\n                cy.get('@textfield')\n                    .type('{moveToEnd}{leftArrow}')\n                    .type('t')\n                    .should('have.value', '12:34 AM')\n                    .should('have.prop', 'selectionStart', '12:34 A'.length)\n                    .should('have.prop', 'selectionEnd', '12:34 A'.length);\n            });\n\n            it('12:34 |AM => Press T => Nothing changed', () => {\n                cy.get('@textfield')\n                    .type('{moveToEnd}')\n                    .type('{leftArrow}'.repeat(2))\n                    .type('t')\n                    .should('have.value', '12:34 AM')\n                    .should('have.prop', 'selectionStart', '12:34 '.length)\n                    .should('have.prop', 'selectionEnd', '12:34 '.length);\n            });\n        });\n\n        describe('hour segment bounds', () => {\n            it('cannot be less than 01 (rejects zero as the 2nd hour segment)', () => {\n                cy.get('@textfield')\n                    .type('00')\n                    .should('have.value', '0')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            it('can be 1 (as the 1st digit segment)', () => {\n                cy.get('@textfield')\n                    .type('1')\n                    .should('have.value', '1')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            describe('automatically pads with zero', () => {\n                range(2, 9).forEach((x) => {\n                    it(`on attempt to enter ${x} as the first hour segment`, () => {\n                        cy.get('@textfield')\n                            .type(String(x))\n                            .should('have.value', `0${x}`)\n                            .should('have.prop', 'selectionStart', 2)\n                            .should('have.prop', 'selectionEnd', 2);\n                    });\n                });\n            });\n\n            range(10, 12).forEach((x) => {\n                const value = String(x);\n\n                it(`can be ${x}`, () => {\n                    cy.get('@textfield')\n                        .type(value)\n                        .should('have.value', value)\n                        .should('have.prop', 'selectionStart', 2)\n                        .should('have.prop', 'selectionEnd', 2);\n                });\n            });\n\n            describe('rejects insertion', () => {\n                range(13, 19).forEach((x) => {\n                    it(`on attempt to enter ${x} as the last hour segment`, () => {\n                        cy.get('@textfield')\n                            .type(String(x))\n                            .should('have.value', '1')\n                            .should('have.prop', 'selectionStart', 1)\n                            .should('have.prop', 'selectionEnd', 1);\n                    });\n                });\n            });\n        });\n\n        describe('toggle meridiem value on ArrowUp / ArrowDown', () => {\n            describe('Initial value === \"12:34 |\"', () => {\n                beforeEach(() => {\n                    cy.get('@textfield').type('1234 ');\n                });\n\n                it('↑ --- 12:34 |AM', () => {\n                    cy.get('@textfield')\n                        .type('{upArrow}')\n                        .should('have.value', '12:34 AM')\n                        .should('have.prop', 'selectionStart', '12:34 '.length)\n                        .should('have.prop', 'selectionEnd', '12:34 '.length);\n                });\n\n                it('↓ --- 12:34 |PM', () => {\n                    cy.get('@textfield')\n                        .type('{downArrow}')\n                        .should('have.value', '12:34 PM')\n                        .should('have.prop', 'selectionStart', '12:34 '.length)\n                        .should('have.prop', 'selectionEnd', '12:34 '.length);\n                });\n            });\n\n            describe('Initial value === \"12:34 AM\"', () => {\n                const initialValue = '12:34 AM';\n                const toggledValue = '12:34 PM';\n\n                beforeEach(() => {\n                    cy.get('@textfield')\n                        .type('1234a')\n                        .should('have.value', initialValue)\n                        .type('{moveToStart}');\n                });\n\n                ['12:34 '.length, '12:34 A'.length, '12:34 AM'.length].forEach(\n                    (initialCaretIndex) => {\n                        const initialValueWithCaretLabel = withCaretLabel(\n                            initialValue,\n                            initialCaretIndex,\n                        );\n                        const toggledValueWithCaretLabel = withCaretLabel(\n                            toggledValue,\n                            initialCaretIndex,\n                        );\n\n                        it(`${initialValueWithCaretLabel} --- ↑ --- ${toggledValueWithCaretLabel}`, () => {\n                            cy.get('@textfield')\n                                .type('{rightArrow}'.repeat(initialCaretIndex))\n                                .type('{upArrow}')\n                                .should('have.value', toggledValue)\n                                .should('have.prop', 'selectionStart', initialCaretIndex)\n                                .should('have.prop', 'selectionEnd', initialCaretIndex);\n                        });\n\n                        it(`${initialValueWithCaretLabel} --- ↓ --- ${toggledValueWithCaretLabel}`, () => {\n                            cy.get('@textfield')\n                                .type('{rightArrow}'.repeat(initialCaretIndex))\n                                .type('{downArrow}')\n                                .should('have.value', toggledValue)\n                                .should('have.prop', 'selectionStart', initialCaretIndex)\n                                .should('have.prop', 'selectionEnd', initialCaretIndex);\n                        });\n                    },\n                );\n            });\n\n            describe('Initial value === \"01:01 PM\"', () => {\n                const initialValue = '01:01 PM';\n                const toggledValue = '01:01 AM';\n\n                beforeEach(() => {\n                    cy.get('@textfield')\n                        .type('0101p')\n                        .should('have.value', initialValue)\n                        .type('{moveToStart}');\n                });\n\n                ['01:01 '.length, '01:01 P'.length, '01:01 PM'.length].forEach(\n                    (initialCaretIndex) => {\n                        const initialValueWithCaretLabel = withCaretLabel(\n                            initialValue,\n                            initialCaretIndex,\n                        );\n                        const toggledValueWithCaretLabel = withCaretLabel(\n                            toggledValue,\n                            initialCaretIndex,\n                        );\n\n                        it(`${initialValueWithCaretLabel} --- ↑ --- ${toggledValueWithCaretLabel}`, () => {\n                            cy.get('@textfield')\n                                .type('{rightArrow}'.repeat(initialCaretIndex))\n                                .type('{upArrow}')\n                                .should('have.value', toggledValue)\n                                .should('have.prop', 'selectionStart', initialCaretIndex)\n                                .should('have.prop', 'selectionEnd', initialCaretIndex);\n                        });\n\n                        it(`${initialValueWithCaretLabel} --- ↓ --- ${toggledValueWithCaretLabel}`, () => {\n                            cy.get('@textfield')\n                                .type('{rightArrow}'.repeat(initialCaretIndex))\n                                .type('{downArrow}')\n                                .should('have.value', toggledValue)\n                                .should('have.prop', 'selectionStart', initialCaretIndex)\n                                .should('have.prop', 'selectionEnd', initialCaretIndex);\n                        });\n                    },\n                );\n            });\n\n            describe('do nothing when caret is put after any time segment', () => {\n                it('Empty textfield --- ↑↓ --- Empty textfield', () => {\n                    cy.get('@textfield')\n                        .should('have.value', '')\n                        .type('{upArrow}')\n                        .should('have.value', '')\n                        .type('{downArrow}')\n                        .should('have.value', '');\n                });\n\n                ['1', '12', '12:', '12:3', '12:34'].forEach((textfieldValue) => {\n                    it(`${textfieldValue} --- ↑↓ --- ${textfieldValue}`, () => {\n                        cy.get('@textfield')\n                            .type(textfieldValue)\n                            .should('have.value', textfieldValue)\n                            .type('{upArrow}')\n                            .should('have.value', textfieldValue)\n                            .type('{downArrow}')\n                            .should('have.value', textfieldValue);\n                    });\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/time/time-mode.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\nimport {repeatKey} from '../../utils';\n\ndescribe('Time', () => {\n    describe('Mode', () => {\n        describe('HH:MM', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Time}/API?mode=HH:MM`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .clear()\n                    .as('input');\n            });\n\n            describe('Typing new character overwrite character after cursor', () => {\n                it('new character is different from the next one', () => {\n                    cy.get('@input')\n                        .type('2359')\n                        .type('{moveToStart}')\n                        .type('00')\n                        .should('have.value', '00:59')\n                        .should('have.prop', 'selectionStart', '00:'.length)\n                        .should('have.prop', 'selectionEnd', '00:'.length)\n                        .type('00')\n                        .should('have.value', '00:00')\n                        .should('have.prop', 'selectionStart', '00:00'.length)\n                        .should('have.prop', 'selectionEnd', '00:00'.length);\n                });\n\n                it('moves cursor behind next character if new character is the same with the next one', () => {\n                    cy.get('@input')\n                        .type('0259')\n                        .type('{moveToStart}{rightArrow}')\n                        .type('2')\n                        .should('have.value', '02:59')\n                        .should('have.prop', 'selectionStart', '02:'.length)\n                        .should('have.prop', 'selectionEnd', '02:'.length);\n                });\n            });\n\n            describe('Select range and press new digit', () => {\n                it('|23|:59 => Type 1 => 1|0:59', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('235959')\n                        .should('have.value', '23:59')\n                        .realPress([\n                            ...repeatKey('ArrowLeft', ':59'.length),\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '23'.length),\n                        ]);\n\n                    cy.get('@input')\n                        .type('1')\n                        .should('have.value', '10:59')\n                        .should('have.prop', 'selectionStart', 1)\n                        .should('have.prop', 'selectionEnd', 1);\n                });\n\n                it('|23|:59 => Type 0 => 0|0:59', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('235959')\n                        .should('have.value', '23:59')\n                        .realPress([\n                            ...repeatKey('ArrowLeft', ':59'.length),\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '23'.length),\n                        ]);\n\n                    cy.get('@input')\n                        .type('0')\n                        .should('have.value', '00:59')\n                        .should('have.prop', 'selectionStart', 1)\n                        .should('have.prop', 'selectionEnd', 1);\n                });\n            });\n\n            it('Pad typed value with zero if digit exceeds the first digit of time segment', () => {\n                cy.get('@input')\n                    .type('9')\n                    .should('have.value', '09')\n                    .should('have.prop', 'selectionStart', '09'.length)\n                    .should('have.prop', 'selectionEnd', '09'.length)\n                    .type('9')\n                    .should('have.value', '09:09')\n                    .should('have.prop', 'selectionStart', '09:09'.length)\n                    .should('have.prop', 'selectionEnd', '09:09'.length);\n            });\n\n            it('cannot insert >23 hours', () => {\n                cy.get('@input')\n                    .type('2')\n                    .should('have.value', '2')\n                    .type('7')\n                    .should('have.value', '2')\n                    .should('have.prop', 'selectionStart', '2'.length)\n                    .should('have.prop', 'selectionEnd', '2'.length);\n            });\n        });\n\n        describe('HH:MM:SS', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Time}/API?mode=HH:MM:SS`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .clear()\n                    .as('input');\n            });\n\n            describe('Typing new character overwrite character after cursor', () => {\n                it('new character is different from the next one', () => {\n                    cy.get('@input')\n                        .type('235959')\n                        .type('{moveToStart}')\n                        .type('0')\n                        .should('have.value', '03:59:59')\n                        .should('have.prop', 'selectionStart', '0'.length)\n                        .should('have.prop', 'selectionEnd', '0'.length)\n                        .type('000')\n                        .should('have.value', '00:00:59')\n                        .should('have.prop', 'selectionStart', '00:00:'.length)\n                        .should('have.prop', 'selectionEnd', '00:00:'.length)\n                        .type('00')\n                        .should('have.value', '00:00:00')\n                        .should('have.prop', 'selectionStart', '00:00:00'.length)\n                        .should('have.prop', 'selectionEnd', '00:00:00'.length);\n                });\n\n                it('moves cursor behind next character if new character is the same with the next one', () => {\n                    cy.get('@input')\n                        .type('02:59:59')\n                        .should('have.value', '02:59:59')\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat('02:'.length))\n                        .type('5')\n                        .should('have.value', '02:59:59')\n                        .should('have.prop', 'selectionStart', '02:5'.length)\n                        .should('have.prop', 'selectionEnd', '02:5'.length);\n                });\n            });\n\n            describe('Select range and press new digit', () => {\n                it(\n                    '23:|59|:59 => Type 2 => 23:2|0:59',\n                    BROWSER_SUPPORTS_REAL_EVENTS,\n                    () => {\n                        cy.get('@input')\n                            .type('235959')\n                            .should('have.value', '23:59:59')\n                            .realPress([\n                                ...repeatKey('ArrowLeft', ':59'.length),\n                                'Shift',\n                                ...repeatKey('ArrowLeft', '59'.length),\n                            ]);\n\n                        cy.get('@input')\n                            .type('2')\n                            .should('have.value', '23:20:59')\n                            .should('have.prop', 'selectionStart', '23:2'.length)\n                            .should('have.prop', 'selectionEnd', '23:2'.length);\n                    },\n                );\n\n                it(\n                    '23:|59|:59 => Type 0 => 23:0|0:59',\n                    BROWSER_SUPPORTS_REAL_EVENTS,\n                    () => {\n                        cy.get('@input')\n                            .type('235959')\n                            .should('have.value', '23:59:59')\n                            .realPress([\n                                ...repeatKey('ArrowLeft', ':59'.length),\n                                'Shift',\n                                ...repeatKey('ArrowLeft', '59'.length),\n                            ]);\n\n                        cy.get('@input')\n                            .type('0')\n                            .should('have.value', '23:00:59')\n                            .should('have.prop', 'selectionStart', '23:0'.length)\n                            .should('have.prop', 'selectionEnd', '23:0'.length);\n                    },\n                );\n            });\n\n            it('Pad typed value with zero if digit exceeds the first digit of time segment', () => {\n                cy.get('@input')\n                    .type('9')\n                    .should('have.value', '09')\n                    .should('have.prop', 'selectionStart', '09'.length)\n                    .should('have.prop', 'selectionEnd', '09'.length)\n                    .type('9')\n                    .should('have.value', '09:09')\n                    .should('have.prop', 'selectionStart', '09:09'.length)\n                    .should('have.prop', 'selectionEnd', '09:09'.length)\n                    .type('9')\n                    .should('have.value', '09:09:09')\n                    .should('have.prop', 'selectionStart', '09:09:09'.length)\n                    .should('have.prop', 'selectionEnd', '09:09:09'.length);\n            });\n        });\n\n        describe('HH:MM:SS.MSS', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Time}/API?mode=HH:MM:SS.MSS`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .clear()\n                    .as('input');\n            });\n\n            describe('Typing new character overwrite character after cursor', () => {\n                it('new character is different from the next one', () => {\n                    cy.get('@input')\n                        .type('235959999')\n                        .type('{moveToStart}')\n                        .type('0')\n                        .should('have.value', '03:59:59.999')\n                        .should('have.prop', 'selectionStart', '0'.length)\n                        .should('have.prop', 'selectionEnd', '0'.length)\n                        .type('000')\n                        .should('have.value', '00:00:59.999')\n                        .should('have.prop', 'selectionStart', '00:00:'.length)\n                        .should('have.prop', 'selectionEnd', '00:00:'.length)\n                        .type('00')\n                        .should('have.value', '00:00:00.999')\n                        .should('have.prop', 'selectionStart', '00:00:00.'.length)\n                        .should('have.prop', 'selectionEnd', '00:00:00.'.length)\n                        .type('0')\n                        .should('have.value', '00:00:00.099')\n                        .should('have.prop', 'selectionStart', '00:00:00.0'.length)\n                        .should('have.prop', 'selectionEnd', '00:00:00.0'.length);\n                });\n\n                it('moves cursor behind next character if new character is the same with the next one', () => {\n                    cy.get('@input')\n                        .type('02:59:59.999')\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat('02:59:59.'.length))\n                        .type('9')\n                        .should('have.value', '02:59:59.999')\n                        .should('have.prop', 'selectionStart', '02:59:59.9'.length)\n                        .should('have.prop', 'selectionEnd', '02:59:59.9'.length);\n                });\n            });\n\n            describe('Select range and press new digit', () => {\n                it(\n                    '23:|59|:59.999 => Type 2 => 23:2|0:59.999',\n                    BROWSER_SUPPORTS_REAL_EVENTS,\n                    () => {\n                        cy.get('@input')\n                            .type('235959999')\n                            .should('have.value', '23:59:59.999')\n                            .realPress([\n                                ...repeatKey('ArrowLeft', ':59.999'.length),\n                                'Shift',\n                                ...repeatKey('ArrowLeft', '59'.length),\n                            ]);\n\n                        cy.get('@input')\n                            .type('2')\n                            .should('have.value', '23:20:59.999')\n                            .should('have.prop', 'selectionStart', '23:2'.length)\n                            .should('have.prop', 'selectionEnd', '23:2'.length);\n                    },\n                );\n\n                it(\n                    '23:|59|:59.999 => Type 0 => 23:0|0:59.999',\n                    BROWSER_SUPPORTS_REAL_EVENTS,\n                    () => {\n                        cy.get('@input')\n                            .type('235959999')\n                            .should('have.value', '23:59:59.999')\n                            .realPress([\n                                ...repeatKey('ArrowLeft', ':59.999'.length),\n                                'Shift',\n                                ...repeatKey('ArrowLeft', '59'.length),\n                            ]);\n\n                        cy.get('@input')\n                            .type('0')\n                            .should('have.value', '23:00:59.999')\n                            .should('have.prop', 'selectionStart', '23:0'.length)\n                            .should('have.prop', 'selectionEnd', '23:0'.length);\n                    },\n                );\n            });\n\n            it('Pad typed value with zero if digit exceeds the first digit of time segment', () => {\n                cy.get('@input')\n                    .type('9')\n                    .should('have.value', '09')\n                    .should('have.prop', 'selectionStart', '09'.length)\n                    .should('have.prop', 'selectionEnd', '09'.length)\n                    .type('9')\n                    .should('have.value', '09:09')\n                    .should('have.prop', 'selectionStart', '09:09'.length)\n                    .should('have.prop', 'selectionEnd', '09:09'.length)\n                    .type('9')\n                    .should('have.value', '09:09:09')\n                    .should('have.prop', 'selectionStart', '09:09:09'.length)\n                    .should('have.prop', 'selectionEnd', '09:09:09'.length);\n            });\n\n            describe('accepts time segment separators typed by user', () => {\n                it('23 => Type : => 23:', () => {\n                    cy.get('@input')\n                        .type('23')\n                        .should('have.value', '23')\n                        .type(':')\n                        .should('have.value', '23:')\n                        .should('have.prop', 'selectionStart', '23:'.length)\n                        .should('have.prop', 'selectionEnd', '23:'.length);\n                });\n\n                it('23:59 => Type : => 23:59:', () => {\n                    cy.get('@input')\n                        .type('2359')\n                        .should('have.value', '23:59')\n                        .type(':')\n                        .should('have.value', '23:59:')\n                        .should('have.prop', 'selectionStart', '23:59:'.length)\n                        .should('have.prop', 'selectionEnd', '23:59:'.length);\n                });\n\n                it('23:59:59 => Type . => 23:59:59.', () => {\n                    cy.get('@input')\n                        .type('235959')\n                        .should('have.value', '23:59:59')\n                        .type('.')\n                        .should('have.value', '23:59:59.')\n                        .should('have.prop', 'selectionStart', '23:59:59.'.length)\n                        .should('have.prop', 'selectionEnd', '23:59:59.'.length);\n                });\n            });\n        });\n\n        describe('HH', () => {\n            describe('default segments', () => {\n                beforeEach(() => {\n                    cy.visit(`/${DemoPath.Time}/API?mode=HH`);\n                    cy.get('#demo-content input')\n                        .should('be.visible')\n                        .first()\n                        .focus()\n                        .clear()\n                        .as('input');\n                });\n\n                it('2| => type 5 => 2|', () => {\n                    cy.get('@input')\n                        .type('2')\n                        .should('have.value', '2')\n                        .should('have.prop', 'selectionStart', '2'.length)\n                        .should('have.prop', 'selectionEnd', '2'.length)\n                        .type('5')\n                        .should('have.value', '2')\n                        .should('have.prop', 'selectionStart', '2'.length)\n                        .should('have.prop', 'selectionEnd', '2'.length);\n                });\n\n                it('2| => type 3 => 23|', () => {\n                    cy.get('@input')\n                        .type('2')\n                        .should('have.value', '2')\n                        .should('have.prop', 'selectionStart', '2'.length)\n                        .should('have.prop', 'selectionEnd', '2'.length)\n                        .type('3')\n                        .should('have.value', '23')\n                        .should('have.prop', 'selectionStart', '23'.length)\n                        .should('have.prop', 'selectionEnd', '23'.length);\n                });\n\n                it('23| => type 5 => 23|', () => {\n                    cy.get('@input')\n                        .type('23')\n                        .should('have.value', '23')\n                        .should('have.prop', 'selectionStart', '23'.length)\n                        .should('have.prop', 'selectionEnd', '23'.length)\n                        .type('5')\n                        .should('have.value', '23')\n                        .should('have.prop', 'selectionStart', '23'.length)\n                        .should('have.prop', 'selectionEnd', '23'.length);\n                });\n            });\n\n            describe('max hours 11', () => {\n                beforeEach(() => {\n                    cy.visit(`/${DemoPath.Time}/API?mode=HH&timeSegmentMaxValues$=2`);\n                    cy.get('#demo-content input')\n                        .should('be.visible')\n                        .first()\n                        .focus()\n                        .clear()\n                        .as('input');\n                });\n\n                it('type 2 => 02', () => {\n                    cy.get('@input')\n                        .type('2')\n                        .should('have.value', '02')\n                        .should('have.prop', 'selectionStart', '02'.length)\n                        .should('have.prop', 'selectionEnd', '02'.length);\n                });\n            });\n        });\n\n        describe('MM:SS.MSS', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Time}/API?mode=MM:SS.MSS`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .clear()\n                    .as('input');\n            });\n\n            describe('Typing new character overwrite character after cursor', () => {\n                it('new character is different from the next one', () => {\n                    cy.get('@input')\n                        .type('5959999')\n                        .type('{moveToStart}')\n                        .type('0')\n                        .should('have.value', '09:59.999')\n                        .should('have.prop', 'selectionStart', '0'.length)\n                        .should('have.prop', 'selectionEnd', '0'.length)\n                        .type('000')\n                        .should('have.value', '00:00.999')\n                        .should('have.prop', 'selectionStart', '00:00.'.length)\n                        .should('have.prop', 'selectionEnd', '00:00.'.length)\n                        .type('00')\n                        .should('have.value', '00:00.009')\n                        .should('have.prop', 'selectionStart', '00:00.00'.length)\n                        .should('have.prop', 'selectionEnd', '00:00.00'.length);\n                });\n\n                it('moves cursor behind next character if new character is the same with the next one', () => {\n                    cy.get('@input')\n                        .type('59:59.999')\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat('59:59.'.length))\n                        .type('9')\n                        .should('have.value', '59:59.999')\n                        .should('have.prop', 'selectionStart', '59:59.9'.length)\n                        .should('have.prop', 'selectionEnd', '59:59.9'.length);\n                });\n            });\n\n            it('Pad typed value with zero if digit exceeds the first digit of time segment', () => {\n                cy.get('@input')\n                    .type('6')\n                    .should('have.value', '06')\n                    .should('have.prop', 'selectionStart', '06'.length)\n                    .should('have.prop', 'selectionEnd', '06'.length)\n                    .type('6')\n                    .should('have.value', '06:06')\n                    .should('have.prop', 'selectionStart', '06:06'.length)\n                    .should('have.prop', 'selectionEnd', '06:06'.length)\n                    .type('999')\n                    .should('have.value', '06:06.999')\n                    .should('have.prop', 'selectionStart', '06:06.999'.length)\n                    .should('have.prop', 'selectionEnd', '06:06.999'.length);\n            });\n\n            describe('Select range and press new digit', () => {\n                it(\n                    '|59|:59.999 => Type 2 => 2|0:59.999',\n                    BROWSER_SUPPORTS_REAL_EVENTS,\n                    () => {\n                        cy.get('@input')\n                            .type('5959999')\n                            .should('have.value', '59:59.999')\n                            .realPress([\n                                ...repeatKey('ArrowLeft', ':59.999'.length),\n                                'Shift',\n                                ...repeatKey('ArrowLeft', '59'.length),\n                            ]);\n\n                        cy.get('@input')\n                            .type('2')\n                            .should('have.value', '20:59.999')\n                            .should('have.prop', 'selectionStart', '2'.length)\n                            .should('have.prop', 'selectionEnd', '2'.length);\n                    },\n                );\n\n                it(\n                    '|59|:59.999 => Type 6 => 06:|59.999',\n                    BROWSER_SUPPORTS_REAL_EVENTS,\n                    () => {\n                        cy.get('@input')\n                            .type('5959999')\n                            .should('have.value', '59:59.999')\n                            .realPress([\n                                ...repeatKey('ArrowLeft', ':59.999'.length),\n                                'Shift',\n                                ...repeatKey('ArrowLeft', '59'.length),\n                            ]);\n\n                        cy.get('@input')\n                            .type('6')\n                            .should('have.value', '06:59.999')\n                            .should('have.prop', 'selectionStart', '06:'.length)\n                            .should('have.prop', 'selectionEnd', '06:'.length);\n                    },\n                );\n            });\n\n            describe('accepts time segment separators typed by user', () => {\n                it('59 => Type : => 59:', () => {\n                    cy.get('@input')\n                        .type('59')\n                        .should('have.value', '59')\n                        .type(':')\n                        .should('have.value', '59:')\n                        .should('have.prop', 'selectionStart', '59:'.length)\n                        .should('have.prop', 'selectionEnd', '59:'.length);\n                });\n\n                it('59:59 => Type . => 59:59.', () => {\n                    cy.get('@input')\n                        .type('5959')\n                        .should('have.value', '59:59')\n                        .type('.')\n                        .should('have.value', '59:59.')\n                        .should('have.prop', 'selectionStart', '59:59.'.length)\n                        .should('have.prop', 'selectionEnd', '59:59.'.length);\n                });\n            });\n\n            it('type 5959999 => 59:59.999', () => {\n                cy.get('@input').type('5959999').should('have.value', '59:59.999');\n            });\n        });\n\n        describe('SS.MSS', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Time}/API?mode=SS.MSS`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .clear()\n                    .as('input');\n            });\n\n            describe('Typing new character overwrite character after cursor', () => {\n                it('new character is different from the next one', () => {\n                    cy.get('@input')\n                        .type('59999')\n                        .type('{moveToStart}')\n                        .type('0')\n                        .should('have.value', '09.999')\n                        .should('have.prop', 'selectionStart', '0'.length)\n                        .should('have.prop', 'selectionEnd', '0'.length)\n                        .type('0')\n                        .should('have.value', '00.999')\n                        .should('have.prop', 'selectionStart', '00.'.length)\n                        .should('have.prop', 'selectionEnd', '00.'.length)\n                        .type('00')\n                        .should('have.value', '00.009')\n                        .should('have.prop', 'selectionStart', '00.00'.length)\n                        .should('have.prop', 'selectionEnd', '00.00'.length);\n                });\n\n                it('moves cursor behind next character if new character is the same with the next one', () => {\n                    cy.get('@input')\n                        .type('59.999')\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat('59.'.length))\n                        .type('9')\n                        .should('have.value', '59.999')\n                        .should('have.prop', 'selectionStart', '59.9'.length)\n                        .should('have.prop', 'selectionEnd', '59.9'.length);\n                });\n            });\n\n            it('Pad typed value with zero if digit exceeds the first digit of time segment', () => {\n                cy.get('@input')\n                    .type('6')\n                    .should('have.value', '06')\n                    .should('have.prop', 'selectionStart', '06'.length)\n                    .should('have.prop', 'selectionEnd', '06'.length)\n                    .type('999')\n                    .should('have.value', '06.999')\n                    .should('have.prop', 'selectionStart', '06.999'.length)\n                    .should('have.prop', 'selectionEnd', '06:999'.length);\n            });\n\n            describe('Select range and press new digit', () => {\n                it('|59|.999 => Type 2 => 2|0.999', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('59999')\n                        .should('have.value', '59.999')\n                        .realPress([\n                            ...repeatKey('ArrowLeft', '.999'.length),\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '59'.length),\n                        ]);\n\n                    cy.get('@input')\n                        .type('2')\n                        .should('have.value', '20.999')\n                        .should('have.prop', 'selectionStart', '2'.length)\n                        .should('have.prop', 'selectionEnd', '2'.length);\n                });\n\n                it('|59|.999 => Type 6 => 06.|999', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('59999')\n                        .should('have.value', '59.999')\n                        .realPress([\n                            ...repeatKey('ArrowLeft', '.999'.length),\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '59'.length),\n                        ]);\n\n                    cy.get('@input')\n                        .type('6')\n                        .should('have.value', '06.999')\n                        .should('have.prop', 'selectionStart', '06.'.length)\n                        .should('have.prop', 'selectionEnd', '06.'.length);\n                });\n            });\n\n            describe('accepts time segment separators typed by user', () => {\n                it('59 => Type . => 59.', () => {\n                    cy.get('@input')\n                        .type('59')\n                        .should('have.value', '59')\n                        .type('.')\n                        .should('have.value', '59.')\n                        .should('have.prop', 'selectionStart', '59.'.length)\n                        .should('have.prop', 'selectionEnd', '59.'.length);\n                });\n            });\n\n            it('type 59999 => 59.999', () => {\n                cy.get('@input').type('59999').should('have.value', '59.999');\n            });\n        });\n\n        describe('MM:SS', () => {\n            beforeEach(() => {\n                cy.visit(`/${DemoPath.Time}/API?mode=MM:SS`);\n                cy.get('#demo-content input')\n                    .should('be.visible')\n                    .first()\n                    .focus()\n                    .clear()\n                    .as('input');\n            });\n\n            describe('Typing new character overwrite character after cursor', () => {\n                it('new character is different from the next one', () => {\n                    cy.get('@input')\n                        .type('59:59')\n                        .type('{moveToStart}')\n                        .type('0')\n                        .should('have.value', '09:59')\n                        .should('have.prop', 'selectionStart', '0'.length)\n                        .should('have.prop', 'selectionEnd', '0'.length)\n                        .type('0')\n                        .should('have.value', '00:59')\n                        .should('have.prop', 'selectionStart', '00:'.length)\n                        .should('have.prop', 'selectionEnd', '00:'.length)\n                        .type('00')\n                        .should('have.value', '00:00')\n                        .should('have.prop', 'selectionStart', '00:00'.length)\n                        .should('have.prop', 'selectionEnd', '00:00'.length);\n                });\n\n                it('moves cursor behind next character if new character is the same with the next one', () => {\n                    cy.get('@input')\n                        .type('59:59')\n                        .type('{moveToStart}')\n                        .type('{rightArrow}'.repeat('59:'.length))\n                        .type('59')\n                        .should('have.value', '59:59')\n                        .should('have.prop', 'selectionStart', '59:59'.length)\n                        .should('have.prop', 'selectionEnd', '59:59'.length);\n                });\n            });\n\n            it('Pad typed value with zero if digit exceeds the first digit of time segment', () => {\n                cy.get('@input')\n                    .type('6')\n                    .should('have.value', '06')\n                    .should('have.prop', 'selectionStart', '06'.length)\n                    .should('have.prop', 'selectionEnd', '06'.length)\n                    .type('9')\n                    .should('have.value', '06:09')\n                    .should('have.prop', 'selectionStart', '06:09'.length)\n                    .should('have.prop', 'selectionEnd', '06:09'.length);\n            });\n\n            describe('Select range and press new digit', () => {\n                it('|59|:59 => Type 2 => 2|0:59', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('5959')\n                        .should('have.value', '59:59')\n                        .realPress([\n                            ...repeatKey('ArrowLeft', ':59'.length),\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '59'.length),\n                        ]);\n\n                    cy.get('@input')\n                        .type('2')\n                        .should('have.value', '20:59')\n                        .should('have.prop', 'selectionStart', '2'.length)\n                        .should('have.prop', 'selectionEnd', '2'.length);\n                });\n\n                it('|59|:59 => Type 6 => 06:|59', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input')\n                        .type('5959')\n                        .should('have.value', '59:59')\n                        .realPress([\n                            ...repeatKey('ArrowLeft', ':59'.length),\n                            'Shift',\n                            ...repeatKey('ArrowLeft', '59'.length),\n                        ]);\n\n                    cy.get('@input')\n                        .type('6')\n                        .should('have.value', '06:59')\n                        .should('have.prop', 'selectionStart', '06:'.length)\n                        .should('have.prop', 'selectionEnd', '06:'.length);\n                });\n            });\n\n            describe('accepts time segment separators typed by user', () => {\n                it('59 => Type : => 59:', () => {\n                    cy.get('@input')\n                        .type('59')\n                        .should('have.value', '59')\n                        .type(':')\n                        .should('have.value', '59:')\n                        .should('have.prop', 'selectionStart', '59:'.length)\n                        .should('have.prop', 'selectionEnd', '59:'.length);\n                });\n            });\n\n            it('type 9999 => 09:09', () => {\n                cy.get('@input').type('9999').should('have.value', '09:09');\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/time/time-segment-max-values.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nimport {range} from '../../utils';\n\ndescribe('Time | [timeSegmentMaxValues] property', () => {\n    describe('{hours: 5, minutes: 5, seconds: 5, milliseconds: 5}', () => {\n        beforeEach(() => {\n            cy.visit(`/${DemoPath.Time}/API?mode=HH:MM&timeSegmentMaxValues$=3`);\n            cy.get('#demo-content input')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('textfield');\n        });\n\n        describe('hour segment', () => {\n            describe('rejects all digits > 5 as the 1st digit (and does not pad them)', () => {\n                range(6, 9).forEach((digit) => {\n                    it(`Empty textfield => type ${digit} => empty textfield`, () => {\n                        cy.get('@textfield')\n                            .type(`${digit}`)\n                            .should('have.value', '')\n                            .should('have.prop', 'selectionStart', 0)\n                            .should('have.prop', 'selectionEnd', 0);\n                    });\n                });\n            });\n\n            describe('pads the first hour digit with zero for typed digits <=5', () => {\n                range(1, 5).forEach((digit) => {\n                    it(`Empty textfield => type ${digit} => 0${digit}`, () => {\n                        cy.get('@textfield')\n                            .type(`${digit}`)\n                            .should('have.value', `0${digit}`)\n                            .should('have.prop', 'selectionStart', 2)\n                            .should('have.prop', 'selectionEnd', 2);\n                    });\n                });\n            });\n\n            it('accepts 0 as the 1st hour digit', () => {\n                cy.get('@textfield')\n                    .type('0')\n                    .should('have.value', '0')\n                    .should('have.prop', 'selectionStart', 1)\n                    .should('have.prop', 'selectionEnd', 1);\n            });\n\n            describe('rejects all digits > 5 as the 2nd digit', () => {\n                range(6, 9).forEach((digit) => {\n                    it(`Empty textfield => type 0${digit} => 0`, () => {\n                        cy.get('@textfield')\n                            .type(`0${digit}`)\n                            .should('have.value', '0')\n                            .should('have.prop', 'selectionStart', 1)\n                            .should('have.prop', 'selectionEnd', 1);\n                    });\n                });\n            });\n\n            describe('accepts all digits <=5 as the 2nd hour digit', () => {\n                range(0, 5).forEach((digit) => {\n                    it(`Empty textfield => type 0${digit} => 0${digit}`, () => {\n                        cy.get('@textfield')\n                            .type(`0${digit}`)\n                            .should('have.value', `0${digit}`)\n                            .should('have.prop', 'selectionStart', 2)\n                            .should('have.prop', 'selectionEnd', 2);\n                    });\n                });\n            });\n\n            it('05:05 => type 1 => 00:01', () => {\n                cy.get('@textfield')\n                    .type('0505')\n                    .should('have.value', '05:05')\n                    .type('{moveToStart}')\n                    .type('1')\n                    .should('have.value', '01:05')\n                    .should('have.prop', 'selectionStart', '01:'.length)\n                    .should('have.prop', 'selectionEnd', '01:'.length);\n            });\n        });\n\n        describe('minute segment', () => {\n            describe('rejects all digits > 5 as the 1st minute digit (and does not pad them)', () => {\n                range(6, 9).forEach((digit) => {\n                    it(`00 => type ${digit} => 00`, () => {\n                        cy.get('@textfield')\n                            .type(`00${digit}`)\n                            .should('have.value', '00')\n                            .should('have.prop', 'selectionStart', 2)\n                            .should('have.prop', 'selectionEnd', 2);\n                    });\n                });\n            });\n\n            describe('pads the first minute digit with zero for typed digits <=5', () => {\n                range(1, 5).forEach((digit) => {\n                    it(`00 => type ${digit} => 0${digit}`, () => {\n                        cy.get('@textfield')\n                            .type(`00${digit}`)\n                            .should('have.value', `00:0${digit}`)\n                            .should('have.prop', 'selectionStart', `00:0${digit}`.length)\n                            .should('have.prop', 'selectionEnd', `00:0${digit}`.length);\n                    });\n                });\n            });\n\n            it('accepts 0 as the 1st minute digit', () => {\n                cy.get('@textfield')\n                    .type('000')\n                    .should('have.value', '00:0')\n                    .should('have.prop', 'selectionStart', '00:0'.length)\n                    .should('have.prop', 'selectionEnd', '00:0'.length);\n            });\n\n            describe('rejects all digits > 5 as the 2nd minute digit', () => {\n                range(6, 9).forEach((digit) => {\n                    it(`00 => type 0${digit} => 0`, () => {\n                        cy.get('@textfield')\n                            .type(`000${digit}`)\n                            .should('have.value', '00:0')\n                            .should('have.prop', 'selectionStart', '00:0'.length)\n                            .should('have.prop', 'selectionEnd', '00:0'.length);\n                    });\n                });\n            });\n\n            describe('accepts all digits <=5 as the 2nd hour digit', () => {\n                range(0, 5).forEach((digit) => {\n                    it(`00:0 => type 0${digit} => 0${digit}`, () => {\n                        cy.get('@textfield')\n                            .type(`000${digit}`)\n                            .should('have.value', `00:0${digit}`)\n                            .should('have.prop', 'selectionStart', `00:0${digit}`.length)\n                            .should('have.prop', 'selectionEnd', `00:0${digit}`.length);\n                    });\n                });\n            });\n\n            it('00:|02 => type 1 => 00:01', () => {\n                cy.get('@textfield')\n                    .type('0002')\n                    .should('have.value', '00:02')\n                    .type('{leftArrow}'.repeat(2))\n                    .should('have.prop', 'selectionStart', '00:'.length)\n                    .should('have.prop', 'selectionEnd', '00:'.length)\n                    .type('1')\n                    .should('have.value', '00:01')\n                    .should('have.prop', 'selectionStart', '00:01'.length)\n                    .should('have.prop', 'selectionEnd', '00:01'.length);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/kit/time/time-step.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from 'projects/demo-integrations/src/support/constants';\n\nimport {withCaretLabel} from '../../utils';\n\ndescribe('Time', () => {\n    describe('Mode', () => {\n        describe('HH:MM', () => {\n            describe('step = 1', () => {\n                beforeEach(() => {\n                    cy.visit(`/${DemoPath.Time}/API?mode=HH:MM&step=1`);\n                    cy.get('#demo-content input')\n                        .should('be.visible')\n                        .first()\n                        .focus()\n                        .clear()\n                        .as('input');\n                });\n\n                it('empty field => {upArrow} => 01', () => {\n                    cy.get('@input').type('{upArrow}').should('have.value', '01');\n                });\n\n                it('type 2 + type {upArrow} => 21', () => {\n                    cy.get('@input').type('2{upArrow}').should('have.value', '21');\n                });\n\n                it('{downArrow} => |23 => 23| => type {upArrow} => 23:01', () => {\n                    cy.get('@input')\n                        .type('{downArrow}')\n                        .should('have.value', '23')\n                        .should('have.a.prop', 'selectionStart', 0)\n                        .should('have.a.prop', 'selectionEnd', 0)\n                        .type('{rightArrow}'.repeat(2))\n                        .type(':{upArrow}')\n                        .should('have.value', '23:01');\n                });\n\n                it('{downArrow} => |23 => 23| => type :{downArrow} => 23:59', () => {\n                    cy.get('@input')\n                        .type('{downArrow}')\n                        .should('have.value', '23')\n                        .should('have.a.prop', 'selectionStart', 0)\n                        .should('have.a.prop', 'selectionEnd', 0)\n                        .type('{rightArrow}'.repeat(2))\n                        .type(':{downArrow}')\n                        .should('have.value', '23:59');\n                });\n\n                it('{upArrow}*24 => 00', () => {\n                    cy.get('@input')\n                        .type('{upArrow}'.repeat(24))\n                        .should('have.value', '00');\n                });\n\n                it('type {upArrow}*12 => |12 => 1|2 => type {upArrow} => 1|3 => 13| => type {downArrow} => 12|', () => {\n                    cy.get('@input')\n                        .type('{upArrow}'.repeat(12))\n                        .should('have.value', '12')\n                        .should('have.a.prop', 'selectionStart', 0)\n                        .should('have.a.prop', 'selectionEnd', 0)\n                        .type('{rightArrow}')\n                        .should('have.a.prop', 'selectionStart', 1)\n                        .should('have.a.prop', 'selectionEnd', 1)\n                        .type('{upArrow}')\n                        .should('have.value', '13')\n                        .should('have.a.prop', 'selectionStart', 1)\n                        .should('have.a.prop', 'selectionEnd', 1)\n                        .type('{rightArrow}')\n                        .should('have.a.prop', 'selectionStart', 2)\n                        .should('have.a.prop', 'selectionEnd', 2)\n                        .type('{downArrow}')\n                        .should('have.value', '12')\n                        .should('have.a.prop', 'selectionStart', 2)\n                        .should('have.a.prop', 'selectionEnd', 2);\n                });\n\n                it('type 12:{upArrow}{rightArrow}{downArrow}{rightArrow}{downArrow} => 12:59', () => {\n                    cy.get('@input')\n                        .type('12:{upArrow}')\n                        .type('{rightArrow}{downArrow}'.repeat(2))\n                        .should('have.value', '12:59');\n                });\n\n                it('12|:0 => {upArrow} => 13|:0', () => {\n                    cy.get('@input')\n                        .type('12:0')\n                        .type('{leftArrow}'.repeat(2))\n                        .type('{upArrow}')\n                        .should('have.value', '13:0')\n                        .should('have.a.prop', 'selectionStart', 2)\n                        .should('have.a.prop', 'selectionEnd', 2);\n                });\n            });\n\n            describe('step = 0', () => {\n                beforeEach(() => {\n                    cy.visit(`/${DemoPath.Time}/API?mode=HH:MM&step=0`);\n                    cy.get('#demo-content input')\n                        .should('be.visible')\n                        .first()\n                        .focus()\n                        .clear()\n                        .as('input');\n                });\n\n                it('should be disabled', BROWSER_SUPPORTS_REAL_EVENTS, () => {\n                    cy.get('@input').type('1212').realPress('ArrowUp');\n\n                    cy.get('@input')\n                        .should('have.a.prop', 'selectionStart', 0)\n                        .should('have.a.prop', 'selectionEnd', 0)\n                        .realPress('ArrowDown');\n\n                    cy.get('@input')\n                        .should('have.a.prop', 'selectionStart', '12:12'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:12'.length);\n                });\n            });\n        });\n\n        describe('HH:MM AA', () => {\n            describe('step = 1', () => {\n                beforeEach(() => {\n                    cy.visit(`/${DemoPath.Time}/API?mode=HH:MM%20AA&step=1`);\n                    cy.get('#demo-content input')\n                        .should('be.visible')\n                        .first()\n                        .focus()\n                        .clear()\n                        .as('textfield');\n                });\n\n                describe('time segment digits stepping', () => {\n                    [\n                        {value: '12:34 AM', caretIndex: 0, newValue: '01:34 AM'},\n                        {value: '12:34 AM', caretIndex: '1'.length, newValue: '01:34 AM'},\n                        {\n                            value: '12:34 AM',\n                            caretIndex: '12'.length,\n                            newValue: '01:34 AM',\n                        },\n                        {\n                            value: '12:34 AM',\n                            caretIndex: '12:'.length,\n                            newValue: '12:35 AM',\n                        },\n                        {\n                            value: '12:34 AM',\n                            caretIndex: '12:3'.length,\n                            newValue: '12:35 AM',\n                        },\n                        {\n                            value: '12:34 AM',\n                            caretIndex: '12:34'.length,\n                            newValue: '12:35 AM',\n                        },\n                    ].forEach(({value, caretIndex, newValue}) => {\n                        it(`${withCaretLabel(value, caretIndex)} --- ↑ --- ${withCaretLabel(newValue, caretIndex)}`, () => {\n                            cy.get('@textfield')\n                                .type(value)\n                                .type(`{moveToStart}${'{rightArrow}'.repeat(caretIndex)}`)\n                                .type('{upArrow}')\n                                .should('have.value', newValue)\n                                .should('have.a.prop', 'selectionStart', caretIndex)\n                                .should('have.a.prop', 'selectionEnd', caretIndex);\n                        });\n                    });\n\n                    [\n                        {value: '12:34 PM', caretIndex: 0, newValue: '11:34 PM'},\n                        {value: '12:34 PM', caretIndex: '1'.length, newValue: '11:34 PM'},\n                        {\n                            value: '12:34 PM',\n                            caretIndex: '12'.length,\n                            newValue: '11:34 PM',\n                        },\n                        {\n                            value: '12:34 PM',\n                            caretIndex: '12:'.length,\n                            newValue: '12:33 PM',\n                        },\n                        {\n                            value: '12:34 PM',\n                            caretIndex: '12:3'.length,\n                            newValue: '12:33 PM',\n                        },\n                        {\n                            value: '12:34 PM',\n                            caretIndex: '12:34'.length,\n                            newValue: '12:33 PM',\n                        },\n                    ].forEach(({value, caretIndex, newValue}) => {\n                        it(`${withCaretLabel(value, caretIndex)} --- ↓ --- ${withCaretLabel(newValue, caretIndex)}`, () => {\n                            cy.get('@textfield')\n                                .type(value)\n                                .type(`{moveToStart}${'{rightArrow}'.repeat(caretIndex)}`)\n                                .type('{downArrow}')\n                                .should('have.value', newValue)\n                                .should('have.a.prop', 'selectionStart', caretIndex)\n                                .should('have.a.prop', 'selectionEnd', caretIndex);\n                        });\n                    });\n                });\n\n                describe('meridiem switching', () => {\n                    [\n                        {\n                            value: '12:34 AM',\n                            caretIndex: '12:34 '.length,\n                            newValue: '12:34 PM',\n                        },\n                        {\n                            value: '12:34 AM',\n                            caretIndex: '12:34 A'.length,\n                            newValue: '12:34 PM',\n                        },\n                        {\n                            value: '12:34 AM',\n                            caretIndex: '12:34 AM'.length,\n                            newValue: '12:34 PM',\n                        },\n                    ].forEach(({value, caretIndex, newValue}) => {\n                        it(`${withCaretLabel(value, caretIndex)} --- ↑ --- ${withCaretLabel(newValue, caretIndex)}`, () => {\n                            cy.get('@textfield')\n                                .type(value)\n                                .type(`{moveToStart}${'{rightArrow}'.repeat(caretIndex)}`)\n                                .type('{upArrow}')\n                                .should('have.value', newValue)\n                                .should('have.a.prop', 'selectionStart', caretIndex)\n                                .should('have.a.prop', 'selectionEnd', caretIndex);\n                        });\n                    });\n\n                    [\n                        {\n                            value: '12:34 PM',\n                            caretIndex: '12:34 '.length,\n                            newValue: '12:34 AM',\n                        },\n                        {\n                            value: '12:34 PM',\n                            caretIndex: '12:34 P'.length,\n                            newValue: '12:34 AM',\n                        },\n                        {\n                            value: '12:34 PM',\n                            caretIndex: '12:34 PM'.length,\n                            newValue: '12:34 AM',\n                        },\n                    ].forEach(({value, caretIndex, newValue}) => {\n                        it(`${withCaretLabel(value, caretIndex)} --- ↓ --- ${withCaretLabel(newValue, caretIndex)}`, () => {\n                            cy.get('@textfield')\n                                .type(value)\n                                .type(`{moveToStart}${'{rightArrow}'.repeat(caretIndex)}`)\n                                .type('{downArrow}')\n                                .should('have.value', newValue)\n                                .should('have.a.prop', 'selectionStart', caretIndex)\n                                .should('have.a.prop', 'selectionEnd', caretIndex);\n                        });\n                    });\n                });\n            });\n        });\n\n        describe('HH:MM:SS', () => {\n            describe('step = 1', () => {\n                beforeEach(() => {\n                    cy.visit(`/${DemoPath.Time}/API?mode=HH:MM:SS&step=1`);\n                    cy.get('#demo-content input')\n                        .should('be.visible')\n                        .first()\n                        .focus()\n                        .clear()\n                        .as('input');\n                });\n\n                it('12|:0 => {upArrow} => 13|:0', () => {\n                    cy.get('@input')\n                        .type('12:0')\n                        .type('{leftArrow}'.repeat(2))\n                        .type('{upArrow}')\n                        .should('have.value', '13:0')\n                        .should('have.a.prop', 'selectionStart', 2)\n                        .should('have.a.prop', 'selectionEnd', 2);\n                });\n\n                it('12:34|:5 => {downArrow} => 12:33|:5 ', () => {\n                    cy.get('@input')\n                        .type('12:34:5')\n                        .type('{leftArrow}'.repeat(2))\n                        .type('{downArrow}')\n                        .should('have.value', '12:33:5')\n                        .should('have.a.prop', 'selectionStart', 5)\n                        .should('have.a.prop', 'selectionEnd', 5);\n                });\n            });\n        });\n\n        describe('HH:MM:SS.MSS', () => {\n            describe('step = 1', () => {\n                beforeEach(() => {\n                    cy.visit(`/${DemoPath.Time}/API?mode=HH:MM:SS.MSS&step=1`);\n                    cy.get('#demo-content input')\n                        .should('be.visible')\n                        .first()\n                        .focus()\n                        .clear()\n                        .as('input');\n                });\n\n                it('correctly works for hour segments', () => {\n                    cy.get('@input')\n                        .type('{upArrow}')\n                        .should('have.value', '01')\n                        .type('{rightArrow}{upArrow}')\n                        .should('have.value', '02')\n                        .should('have.a.prop', 'selectionStart', 1)\n                        .should('have.a.prop', 'selectionEnd', 1)\n                        .type('{rightArrow}')\n                        .type('{downArrow}'.repeat(3))\n                        .should('have.value', '23')\n                        .should('have.a.prop', 'selectionStart', 2)\n                        .should('have.a.prop', 'selectionEnd', 2);\n                });\n\n                it('correctly works for minute segments', () => {\n                    cy.get('@input')\n                        .type('12:')\n                        .should('have.value', '12:')\n                        .should('have.a.prop', 'selectionStart', '12:'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:'.length)\n                        .type('{upArrow}')\n                        .should('have.a.prop', 'selectionStart', '12:'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:'.length)\n                        .should('have.value', '12:01')\n                        .type('{rightArrow}')\n                        .type('{downArrow}'.repeat(2))\n                        .should('have.a.prop', 'selectionStart', '12:5'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:5'.length)\n                        .should('have.value', '12:59')\n                        .type('{rightArrow}')\n                        .type('{downArrow}'.repeat(4))\n                        .should('have.a.prop', 'selectionStart', '12:55'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:55'.length)\n                        .should('have.value', '12:55');\n                });\n\n                it('correctly works for second segments', () => {\n                    cy.get('@input')\n                        .type('1234:')\n                        .should('have.value', '12:34:')\n                        .should('have.a.prop', 'selectionStart', '12:34:'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:34:'.length)\n                        .type('{upArrow}'.repeat(5))\n                        .should('have.a.prop', 'selectionStart', '12:34:'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:34:'.length)\n                        .should('have.value', '12:34:05')\n                        .type('{rightArrow}')\n                        .type('{downArrow}'.repeat(8))\n                        .should('have.a.prop', 'selectionStart', '12:34:5'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:34:5'.length)\n                        .should('have.value', '12:34:57')\n                        .type('{rightArrow}')\n                        .type('{upArrow}'.repeat(3))\n                        .should('have.a.prop', 'selectionStart', '12:34:00'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:34:00'.length)\n                        .should('have.value', '12:34:00');\n                });\n\n                it('correctly works for millisecond segments', () => {\n                    cy.get('@input')\n                        .type('123456.')\n                        .should('have.value', '12:34:56.')\n                        .should('have.a.prop', 'selectionStart', '12:34:56.'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:34:56.'.length)\n                        .type('{upArrow}'.repeat(23))\n                        .should('have.value', '12:34:56.023')\n                        .should('have.a.prop', 'selectionStart', '12:34:56.'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:34:56.'.length)\n                        .type('{rightArrow}{downArrow}')\n                        .should('have.value', '12:34:56.022')\n                        .should('have.a.prop', 'selectionStart', '12:34:56.0'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:34:56.0'.length)\n                        .type('{rightArrow}')\n                        .type('{upArrow}'.repeat(3))\n                        .should('have.value', '12:34:56.025')\n                        .should('have.a.prop', 'selectionStart', '12:34:56.02'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:34:56.02'.length)\n                        .type('{rightArrow}')\n                        .type('{downArrow}'.repeat(29))\n                        .should('have.value', '12:34:56.996')\n                        .should('have.a.prop', 'selectionStart', '12:34:56.996'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:34:56.996'.length);\n                });\n            });\n\n            describe('step = 10', () => {\n                beforeEach(() => {\n                    cy.visit(`/${DemoPath.Time}/API?mode=HH:MM:SS.MSS&step=10`);\n                    cy.get('#demo-content input')\n                        .should('be.visible')\n                        .first()\n                        .focus()\n                        .clear()\n                        .as('input');\n                });\n\n                it('correctly works for millisecond segments', () => {\n                    cy.get('@input')\n                        .type('123456.')\n                        .should('have.value', '12:34:56.')\n                        .type('{upArrow}'.repeat(20))\n                        .should('have.value', '12:34:56.200')\n                        .type('{downArrow}'.repeat(30))\n                        .should('have.value', '12:34:56.900');\n                });\n\n                it('correctly works for each time segment', () => {\n                    cy.get('@input')\n                        .type('123456000')\n                        .should('have.value', '12:34:56.000')\n                        .should('have.a.prop', 'selectionStart', '12:34:56.000'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:34:56.000'.length)\n                        .type('{downArrow}')\n                        .should('have.value', '12:34:56.990')\n                        .should('have.a.prop', 'selectionStart', '12:34:56.990'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:34:56.990'.length)\n                        .type('{leftArrow}'.repeat(4))\n                        .type('{downArrow}')\n                        .should('have.value', '12:34:46.990')\n                        .should('have.a.prop', 'selectionStart', '12:34:46'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:34:46'.length)\n                        .type('{leftArrow}'.repeat(3))\n                        .type('{upArrow}')\n                        .should('have.value', '12:44:46.990')\n                        .should('have.a.prop', 'selectionStart', '12:44'.length)\n                        .should('have.a.prop', 'selectionEnd', '12:44'.length)\n                        .type('{leftArrow}'.repeat(3))\n                        .type('{upArrow}')\n                        .should('have.value', '22:44:46.990')\n                        .should('have.a.prop', 'selectionStart', '22'.length)\n                        .should('have.a.prop', 'selectionEnd', '22'.length);\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/react/element-predicate.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('@maskito/react | Element Predicate', () => {\n    describe('Sync predicate works', () => {\n        beforeEach(() => {\n            cy.visit(DemoPath.React);\n            cy.get('#awesome-input-wrapper input.real-input')\n                .scrollIntoView()\n                .should('be.visible')\n                .as('input');\n        });\n\n        it('rejects invalid characters', () => {\n            cy.get('@input').type('abc12def').should('have.value', '12');\n        });\n\n        it('accepts valid input', () => {\n            cy.get('@input').type('12.09.2023').should('have.value', '12.09.2023');\n        });\n\n        it('automatically adds fixed characters', () => {\n            cy.get('@input').type('12092023').should('have.value', '12.09.2023');\n        });\n\n        it('automatically pads day / month segments with zeroes for large digits', () => {\n            cy.get('@input').type('992023').should('have.value', '09.09.2023');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/card/card.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Card', () => {\n    describe('Card number input', () => {\n        beforeEach(() => {\n            cy.visit(DemoPath.Card);\n            cy.get('#card input').should('be.visible').first().focus().as('input');\n        });\n\n        it('accepts 16-digits card number', () => {\n            cy.get('@input')\n                .type('1234567890123456')\n                .should('have.value', '1234 5678 9012 3456');\n        });\n\n        it('accepts 18-digits card number', () => {\n            cy.get('@input')\n                .type('123456789012345678')\n                .should('have.value', '1234 5678 9012 3456 78');\n        });\n\n        it('accepts 19-digits card number', () => {\n            cy.get('@input')\n                .type('1234567890123456789')\n                .should('have.value', '1234 5678 9012 3456 789');\n        });\n\n        it('does not accept more than 19-digits', () => {\n            cy.get('@input')\n                .type('0'.repeat(25))\n                .should('have.value', '0000 0000 0000 0000 000');\n        });\n    });\n\n    describe('Expiration date', () => {\n        beforeEach(() => {\n            cy.visit(DemoPath.Card);\n            cy.get('#card input').should('be.visible').eq(2).focus().as('input');\n        });\n\n        it('input 321 => 03/21', () => {\n            cy.get('@input')\n                .type('321')\n                .should('have.value', '03/21')\n                .should('have.prop', 'selectionStart', '03/21'.length)\n                .should('have.prop', 'selectionEnd', '03/21'.length);\n        });\n    });\n\n    describe('CVV', () => {\n        beforeEach(() => {\n            cy.visit(DemoPath.Card);\n            cy.get('#card input').should('be.visible').eq(4).focus().as('input');\n        });\n\n        it('input 4321 => 432', () => {\n            cy.get('@input')\n                .type('4321')\n                .should('have.value', '432')\n                .should('have.prop', 'selectionStart', '432'.length)\n                .should('have.prop', 'selectionEnd', '432'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/content-editable/multi-line.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('ContentEditable | Multi-line support', () => {\n    describe('Deletion', () => {\n        beforeEach(() => {\n            cy.visit(DemoPath.ContentEditable);\n            cy.get('#multi-line [contenteditable]')\n                .should('be.visible')\n                .first()\n                .focus()\n                .as('element');\n        });\n\n        it('Select all + delete => Empty', () => {\n            cy.get('@element').type('{selectAll}{del}').should('have.text', '');\n        });\n\n        it('Select all + Backspace => Empty', () => {\n            cy.get('@element').type('{selectAll}{backspace}').should('have.text', '');\n        });\n\n        it('Long multi-line text', () => {\n            cy.get('@element')\n                .clear()\n                .type('a b{enter}cd ef{enter}aa11bb')\n                .should('have.text', 'a b\\ncd ef\\naabb')\n                .type('{backspace}'.repeat(5))\n                .should('have.text', 'a b\\ncd ef')\n                .type('{backspace}'.repeat(2))\n                .should('have.text', 'a b\\ncd ')\n                .type('{backspace}'.repeat(4))\n                .should('have.text', 'a b');\n        });\n    });\n\n    describe('Rejects invalid symbols on EVERY line', () => {\n        beforeEach(() => {\n            cy.visit(DemoPath.ContentEditable);\n            cy.get('#multi-line [contenteditable]')\n                .should('be.visible')\n                .first()\n                .focus()\n                .clear()\n                .should('have.text', '')\n                .as('element');\n        });\n\n        const tests = [\n            // [Typed value, Masked value]\n            ['abc123 def', 'abc def'],\n            ['abc123 def{enter}1a2b3c 4d', 'abc def\\nabc d'],\n            ['a{enter}b{enter}{enter}aa11bb', 'a\\nb\\n\\naabb'],\n        ] as const;\n\n        tests.forEach(([typed, masked]) => {\n            it(`Type ${typed} => ${masked}`, () => {\n                cy.get('@element').type(typed).should('have.text', masked);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/content-editable/single-line-time-mask.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('ContentEditable | With Time mask', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.ContentEditable);\n        cy.get('#time [contenteditable]')\n            .should('be.visible')\n            .first()\n            .clear()\n            .focus()\n            .should('have.value', '')\n            .as('element');\n    });\n\n    describe('basic typing (1 character per keydown)', () => {\n        const tests = [\n            // [Typed value, Masked value]\n            ['1', '1'],\n            ['1.', '1'],\n            ['12', '12'],\n            ['12:', '12:'],\n            ['9', '09'],\n            ['99', '09:09'],\n            ['123', '12:3'],\n            ['128', '12:08'],\n            ['25', '2'],\n        ] as const;\n\n        tests.forEach(([typed, masked]) => {\n            it(`Type ${typed} => ${masked}`, () => {\n                cy.get('@element').type(typed).should('have.text', masked);\n            });\n        });\n    });\n\n    describe('basic deletion via backspace', () => {\n        const tests = [\n            // [initialValue, n-times backspace pressed, result]\n            ['23:59', 1, '23:5'],\n            ['23:59', 2, '23'],\n            ['23:59', 3, '2'],\n            ['23:59', 4, ''],\n        ] as const;\n\n        tests.forEach(([initialValue, n, result]) => {\n            it(`${initialValue} => Backspace x${n} => ${result}`, () => {\n                cy.get('@element')\n                    .type(initialValue)\n                    .type('{backspace}'.repeat(n))\n                    .should('have.text', result);\n            });\n        });\n    });\n\n    describe('basic deletion via delete', () => {\n        const tests = [\n            // [initialValue, n-times backspace pressed, result]\n            ['23:59', 1, '03:59'],\n            ['23:59', 2, '00:59'],\n            ['23:59', 3, '00:59'],\n            ['23:59', 4, '00:09'],\n            ['23:59', 5, '00:0'],\n        ] as const;\n\n        tests.forEach(([initialValue, n, result]) => {\n            it(`${initialValue} => Move cursor to start => Delete x${n} => ${result}`, () => {\n                cy.get('@element')\n                    .type(initialValue)\n                    .type('{moveToStart}')\n                    .type('{del}'.repeat(n))\n                    .should('have.text', result);\n            });\n        });\n    });\n\n    it('12:|36 => 12:09|', () => {\n        cy.get('@element')\n            .type('1236')\n            .should('have.text', '12:36')\n            .type('{leftArrow}'.repeat(2))\n            .type('9')\n            .should('have.text', '12:09');\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/network-address/ipv4.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Network Address | IPv4', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.NetworkAddress);\n        cy.get('#ipv4 input')\n            .should('be.visible')\n            .first()\n            .should('have.value', '')\n            .focus()\n            .as('input');\n    });\n\n    describe('valid', () => {\n        it('accepts full IPv4 value with separators inserted', () => {\n            cy.get('@input').type('192168001001').should('have.value', '192.168.001.001');\n        });\n\n        it('accepts paste of value without separators', () => {\n            cy.get('@input')\n                .paste('192168001001')\n                .should('have.value', '192.168.001.001');\n        });\n\n        it('accepts paste of value with separators', () => {\n            cy.get('@input').paste('10.0.0.1').should('have.value', '10.0.0.1');\n        });\n\n        it('accepts paste of value with partial separators', () => {\n            cy.get('@input')\n                .paste('192.168001001')\n                .should('have.value', '192.168.001.001');\n        });\n\n        it('accepts paste of long value and truncates', () => {\n            cy.get('@input')\n                .paste('192.168.1.01.255.255')\n                .should('have.value', '192.168.1.01');\n        });\n\n        it('inserts separator after third digit in octet', () => {\n            cy.get('@input').type('1921').should('have.value', '192.1');\n        });\n\n        it('clamps octet value to 255', () => {\n            cy.get('@input').type('999').should('have.value', '255');\n        });\n\n        it('clamps later octets to 255', () => {\n            cy.get('@input').type('192999001001').should('have.value', '192.255.001.001');\n        });\n    });\n\n    describe('invalid', () => {\n        it('ignores non-digit characters', () => {\n            cy.get('@input').type('1a2b3c4').should('have.value', '123.4');\n        });\n\n        it('ignores separators without enough digits', () => {\n            cy.get('@input').type('..').should('have.value', '');\n        });\n\n        it('ignores separators mixed with too few digits', () => {\n            cy.get('@input').type('1..').should('have.value', '1.');\n        });\n\n        it('does not accept more than 4 octets', () => {\n            cy.get('@input').type('1'.repeat(20)).should('have.value', '111.111.111.111');\n        });\n    });\n\n    describe('editing', () => {\n        it('backspace deletes last character', () => {\n            cy.get('@input')\n                .type('192.168')\n                .type('{backspace}')\n                .should('have.value', '192.16');\n        });\n\n        it('backspace deletes trailing separator', () => {\n            cy.get('@input').type('192.').type('{backspace}').should('have.value', '192');\n        });\n\n        it('delete removes character in the middle', () => {\n            cy.get('@input')\n                .type('192168')\n                .type('{leftArrow}'.repeat(2))\n                .type('{del}')\n                .should('have.value', '192.18');\n        });\n\n        it('prevents deletion of separator character', () => {\n            cy.get('@input')\n                .type('192168')\n                .type('{leftArrow}'.repeat(4))\n                .type('{del}')\n                .should('have.value', '192.168');\n        });\n\n        it('text selection overwrite', () => {\n            cy.get('@input')\n                .type('192.168')\n                .type('{selectall}10')\n                .should('have.value', '10');\n        });\n\n        it('prevents insertion when full', () => {\n            cy.get('@input')\n                .type('192168001001')\n                .should('have.value', '192.168.001.001')\n                .type('5')\n                .should('have.value', '192.168.001.001');\n        });\n    });\n\n    describe('partially omitted separators', () => {\n        it('3 digits + 1 digit & separator + 3 digits + 3 digits', () => {\n            cy.get('@input').paste('1921.123123').should('have.value', '192.1.123.123');\n        });\n\n        it('3 digits + 3 digits + 1 digit & separator + 3 digits', () => {\n            cy.get('@input').paste('1921681.001').should('have.value', '192.168.1.001');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/network-address/ipv6.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Network Address | IPv6', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.NetworkAddress);\n        cy.get('#ipv6 input')\n            .should('be.visible')\n            .first()\n            .should('have.value', '')\n            .focus()\n            .as('input');\n    });\n\n    describe('valid', () => {\n        it('accepts full IPv6 value and normalizes to lowercase', () => {\n            cy.get('@input')\n                .type('20010DB885A3000000008A2E03707334')\n                .should('have.value', '2001:0db8:85a3:0000:0000:8a2e:0370:7334');\n        });\n\n        it('accepts paste of value with separators', () => {\n            cy.get('@input')\n                .paste('2001:0db8:0000:0000:0000:ff00:0042:8329')\n                .should('have.value', '2001:0db8:0000:0000:0000:ff00:0042:8329');\n        });\n\n        it('accepts paste of value without separators', () => {\n            cy.get('@input')\n                .paste('20010db8000000000000ff0000428329')\n                .should('have.value', '2001:0db8:0000:0000:0000:ff00:0042:8329');\n        });\n\n        it('accepts paste of long value and truncates', () => {\n            cy.get('@input')\n                .paste('2001:0db8:0000:0000:0000:ff00:0042:8329:aaaa:aaaa')\n                .should('have.value', '2001:0db8:0000:0000:0000:ff00:0042:8329');\n        });\n\n        it('inserts separators after 4 hex digits', () => {\n            cy.get('@input').type('abcd1').should('have.value', 'abcd:1');\n        });\n\n        it('accepts mixed-case hex digits', () => {\n            cy.get('@input').type('ABcdEF12').should('have.value', 'abcd:ef12');\n        });\n    });\n\n    describe('invalid', () => {\n        it('ignores non-hex characters', () => {\n            cy.get('@input').type('abg1!2Z3').should('have.value', 'ab12:3');\n        });\n\n        it('ignores separators without enough hex digits', () => {\n            cy.get('@input').type('::::').should('have.value', '');\n        });\n\n        it('ignores separators mixed with too few hex digits', () => {\n            cy.get('@input').type('ab::').should('have.value', 'ab');\n        });\n\n        it('does not accept more than 32 hex digits', () => {\n            cy.get('@input')\n                .type('f'.repeat(40))\n                .should('have.value', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff');\n        });\n    });\n\n    describe('editing', () => {\n        it('backspace deletes last character', () => {\n            cy.get('@input')\n                .type('2001:0db8')\n                .type('{backspace}')\n                .should('have.value', '2001:0db');\n        });\n\n        it('backspace deletes trailing separator', () => {\n            cy.get('@input')\n                .type('2001:')\n                .type('{backspace}')\n                .should('have.value', '2001');\n        });\n\n        it('delete removes hex character in the middle', () => {\n            cy.get('@input')\n                .type('20010db8')\n                .type('{leftArrow}'.repeat(3))\n                .type('{del}')\n                .should('have.value', '2001:0b8');\n        });\n\n        it('prevents delete of separator character', () => {\n            cy.get('@input')\n                .type('20010db8')\n                .type('{leftArrow}'.repeat(5))\n                .type('{del}')\n                .should('have.value', '2001:0db8');\n        });\n\n        it('text selection overwrite', () => {\n            cy.get('@input')\n                .type('2001:0db8')\n                .type('{selectall}1111')\n                .should('have.value', '1111');\n        });\n\n        it('prevents insertion when full', () => {\n            cy.get('@input')\n                .type('1aaA2bBb3ccc4dDd5eee6fFF77778888')\n                .should('have.value', '1aaa:2bbb:3ccc:4ddd:5eee:6fff:7777:8888')\n                .type('a')\n                .should('have.value', '1aaa:2bbb:3ccc:4ddd:5eee:6fff:7777:8888');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/network-address/mac.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Network Address | MAC', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.NetworkAddress);\n        cy.get('#mac input')\n            .should('be.visible')\n            .first()\n            .should('have.value', '')\n            .focus()\n            .as('input');\n    });\n\n    describe('valid', () => {\n        it('accepts full MAC value and normalizes to uppercase', () => {\n            cy.get('@input')\n                .type('aabbccddeeff')\n                .should('have.value', 'AA:BB:CC:DD:EE:FF');\n        });\n\n        it('accepts paste of value with separators', () => {\n            cy.get('@input')\n                .paste('00:1A:2B:3C:4D:5E')\n                .should('have.value', '00:1A:2B:3C:4D:5E');\n        });\n\n        it('accepts paste of value without separators', () => {\n            cy.get('@input')\n                .paste('aabbccddeeff')\n                .should('have.value', 'AA:BB:CC:DD:EE:FF');\n        });\n\n        it('accepts paste of long value and truncates', () => {\n            cy.get('@input')\n                .paste('00:1A:2B:3C:4D:5E:FF:FF')\n                .should('have.value', '00:1A:2B:3C:4D:5E');\n        });\n\n        it('inserts separator after second hex digit', () => {\n            cy.get('@input').type('aab').should('have.value', 'AA:B');\n        });\n\n        it('accepts mixed-case hex digits', () => {\n            cy.get('@input').type('aBcD').should('have.value', 'AB:CD');\n        });\n    });\n\n    describe('invalid', () => {\n        it('ignores non-hex characters', () => {\n            cy.get('@input').type('abg1!2Z3').should('have.value', 'AB:12:3');\n        });\n\n        it('ignores separators without enough hex digits', () => {\n            cy.get('@input').type('::::').should('have.value', '');\n        });\n\n        it('ignores consecutive separators after valid input', () => {\n            cy.get('@input').type('aa::').should('have.value', 'AA:');\n        });\n\n        it('does not accept more than 12 hex digits', () => {\n            cy.get('@input')\n                .type('f'.repeat(20))\n                .should('have.value', 'FF:FF:FF:FF:FF:FF');\n        });\n    });\n\n    describe('editing', () => {\n        it('backspace deletes last character', () => {\n            cy.get('@input')\n                .type('aabbcc')\n                .type('{backspace}')\n                .should('have.value', 'AA:BB:C');\n        });\n\n        it('backspace deletes trailing separator', () => {\n            cy.get('@input').type('aa:').type('{backspace}').should('have.value', 'AA');\n        });\n\n        it('delete removes hex character in the middle', () => {\n            cy.get('@input')\n                .type('aabbcc')\n                .type('{leftArrow}'.repeat(4))\n                .type('{del}')\n                .should('have.value', 'AA:BC:C');\n        });\n\n        it('prevents delete of separator character', () => {\n            cy.get('@input')\n                .type('aabbcc')\n                .type('{leftArrow}'.repeat(3))\n                .type('{del}')\n                .should('have.value', 'AA:BB:CC');\n        });\n\n        it('text selection overwrite', () => {\n            cy.get('@input')\n                .type('aabbcc')\n                .type('{selectall}1122')\n                .should('have.value', '11:22');\n        });\n\n        it('prevents insertion when full', () => {\n            cy.get('@input')\n                .type('aabbccddeeff')\n                .should('have.value', 'AA:BB:CC:DD:EE:FF')\n                .type('a')\n                .should('have.value', 'AA:BB:CC:DD:EE:FF');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/phone/phone.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\nimport {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants';\nimport {repeatKey} from '../../utils';\n\ndescribe('Phone', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.Phone);\n        cy.get('#kz input')\n            .should('be.visible')\n            .first()\n            .should('have.value', '')\n            .focus()\n            .as('input');\n    });\n\n    describe('basic typing (1 character per keydown)', () => {\n        const tests = [\n            // [Typed value, Masked value]\n            ['9', '+7 (9'],\n            ['91', '+7 (91'],\n            ['912', '+7 (912'],\n            ['9123', '+7 (912) 3'],\n            ['91234', '+7 (912) 34'],\n            ['912345', '+7 (912) 345'],\n            ['9123456', '+7 (912) 345-6'],\n            ['91234567', '+7 (912) 345-67'],\n            ['912345678', '+7 (912) 345-67-8'],\n            ['9123456789', '+7 (912) 345-67-89'],\n            ['91234567890', '+7 (912) 345-67-89'],\n            ['912345678900000000', '+7 (912) 345-67-89'],\n        ] as const;\n\n        tests.forEach(([typedValue, maskedValue]) => {\n            it(`Typing \"${typedValue}\" => \"${maskedValue}\"`, () => {\n                cy.get('@input')\n                    .type(typedValue)\n                    .should('have.value', maskedValue)\n                    .should('have.prop', 'selectionStart', maskedValue.length)\n                    .should('have.prop', 'selectionEnd', maskedValue.length);\n            });\n        });\n    });\n\n    describe('basic erasing (caret is placed after the last value)', () => {\n        beforeEach(() => {\n            cy.get('@input').type('9123456789');\n        });\n\n        const tests = [\n            // [How many times \"Backspace\"-key was pressed, Masked value]\n            [1, '+7 (912) 345-67-8'],\n            [2, '+7 (912) 345-67'],\n            [3, '+7 (912) 345-6'],\n            [4, '+7 (912) 345'],\n            [5, '+7 (912) 34'],\n            [6, '+7 (912) 3'],\n            [7, '+7 (912'],\n            [8, '+7 (91'],\n            [9, '+7 (9'],\n            [10, '+7 '],\n        ] as const;\n\n        tests.forEach(([n, maskedValue]) => {\n            it(`Backspace x${n} => \"${maskedValue}\"`, () => {\n                cy.get('@input')\n                    .type('{backspace}'.repeat(n))\n                    .should('have.value', maskedValue)\n                    .should('have.prop', 'selectionStart', maskedValue.length)\n                    .should('have.prop', 'selectionEnd', maskedValue.length);\n            });\n        });\n\n        it('Delete => no value change && no caret index change', () => {\n            cy.get('@input')\n                .type('{del}')\n                .should('have.value', '+7 (912) 345-67-89')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-67-89'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-67-89'.length);\n        });\n    });\n\n    describe('Editing somewhere in the middle of a value (NOT the last character)', () => {\n        beforeEach(() => {\n            cy.get('@input').type('9123456789');\n        });\n\n        // \"|\"-symbol is the caret position\n\n        it('+7 (912) 345-67-8|9 => Backspace + 0 => +7 (912) 345-67-0|9', () => {\n            cy.get('@input')\n                .focus()\n                .type('{leftArrow}')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-67-8'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-67-8'.length)\n                .type('{backspace}')\n                .should('have.value', '+7 (912) 345-67-9')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-67-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-67-'.length)\n                .type('0')\n                .should('have.value', '+7 (912) 345-67-09')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-67-0'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-67-0'.length);\n        });\n\n        it('+7 (912) 345-67|-89 => Backspace + 0 => +7 (912) 345-60-|89', () => {\n            cy.get('@input')\n                .focus()\n                .type('{leftArrow}{leftArrow}{leftArrow}')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-67'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-67'.length)\n                .type('{backspace}')\n                .should('have.value', '+7 (912) 345-68-9')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-6'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-6'.length)\n                .type('0')\n                .should('have.value', '+7 (912) 345-60-89')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-60-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-60-'.length);\n        });\n\n        it('+7 (912) 345-6|7-89 => Backspace + 0 => +7 (912) 345-0|7-89', () => {\n            cy.get('@input')\n                .focus()\n                .type('{leftArrow}'.repeat('7-89'.length))\n                .should('have.prop', 'selectionStart', '+7 (912) 345-6'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-6'.length)\n                .type('{backspace}')\n                .should('have.value', '+7 (912) 345-78-9')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-'.length)\n                .type('0')\n                .should('have.value', '+7 (912) 345-07-89')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-0'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-0'.length);\n        });\n\n        it('+7 (912) 345-67-|89 => Delete + 0 => +7 (912) 345-67-0|9', () => {\n            cy.get('@input')\n                .focus()\n                .type('{leftArrow}'.repeat('89'.length))\n                .should('have.prop', 'selectionStart', '+7 (912) 345-67-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-67-'.length)\n                .type('{del}')\n                .should('have.value', '+7 (912) 345-67-9')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-67-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-67-'.length)\n                .type('0')\n                .should('have.value', '+7 (912) 345-67-09')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-67-0'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-67-0'.length);\n        });\n\n        it('+7 (912) 345-6|7-89 => Delete + 0 => +7 (912) 345-60-|89', () => {\n            cy.get('@input')\n                .focus()\n                .type('{leftArrow}'.repeat('7-89'.length))\n                .should('have.prop', 'selectionStart', '+7 (912) 345-6'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-6'.length)\n                .type('{del}')\n                .should('have.value', '+7 (912) 345-68-9')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-6'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-6'.length)\n                .type('0')\n                .should('have.value', '+7 (912) 345-60-89')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-60-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-60-'.length);\n        });\n\n        it('+7 (912) 345-|67-89 => Delete + 0 => +7 (912) 345-0|7-89', () => {\n            cy.get('@input')\n                .focus()\n                .type('{leftArrow}'.repeat('67-89'.length))\n                .should('have.prop', 'selectionStart', '+7 (912) 345-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-'.length)\n                .type('{del}')\n                .should('have.value', '+7 (912) 345-78-9')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-'.length)\n                .type('0')\n                .should('have.value', '+7 (912) 345-07-89')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-0'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-0'.length);\n        });\n    });\n\n    describe('Press Backspace after fixed value => no value change => move caret to the left', () => {\n        beforeEach(() => {\n            cy.get('@input').type('9123456789');\n        });\n\n        it('+7 (912) 345-67-|89 => Backspace => +7 (912) 345-67|-89', () => {\n            cy.get('@input')\n                .focus()\n                .type('{leftArrow}{leftArrow}')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-67-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-67-'.length)\n                .type('{backspace}')\n                .should('have.value', '+7 (912) 345-67-89')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-67'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-67'.length);\n        });\n\n        it('+7 (912) 345-|67-89 => Backspace => +7 (912) 345|-67-89', () => {\n            cy.get('@input')\n                .focus()\n                .type('{leftArrow}'.repeat('67-89'.length))\n                .should('have.prop', 'selectionStart', '+7 (912) 345-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-'.length)\n                .type('{backspace}')\n                .should('have.value', '+7 (912) 345-67-89')\n                .should('have.prop', 'selectionStart', '+7 (912) 345'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345'.length);\n        });\n\n        it('+7 (912) |345-67-89 => Backspace x2 => +7 (912|) 345-67-89', () => {\n            cy.get('@input')\n                .focus()\n                .type('{leftArrow}'.repeat('345-67-89'.length))\n                .should('have.prop', 'selectionStart', '+7 (912) '.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) '.length)\n                .type('{backspace}{backspace}')\n                .should('have.value', '+7 (912) 345-67-89')\n                .should('have.prop', 'selectionStart', '+7 (912'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912'.length);\n        });\n    });\n\n    describe('Press Delete before fixed value => no value change => move caret to the right', () => {\n        beforeEach(() => {\n            cy.get('@input').type('9123456789');\n        });\n\n        it('+7 (912) 345-67|-89 => Delete => +7 (912) 345-67-|89', () => {\n            cy.get('@input')\n                .focus()\n                .type('{leftArrow}'.repeat('-89'.length))\n                .should('have.prop', 'selectionStart', '+7 (912) 345-67'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-67'.length)\n                .type('{del}')\n                .should('have.value', '+7 (912) 345-67-89')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-67-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-67-'.length);\n        });\n\n        it('+7 (912) 345|-67-89 => Delete => +7 (912) 345-|67-89', () => {\n            cy.get('@input')\n                .focus()\n                .type('{leftArrow}'.repeat('-67-89'.length))\n                .should('have.prop', 'selectionStart', '+7 (912) 345'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345'.length)\n                .type('{del}')\n                .should('have.value', '+7 (912) 345-67-89')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-'.length);\n        });\n\n        it('+7 (912|) 345-67-89 => Backspace x2 => +7 (912) |345-67-89', () => {\n            cy.get('@input')\n                .focus()\n                .type('{leftArrow}'.repeat(') 345-67-89'.length))\n                .should('have.prop', 'selectionStart', '+7 (912'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912'.length)\n                .type('{del}{del}')\n                .should('have.value', '+7 (912) 345-67-89')\n                .should('have.prop', 'selectionStart', '+7 (912) '.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) '.length);\n        });\n    });\n\n    describe('Text selection', () => {\n        // ab|cd|e – it means that in string \"abcde\" characters \"cd\" are selected\n\n        beforeEach(() => {\n            cy.get('@input').type('9123456789');\n        });\n\n        describe('Select range and press \"Backspace\"', () => {\n            it(\n                '+7 (912) 345-67-|89| => Backspace => +7 (912) 345-67|',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        'Shift',\n                        'ArrowLeft',\n                        'ArrowLeft',\n                        'Backspace',\n                    ]);\n\n                    cy.get('@input')\n                        .should('have.value', '+7 (912) 345-67')\n                        .should('have.prop', 'selectionStart', '+7 (912) 345-67'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) 345-67'.length);\n                },\n            );\n\n            it(\n                '+7 (912) 345-6|7-89| => Backspace => +7 (912) 345-6|',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '7-89'.length),\n                        'Backspace',\n                    ]);\n\n                    cy.get('@input')\n                        .should('have.value', '+7 (912) 345-6')\n                        .should('have.prop', 'selectionStart', '+7 (912) 345-6'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) 345-6'.length);\n                },\n            );\n\n            it(\n                '+7 (912) 345-6|7-8|9 => Backspace => +7 (912) 345-6|9',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        'ArrowLeft',\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '7-8'.length),\n                        'Backspace',\n                    ]);\n\n                    cy.get('@input')\n                        .should('have.value', '+7 (912) 345-69')\n                        .should('have.prop', 'selectionStart', '+7 (912) 345-6'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) 345-6'.length);\n                },\n            );\n\n            it(\n                '+7 (912) 345-|67|-89 => Backspace => +7 (912) 345-|89',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        ...repeatKey('ArrowLeft', '-89'.length),\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '67'.length),\n                        'Backspace',\n                    ]);\n\n                    cy.get('@input')\n                        .should('have.value', '+7 (912) 345-89')\n                        .should('have.prop', 'selectionStart', '+7 (912) 345-'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) 345-'.length);\n                },\n            );\n\n            it(\n                '+7 (912) |345|-67-89 => Backspace => +7 (912) |678-9',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        ...repeatKey('ArrowLeft', '-67-89'.length),\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '345'.length),\n                        'Backspace',\n                    ]);\n\n                    cy.get('@input')\n                        .should('have.value', '+7 (912) 678-9')\n                        .should('have.prop', 'selectionStart', '+7 (912) '.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) '.length);\n                },\n            );\n        });\n\n        describe('Select range and press \"Delete\"', () => {\n            it(\n                '+7 (912) 345-67-|89| => Delete => +7 (912) 345-67|',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress(['Shift', 'ArrowLeft', 'ArrowLeft']);\n\n                    cy.get('@input')\n                        .type('{del}')\n                        .should('have.value', '+7 (912) 345-67')\n                        .should('have.prop', 'selectionStart', '+7 (912) 345-67'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) 345-67'.length);\n                },\n            );\n\n            it(\n                '+7 (912) 345-6|7-89| => Delete => +7 (912) 345-6|',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '7-89'.length),\n                    ]);\n\n                    cy.get('@input')\n                        .type('{del}')\n                        .should('have.value', '+7 (912) 345-6')\n                        .should('have.prop', 'selectionStart', '+7 (912) 345-6'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) 345-6'.length);\n                },\n            );\n\n            it(\n                '+7 (912) 345-6|7-8|9 => Delete => +7 (912) 345-6|9',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        'ArrowLeft',\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '7-8'.length),\n                    ]);\n\n                    cy.get('@input')\n                        .type('{del}')\n                        .should('have.value', '+7 (912) 345-69')\n                        .should('have.prop', 'selectionStart', '+7 (912) 345-6'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) 345-6'.length);\n                },\n            );\n\n            it(\n                '+7 (912) 345-|67|-89 => Delete => +7 (912) 345-|89',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        ...repeatKey('ArrowLeft', '-89'.length),\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '67'.length),\n                    ]);\n\n                    cy.get('@input')\n                        .type('{del}')\n                        .should('have.value', '+7 (912) 345-89')\n                        .should('have.prop', 'selectionStart', '+7 (912) 345-'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) 345-'.length);\n                },\n            );\n\n            it(\n                '+7 (912) |345|-67-89 => Delete => +7 (912) |678-9',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        ...repeatKey('ArrowLeft', '-67-89'.length),\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '345'.length),\n                    ]);\n\n                    cy.get('@input')\n                        .type('{del}')\n                        .should('have.value', '+7 (912) 678-9')\n                        .should('have.prop', 'selectionStart', '+7 (912) '.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) '.length);\n                },\n            );\n        });\n\n        describe('Select range and press new digit', () => {\n            it(\n                '+7 (912) 345-67-|89| => Press 0 => +7 (912) 345-67-0|',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress(['Shift', 'ArrowLeft', 'ArrowLeft']);\n\n                    cy.get('@input')\n                        .type('0')\n                        .should('have.value', '+7 (912) 345-67-0')\n                        .should('have.prop', 'selectionStart', '+7 (912) 345-67-0'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) 345-67-0'.length);\n                },\n            );\n\n            it(\n                '+7 (912) 345-6|7-89| => Press 0 => +7 (912) 345-60|',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '7-89'.length),\n                    ]);\n\n                    cy.get('@input')\n                        .type('0')\n                        .should('have.value', '+7 (912) 345-60')\n                        .should('have.prop', 'selectionStart', '+7 (912) 345-60'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) 345-60'.length);\n                },\n            );\n\n            it(\n                '+7 (912) 345-6|7-8|9 => Press 0 => +7 (912) 345-60-|9',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        'ArrowLeft',\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '7-8'.length),\n                    ]);\n\n                    cy.get('@input')\n                        .type('0')\n                        .should('have.value', '+7 (912) 345-60-9')\n                        .should('have.prop', 'selectionStart', '+7 (912) 345-60-'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) 345-60-'.length);\n                },\n            );\n\n            it(\n                '+7 (912) 345-|67|-89 => Press 0 => +7 (912) 345-0|8-9',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        ...repeatKey('ArrowLeft', '-89'.length),\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '67'.length),\n                    ]);\n\n                    cy.get('@input')\n                        .type('0')\n                        .should('have.value', '+7 (912) 345-08-9')\n                        .should('have.prop', 'selectionStart', '+7 (912) 345-0'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) 345-0'.length);\n                },\n            );\n\n            it(\n                '+7 (912) |345|-67-89 => Press \"0\" => +7 (912) 0|67-89',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        ...repeatKey('ArrowLeft', '-67-89'.length),\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '345'.length),\n                    ]);\n\n                    cy.get('@input')\n                        .type('0')\n                        .should('have.value', '+7 (912) 067-89')\n                        .should('have.prop', 'selectionStart', '+7 (912) 0'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) 0'.length);\n                },\n            );\n        });\n\n        describe('Select fixed characters only', () => {\n            it(\n                'and press Backspace => no changes => move caret to the left side',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        ...repeatKey('ArrowLeft', '345-67-89'.length),\n                        'Shift',\n                        ...repeatKey('ArrowLeft', ') '.length),\n                        'Backspace',\n                    ]);\n\n                    cy.get('@input')\n                        .should('have.value', '+7 (912) 345-67-89')\n                        .should('have.prop', 'selectionStart', '+7 (912'.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912'.length);\n                },\n            );\n\n            it(\n                'and press Delete => no changes => move caret to the right side',\n                BROWSER_SUPPORTS_REAL_EVENTS,\n                () => {\n                    cy.get('@input').realPress([\n                        ...repeatKey('ArrowLeft', '345-67-89'.length),\n                        'Shift',\n                        ...repeatKey('ArrowLeft', ') '.length),\n                    ]);\n\n                    cy.get('@input')\n                        .type('{del}')\n                        .should('have.value', '+7 (912) 345-67-89')\n                        .should('have.prop', 'selectionStart', '+7 (912) '.length)\n                        .should('have.prop', 'selectionEnd', '+7 (912) '.length);\n                },\n            );\n        });\n    });\n\n    describe('Undo', () => {\n        it('Select all + Delete => Ctrl + Z', () => {\n            cy.get('@input')\n                .type('9123456789')\n                .type('{selectall}{del}')\n                .should('have.value', '+7 ')\n                .type('{ctrl+z}')\n                .should('have.value', '+7 (912) 345-67-89');\n        });\n\n        it('+7 (912) 345-67|-89 => Backspace (x2) => Ctrl + Z (x2)', () => {\n            cy.get('@input')\n                .type('9123456789')\n                .type('{leftArrow}'.repeat('-89'.length))\n                .type('{backspace}{backspace}')\n                .should('have.value', '+7 (912) 345-89')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-'.length)\n                .type('{ctrl+z}')\n                .should('have.value', '+7 (912) 345-68-9')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-6'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-6'.length)\n                .type('{ctrl+z}')\n                .should('have.value', '+7 (912) 345-67-89')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-67'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-67'.length);\n        });\n\n        it(\n            '+7 (912) |345-67|-89 => Delete => Cmd + Z',\n            BROWSER_SUPPORTS_REAL_EVENTS,\n            () => {\n                cy.get('@input')\n                    .type('9123456789')\n                    .realPress([\n                        ...repeatKey('ArrowLeft', '-89'.length),\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '345-67'.length),\n                    ]);\n\n                cy.get('@input')\n                    .type('{del}')\n                    .should('have.value', '+7 (912) 89')\n                    .should('have.prop', 'selectionStart', '+7 (912) '.length)\n                    .should('have.prop', 'selectionEnd', '+7 (912) '.length)\n                    .type('{cmd+z}')\n                    .should('have.value', '+7 (912) 345-67-89')\n                    .should('have.prop', 'selectionStart', '+7 (912) '.length)\n                    .should('have.prop', 'selectionEnd', '+7 (912) 345-67'.length);\n            },\n        );\n    });\n\n    describe('Redo', () => {\n        it('Select all + Delete => Cmd + Z => Cmd + Shift + Z', () => {\n            cy.get('@input')\n                .type('9123456789')\n                .type('{selectall}{del}')\n                .should('have.value', '+7 ')\n                .type('{cmd+z}')\n                .should('have.value', '+7 (912) 345-67-89')\n                .type('{cmd+shift+z}')\n                .should('have.value', '+7 ');\n        });\n\n        it('+7 (912) 345-67|-89 => Backspace (x2) => Ctrl + Z (x2) => Ctrl + Y (x2)', () => {\n            cy.get('@input')\n                .type('9123456789')\n                .type('{leftArrow}'.repeat('-89'.length))\n                .type('{backspace}{backspace}')\n                .type('{ctrl+z}{ctrl+z}')\n                .type('{ctrl+y}{ctrl+y}')\n                .should('have.value', '+7 (912) 345-89')\n                .should('have.prop', 'selectionStart', '+7 (912) 345-'.length)\n                .should('have.prop', 'selectionEnd', '+7 (912) 345-'.length);\n        });\n\n        it(\n            '+7 (912) |345-67|-89 => Delete => Cmd + Z => Cmd + Shift + Z',\n            BROWSER_SUPPORTS_REAL_EVENTS,\n            () => {\n                cy.get('@input')\n                    .type('9123456789')\n                    .realPress([\n                        ...repeatKey('ArrowLeft', '-89'.length),\n                        'Shift',\n                        ...repeatKey('ArrowLeft', '345-67'.length),\n                    ]);\n\n                cy.get('@input')\n                    .type('{del}')\n                    .type('{cmd+z}')\n                    .type('{cmd+shift+z}')\n                    .should('have.value', '+7 (912) 89')\n                    .should('have.prop', 'selectionStart', '+7 (912) '.length)\n                    .should('have.prop', 'selectionEnd', '+7 (912) '.length);\n            },\n        );\n    });\n\n    describe('Non-removable country prefix', () => {\n        it('cannot be removed via selectAll + Backspace', () => {\n            cy.get('@input')\n                .type('9123456789')\n                .type('{selectall}{backspace}')\n                .should('have.value', '+7 ')\n                .should('have.prop', 'selectionStart', '+7 '.length)\n                .should('have.prop', 'selectionEnd', '+7 '.length);\n        });\n\n        it('cannot be removed via selectAll + Delete', () => {\n            cy.get('@input')\n                .type('9123456789')\n                .type('{selectall}{del}')\n                .should('have.value', '+7 ')\n                .should('have.prop', 'selectionStart', '+7 '.length)\n                .should('have.prop', 'selectionEnd', '+7 '.length);\n        });\n\n        it('cannot be removed via Backspace', () => {\n            cy.get('@input')\n                .type('9123456789')\n                .type('{backspace}'.repeat('+7 (912) 345-89'.length))\n                .should('have.value', '+7 ')\n                .should('have.prop', 'selectionStart', '+7 '.length)\n                .should('have.prop', 'selectionEnd', '+7 '.length);\n        });\n\n        it('cannot be removed via Delete', () => {\n            cy.get('@input')\n                .type('9123456789')\n                .type('{moveToStart}')\n                .type('{del}'.repeat('+7 (912) 345-89'.length))\n                .should('have.value', '+7 ')\n                .should('have.prop', 'selectionStart', '+7 '.length)\n                .should('have.prop', 'selectionEnd', '+7 '.length);\n        });\n\n        it('appears on focus if input is empty', () => {\n            cy.get('@input')\n                .blur()\n                .should('have.value', '')\n                .focus()\n                .should('have.value', '+7 ')\n                .should('have.prop', 'selectionStart', '+7 '.length)\n                .should('have.prop', 'selectionEnd', '+7 '.length);\n        });\n\n        it('disappears on blur if there are no more digits except it', () => {\n            cy.get('@input')\n                .focus()\n                .should('have.value', '+7 ')\n                .blur()\n                .should('have.value', '');\n        });\n\n        describe('with caret guard', () => {\n            it('forbids to put caret before country prefix', () => {\n                cy.get('@input')\n                    .should('have.value', '+7 ')\n                    .should('have.prop', 'selectionStart', '+7 '.length)\n                    .should('have.prop', 'selectionEnd', '+7 '.length)\n                    .type('{moveToStart}')\n                    .should('have.value', '+7 ')\n                    .should('have.prop', 'selectionStart', '+7 '.length)\n                    .should('have.prop', 'selectionEnd', '+7 '.length)\n                    .type('{leftArrow}'.repeat(5))\n                    .should('have.value', '+7 ')\n                    .should('have.prop', 'selectionStart', '+7 '.length)\n                    .should('have.prop', 'selectionEnd', '+7 '.length);\n            });\n\n            it('can be selected via selectAll', () => {\n                cy.get('@input')\n                    .type('9123456789')\n                    .type('{selectall}')\n                    .should('have.value', '+7 (912) 345-67-89')\n                    .should('have.prop', 'selectionStart', 0)\n                    .should('have.prop', 'selectionEnd', '+7 (912) 345-67-89'.length);\n            });\n        });\n    });\n\n    describe('New typed character is equal to the previous (already existing) fixed character', () => {\n        it('+7 | => Type 7 => +7 (7', () => {\n            cy.get('@input')\n                .type('7')\n                .should('have.value', '+7 (7')\n                .should('have.prop', 'selectionStart', '+7 (7'.length)\n                .should('have.prop', 'selectionEnd', '+7 (7'.length);\n        });\n\n        it('+7 (7| => Type 7 => +7 (77', () => {\n            cy.get('@input')\n                .type('77')\n                .should('have.value', '+7 (77')\n                .should('have.prop', 'selectionStart', '+7 (77'.length)\n                .should('have.prop', 'selectionEnd', '+7 (77'.length);\n        });\n    });\n\n    it('pressing double space twice does not delete character', () => {\n        cy.get('@input')\n            .type('1234567890')\n            .should('have.value', '+7 (123) 456-78-90')\n            .should('have.prop', 'selectionStart', '+7 (123) 456-78-90'.length)\n            .should('have.prop', 'selectionEnd', '+7 (123) 456-78-90'.length)\n            .type(' ')\n            .type(' ')\n            .should('have.value', '+7 (123) 456-78-90')\n            .should('have.prop', 'selectionStart', '+7 (123) 456-78-90'.length)\n            .should('have.prop', 'selectionEnd', '+7 (123) 456-78-90'.length);\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/placeholder/date.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Placeholder | Date', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.Placeholder);\n        cy.get('#date input')\n            .should('be.visible')\n            .first()\n            .should('have.value', '')\n            .focus()\n            .should('have.value', 'dd/mm/yyyy')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 0)\n            .as('input');\n\n        cy.get('#date tui-input').as('inputWrapper');\n    });\n\n    describe('basic typing (1 character per keydown)', () => {\n        const tests = [\n            // [Typed value, Masked value, caretIndex]\n            ['1', '1d/mm/yyyy', 1],\n            ['16', '16/mm/yyyy', '16'.length],\n            ['160', '16/0m/yyyy', '16/0'.length],\n            ['1605', '16/05/yyyy', '16/05'.length],\n            ['16052', '16/05/2yyy', '16/05/2'.length],\n            ['160520', '16/05/20yy', '16/05/20'.length],\n            ['1605202', '16/05/202y', '16/05/202'.length],\n            ['16052023', '16/05/2023', '16/05/2023'.length],\n        ] as const;\n\n        tests.forEach(([typed, masked, caretIndex]) => {\n            it(`Type ${typed} => ${masked}`, () => {\n                cy.get('@input')\n                    .type(typed)\n                    .should('have.value', masked)\n                    .should('have.prop', 'selectionStart', caretIndex)\n                    .should('have.prop', 'selectionEnd', caretIndex);\n            });\n        });\n    });\n\n    it('Type 999 => 09/09/9yyy', () => {\n        cy.get('@input')\n            .type('999')\n            .should('have.value', '09/09/9yyy')\n            .should('have.prop', 'selectionStart', '09/09/9'.length)\n            .should('have.prop', 'selectionEnd', '09/09/9'.length);\n    });\n\n    it('Type 39 => 03/09/yyyy', () => {\n        cy.get('@input')\n            .type('39')\n            .should('have.value', '03/09/yyyy')\n            .should('have.prop', 'selectionStart', '03/09'.length)\n            .should('have.prop', 'selectionEnd', '03/09'.length);\n    });\n\n    it('Type 31/13 => 31/1m/yyyy', () => {\n        cy.get('@input')\n            .type('3113')\n            .should('have.value', '31/1m/yyyy')\n            .should('have.prop', 'selectionStart', '31/1'.length)\n            .should('have.prop', 'selectionEnd', '31/1'.length);\n    });\n\n    it('Cannot move caret outside actual value', () => {\n        cy.get('@input')\n            .type('311')\n            .type('{rightArrow}')\n            .should('have.prop', 'selectionStart', '31/1'.length)\n            .should('have.prop', 'selectionEnd', '31/1'.length)\n            .type('{selectAll}')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', '31/1'.length);\n    });\n\n    it('Removes placeholder on blur', () => {\n        cy.get('@input')\n            .type('311')\n            .should('have.value', '31/1m/yyyy')\n            .should('have.prop', 'selectionStart', '31/1'.length)\n            .should('have.prop', 'selectionEnd', '31/1'.length)\n            .blur()\n            .should('have.value', '31/1');\n    });\n\n    it('Removes placeholder from Angular control', () => {\n        cy.get('@input')\n            .type('311')\n            .should('have.value', '31/1m/yyyy')\n            .blur()\n            .should('have.value', '31/1');\n\n        cy.get('@inputWrapper').should('have.ngControlValue', '31/1');\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/placeholder/us-phone.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Placeholder | US phone', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.Placeholder);\n        cy.get('#phone input')\n            .should('be.visible')\n            .first()\n            .should('have.value', '')\n            .focus()\n            .should('have.value', '+1 (   ) ___-____')\n            .should('have.prop', 'selectionStart', '+1'.length)\n            .should('have.prop', 'selectionEnd', '+1'.length)\n            .as('input');\n    });\n\n    describe('basic typing (1 character per keydown)', () => {\n        const tests = [\n            // [Typed value, Masked value, valueWithoutPlaceholder]\n            ['2', '+1 (2  ) ___-____', '+1 (2'],\n            ['21', '+1 (21 ) ___-____', '+1 (21'],\n            ['212', '+1 (212) ___-____', '+1 (212'],\n            ['2125', '+1 (212) 5__-____', '+1 (212) 5'],\n            ['21255', '+1 (212) 55_-____', '+1 (212) 55'],\n            ['212555', '+1 (212) 555-____', '+1 (212) 555'],\n            ['2125552', '+1 (212) 555-2___', '+1 (212) 555-2'],\n            ['21255523', '+1 (212) 555-23__', '+1 (212) 555-23'],\n            ['212555236', '+1 (212) 555-236_', '+1 (212) 555-236'],\n            ['2125552368', '+1 (212) 555-2368', '+1 (212) 555-2368'],\n        ] as const;\n\n        tests.forEach(([typed, masked, valueWithoutPlaceholder]) => {\n            it(`Type ${typed} => ${masked}`, () => {\n                cy.get('@input')\n                    .type(typed)\n                    .should('have.value', masked)\n                    .should('have.prop', 'selectionStart', valueWithoutPlaceholder.length)\n                    .should('have.prop', 'selectionEnd', valueWithoutPlaceholder.length)\n                    .blur()\n                    .should('have.value', valueWithoutPlaceholder);\n            });\n        });\n    });\n\n    it('Can type 1 after country code +1', () => {\n        cy.get('@input')\n            .type('1')\n            .should('have.value', '+1 (1  ) ___-____')\n            .should('have.prop', 'selectionStart', '+1 (1'.length)\n            .should('have.prop', 'selectionEnd', '+1 (1'.length);\n    });\n\n    it('cannot erase country code +1', () => {\n        cy.get('@input')\n            .type('{backspace}'.repeat(10))\n            .should('have.value', '+1 (   ) ___-____')\n            .type('{selectAll}{backspace}')\n            .should('have.value', '+1 (   ) ___-____')\n            .type('{selectAll}{del}')\n            .should('have.value', '+1 (   ) ___-____')\n            .should('have.prop', 'selectionStart', '+1'.length)\n            .should('have.prop', 'selectionEnd', '+1'.length);\n    });\n\n    it('cannot move caret outside actual value', () => {\n        cy.get('@input')\n            .type('{rightArrow}')\n            .should('have.prop', 'selectionStart', '+1'.length)\n            .should('have.prop', 'selectionEnd', '+1'.length)\n            .type('{selectAll}')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', '+1'.length);\n    });\n\n    it('Value contains only country code and placeholder => Blur => Value is empty', () => {\n        cy.get('@input')\n            .focus()\n            .should('have.value', '+1 (   ) ___-____')\n            .blur()\n            .should('have.value', '');\n    });\n\n    describe('caret navigation on attempt to erase fixed character', () => {\n        beforeEach(() => {\n            cy.get('@input')\n                .type('2125552')\n                .should('have.value', '+1 (212) 555-2___')\n                .should('have.prop', 'selectionStart', '+1 (212) 555-2'.length)\n                .should('have.prop', 'selectionEnd', '+1 (212) 555-2'.length);\n        });\n\n        it('+1 (212) 555-|2___ => Backspace => +1 (212) 555|-2___', () => {\n            cy.get('@input')\n                .type('{leftArrow}{backspace}')\n                .should('have.value', '+1 (212) 555-2___')\n                .should('have.prop', 'selectionStart', '+1 (212) 555'.length)\n                .should('have.prop', 'selectionEnd', '+1 (212) 555'.length);\n        });\n\n        it('+1 (212) 555|-2___ => Delete => +1 (212) 555-|2___', () => {\n            cy.get('@input')\n                .type('{leftArrow}'.repeat(2))\n                .should('have.prop', 'selectionStart', '+1 (212) 555'.length)\n                .should('have.prop', 'selectionEnd', '+1 (212) 555'.length)\n                .type('{del}')\n                .should('have.value', '+1 (212) 555-2___')\n                .should('have.prop', 'selectionStart', '+1 (212) 555-'.length)\n                .should('have.prop', 'selectionEnd', '+1 (212) 555-'.length);\n        });\n\n        it('+1 (212) |555-2___ => Backspace x2 => +1 (212|) 555-2___', () => {\n            cy.get('@input')\n                .type('{leftArrow}'.repeat('555-2'.length))\n                .type('{backspace}')\n                .should('have.value', '+1 (212) 555-2___')\n                .should('have.prop', 'selectionStart', '+1 (212)'.length)\n                .should('have.prop', 'selectionEnd', '+1 (212)'.length)\n                .type('{backspace}')\n                .should('have.value', '+1 (212) 555-2___')\n                .should('have.prop', 'selectionStart', '+1 (212'.length)\n                .should('have.prop', 'selectionEnd', '+1 (212'.length);\n        });\n\n        it('+1 (212|) 555-2___ => Delete => +1 (212) |555-2___', () => {\n            cy.get('@input')\n                .type('{leftArrow}'.repeat(') 555-2'.length))\n                .should('have.prop', 'selectionStart', '+1 (212'.length)\n                .should('have.prop', 'selectionEnd', '+1 (212'.length)\n                .type('{del}')\n                .should('have.value', '+1 (212) 555-2___')\n                .should('have.prop', 'selectionStart', '+1 (212) '.length)\n                .should('have.prop', 'selectionEnd', '+1 (212) '.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/placeholder/сvc-code.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Placeholder | CVC code', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.Placeholder);\n        cy.get('#cvc input')\n            .should('be.visible')\n            .first()\n            .focus()\n            .should('have.value', 'xxx')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 0)\n            .as('input');\n    });\n\n    it('Type 1 => 1|xx', () => {\n        cy.get('@input')\n            .type('1')\n            .should('have.value', '1xx')\n            .should('have.prop', 'selectionStart', 1)\n            .should('have.prop', 'selectionEnd', 1);\n    });\n\n    it('Type 12 => 12|x', () => {\n        cy.get('@input')\n            .type('12')\n            .should('have.value', '12x')\n            .should('have.prop', 'selectionStart', 2)\n            .should('have.prop', 'selectionEnd', 2);\n    });\n\n    it('Type 123 => 123|', () => {\n        cy.get('@input')\n            .type('123')\n            .should('have.value', '123')\n            .should('have.prop', 'selectionStart', 3)\n            .should('have.prop', 'selectionEnd', 3);\n    });\n\n    it('12|3 => Backspace => 1|3x', () => {\n        cy.get('@input')\n            .type('123')\n            .type('{leftArrow}{backspace}')\n            .should('have.value', '13x')\n            .should('have.prop', 'selectionStart', 1)\n            .should('have.prop', 'selectionEnd', 1);\n    });\n\n    it('1|3x => Type 0 => 10|3', () => {\n        cy.get('@input')\n            .type('13')\n            .type('{leftArrow}0')\n            .should('have.value', '103')\n            .should('have.prop', 'selectionStart', 2)\n            .should('have.prop', 'selectionEnd', 2);\n    });\n\n    it('1xx => select all => backspace => xxx', () => {\n        cy.get('@input')\n            .type('1')\n            .type('{selectAll}{backspace}')\n            .should('have.value', 'xxx')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 0);\n    });\n\n    it('1xx => select all => delete => xxx', () => {\n        cy.get('@input')\n            .type('1')\n            .type('{selectAll}{del}')\n            .should('have.value', 'xxx')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 0);\n    });\n\n    it('1x|x => 1|xx', () => {\n        cy.get('@input')\n            .type('1')\n            .should('have.prop', 'selectionStart', 1)\n            .should('have.prop', 'selectionEnd', 1)\n            .type('{rightArrow}')\n            .should('have.prop', 'selectionStart', 1)\n            .should('have.prop', 'selectionEnd', 1);\n    });\n\n    it('12|x => select all => |12|x', () => {\n        cy.get('@input')\n            .type('12')\n            .should('have.prop', 'selectionStart', 2)\n            .should('have.prop', 'selectionEnd', 2)\n            .type('{selectAll}')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 2);\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/plugins/reject.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Plugins | Reject', () => {\n    const original = 'none';\n    const rejected = 'reject-0';\n\n    beforeEach(() => {\n        cy.visit(DemoPath.KitPlugins);\n        cy.get('#reject input')\n            .should('be.visible')\n            .first()\n            .should('have.value', '')\n            .should('have.css', 'animation-name', original)\n            .focus()\n            .as('input');\n    });\n\n    it('Allows digits', () => {\n        cy.get('@input')\n            .type('1')\n            .should('have.value', '1')\n            .should('have.css', 'animation-name', original);\n    });\n\n    it('Rejects letters', () => {\n        cy.get('@input')\n            .type('1a')\n            .should('have.value', '1')\n            .should('have.css', 'animation-name', rejected);\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/postfix/percentage.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Postfix | Dynamic Pattern Mask Expression', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.Postfix);\n        cy.get('#by-pattern-mask-expression input')\n            .should('be.visible')\n            .first()\n            .should('have.value', '')\n            .focus()\n            .as('input');\n    });\n\n    it('Empty input => Type 1 => 1|%', () => {\n        cy.get('@input')\n            .type('1')\n            .should('have.value', '1%')\n            .should('have.prop', 'selectionStart', 1)\n            .should('have.prop', 'selectionEnd', 1);\n    });\n\n    it('Empty input => Type 10 => 10|%', () => {\n        cy.get('@input')\n            .type('10')\n            .should('have.value', '10%')\n            .should('have.prop', 'selectionStart', 2)\n            .should('have.prop', 'selectionEnd', 2);\n    });\n\n    it('10|% => Backspace => 1|%', () => {\n        cy.get('@input')\n            .type('10')\n            .type('{backspace}')\n            .should('have.value', '1%')\n            .should('have.prop', 'selectionStart', 1)\n            .should('have.prop', 'selectionEnd', 1);\n    });\n\n    it('1|% => Backspace => Empty input', () => {\n        cy.get('@input')\n            .type('1')\n            .type('{backspace}')\n            .should('have.value', '')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 0);\n    });\n\n    it('|53% => Delete => |3%', () => {\n        cy.get('@input')\n            .type('53')\n            .type('{moveToStart}{del}')\n            .should('have.value', '3%')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 0);\n    });\n\n    it('|3% => Delete => Empty input', () => {\n        cy.get('@input')\n            .type('3')\n            .type('{moveToStart}{del}')\n            .should('have.value', '')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 0);\n    });\n\n    it('cannot erase the % (by backspace)', () => {\n        cy.get('@input')\n            .type('42')\n            .type('{moveToEnd}')\n            .should('have.value', '42%')\n            .should('have.prop', 'selectionStart', '42%'.length)\n            .should('have.prop', 'selectionEnd', '42%'.length)\n            .type('{backspace}')\n            .should('have.value', '42%')\n            .should('have.prop', 'selectionStart', '42'.length)\n            .should('have.prop', 'selectionEnd', '42'.length);\n    });\n\n    it('cannot erase the % (by delete)', () => {\n        cy.get('@input')\n            .type('42')\n            .type('{del}')\n            .should('have.value', '42%')\n            .should('have.prop', 'selectionStart', '42%'.length)\n            .should('have.prop', 'selectionEnd', '42%'.length);\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/postfix/postprocessor.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Postfix | Postprocessor (maskitoPostfixPostprocessorGenerator)', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.Postfix);\n        cy.get('#by-postprocessor input')\n            .should('be.visible')\n            .first()\n            .should('have.value', '')\n            .focus()\n            .as('input');\n    });\n\n    it('Type 100 => $100|.00', () => {\n        cy.get('@input')\n            .should('have.value', '$.00')\n            .should('have.prop', 'selectionStart', 1)\n            .should('have.prop', 'selectionEnd', 1)\n            .type('100')\n            .should('have.value', '$100.00')\n            .should('have.prop', 'selectionStart', '$100'.length)\n            .should('have.prop', 'selectionEnd', '$100'.length);\n    });\n\n    it('$10|0.00 => Backspace => Type 5 => $15|0.00', () => {\n        cy.get('@input')\n            .should('have.value', '$.00')\n            .should('have.prop', 'selectionStart', 1)\n            .should('have.prop', 'selectionEnd', 1)\n            .type('100')\n            .type('{leftArrow}{backspace}')\n            .type('5')\n            .should('have.value', '$150.00')\n            .should('have.prop', 'selectionStart', '$15'.length)\n            .should('have.prop', 'selectionEnd', '$15'.length);\n    });\n\n    describe('Attempts to delete the prefix', () => {\n        beforeEach(() => {\n            cy.get('@input')\n                .should('have.value', '$.00')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1)\n                .type('1')\n                .should('have.value', '$1.00')\n                .should('have.prop', 'selectionStart', 2)\n                .should('have.prop', 'selectionEnd', 2);\n        });\n\n        it('$|1.00 => Backspace => $|1.00', () => {\n            cy.get('@input')\n                .type('{leftArrow}')\n                .type('{backspace}')\n                .should('have.value', '$1.00')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        it('$1.00 => Select All => Delete => $|.00', () => {\n            cy.get('@input')\n                .type('{selectAll}{del}')\n                .should('have.value', '$.00')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n    });\n\n    describe('Caret guard works', () => {\n        beforeEach(() => {\n            cy.get('@input')\n                .should('have.value', '$.00')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1)\n                .type('42')\n                .should('have.value', '$42.00');\n        });\n\n        it('forbids to put caret before the prefix', () => {\n            cy.get('@input')\n                .type('{moveToStart}')\n                .should('have.value', '$42.00')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1)\n                .type('{leftArrow}'.repeat(5))\n                .should('have.value', '$42.00')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        it('forbids to put caret after the postfix', () => {\n            cy.get('@input')\n                .type('{moveToEnd}')\n                .should('have.value', '$42.00')\n                .should('have.prop', 'selectionStart', '$42'.length)\n                .should('have.prop', 'selectionEnd', '$42'.length)\n                .type('{rightArrow}'.repeat(5))\n                .should('have.value', '$42.00')\n                .should('have.prop', 'selectionStart', '$42'.length)\n                .should('have.prop', 'selectionEnd', '$42'.length);\n        });\n\n        it('forbids to select prefix/postfix via select all', () => {\n            cy.get('@input')\n                .type('{selectAll}')\n                .should('have.value', '$42.00')\n                .should('have.prop', 'selectionStart', '$'.length)\n                .should('have.prop', 'selectionEnd', '$42'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/prefix/dynamic-pattern-mask-expression.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Prefix | Dynamic Pattern Mask Expression', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.Prefix);\n        cy.get('#by-pattern-mask-expression input')\n            .should('be.visible')\n            .first()\n            .should('have.value', '')\n            .focus()\n            .as('input');\n    });\n\n    it('Empty input => $ => $', () => {\n        cy.get('@input')\n            .type('$')\n            .should('have.value', '$')\n            .should('have.prop', 'selectionStart', 1)\n            .should('have.prop', 'selectionEnd', 1);\n    });\n\n    it('Empty input => Type 42 => $42|', () => {\n        cy.get('@input')\n            .type('42')\n            .should('have.value', '$42')\n            .should('have.prop', 'selectionStart', '$42'.length)\n            .should('have.prop', 'selectionEnd', '$42'.length);\n    });\n\n    it('$42| => Backspace => $4|', () => {\n        cy.get('@input')\n            .type('42')\n            .type('{backspace}')\n            .should('have.value', '$4')\n            .should('have.prop', 'selectionStart', '$4'.length)\n            .should('have.prop', 'selectionEnd', '$4'.length);\n    });\n\n    it('$4| => Backspace => Empty input', () => {\n        cy.get('@input')\n            .type('4')\n            .type('{backspace}')\n            .should('have.value', '')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 0);\n    });\n\n    describe('cannot erase prefix if there are digits after it', () => {\n        it('via Backspace', () => {\n            cy.get('@input')\n                .type('42')\n                .type('{moveToStart}{rightArrow}')\n                .type('{backspace}')\n                .should('have.value', '$42')\n                .should('have.prop', 'selectionStart', 0)\n                .should('have.prop', 'selectionEnd', 0);\n        });\n\n        it('via Delete', () => {\n            cy.get('@input')\n                .type('42')\n                .type('{moveToStart}')\n                .type('{del}')\n                .should('have.value', '$42')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n    });\n\n    describe('rejects extra prefix (dollar sign)', () => {\n        it('Empty input => $$$$$ => $', () => {\n            cy.get('@input')\n                .type('$$$$$')\n                .should('have.value', '$')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        it('Empty input => $42$ => $42|', () => {\n            cy.get('@input')\n                .type('$42$')\n                .should('have.value', '$42')\n                .should('have.prop', 'selectionStart', '$42'.length)\n                .should('have.prop', 'selectionEnd', '$42'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/prefix/postprocessor.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Prefix | Postprocessor (maskitoPrefixPostprocessorGenerator)', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.Prefix);\n        cy.get('#by-postprocessor input')\n            .should('be.visible')\n            .first()\n            .should('have.value', '')\n            .as('input');\n    });\n\n    it('Empty input => Focus => $|', () => {\n        cy.get('@input')\n            .focus()\n            .should('have.value', '$')\n            .should('have.prop', 'selectionStart', 1)\n            .should('have.prop', 'selectionEnd', 1);\n    });\n\n    it('$| => Blur => Empty input', () => {\n        cy.get('@input')\n            .focus()\n            .should('have.value', '$')\n            .should('have.prop', 'selectionStart', 1)\n            .should('have.prop', 'selectionEnd', 1)\n            .blur()\n            .should('have.value', '');\n    });\n\n    it('Empty input => Focus + Type 42 => $42|', () => {\n        cy.get('@input')\n            .focus()\n            .type('42')\n            .should('have.value', '$42')\n            .should('have.prop', 'selectionStart', '$42'.length)\n            .should('have.prop', 'selectionEnd', '$42'.length);\n    });\n\n    it('$42| => Backspace => $4|', () => {\n        cy.get('@input')\n            .focus()\n            .type('42')\n            .type('{backspace}')\n            .should('have.value', '$4')\n            .should('have.prop', 'selectionStart', '$4'.length)\n            .should('have.prop', 'selectionEnd', '$4'.length);\n    });\n\n    it('$4| => Backspace => $|', () => {\n        cy.get('@input')\n            .focus()\n            .type('4')\n            .type('{backspace}')\n            .should('have.value', '$')\n            .should('have.prop', 'selectionStart', 1)\n            .should('have.prop', 'selectionEnd', 1);\n    });\n\n    describe('cannot erase prefix', () => {\n        it('via Backspace (+ do not move caret behind prefix)', () => {\n            cy.get('@input')\n                .focus()\n                .type('42')\n                .type('{moveToStart}{rightArrow}')\n                .type('{backspace}')\n                .should('have.value', '$42')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1)\n                .type('{moveToEnd}')\n                .type('{backspace}'.repeat(5))\n                .should('have.value', '$')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        it('via Delete', () => {\n            cy.get('@input')\n                .focus()\n                .type('42')\n                .type('{moveToStart}')\n                .type('{del}')\n                .should('have.value', '$42')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        it('via selectAll + delete', () => {\n            cy.get('@input')\n                .focus()\n                .type('42')\n                .type('{selectAll}{del}')\n                .should('have.value', '$')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n    });\n\n    describe('rejects extra prefix (dollar sign)', () => {\n        it('Empty input => $$$$$ => $', () => {\n            cy.get('@input')\n                .focus()\n                .type('$$$$$')\n                .should('have.value', '$')\n                .should('have.prop', 'selectionStart', 1)\n                .should('have.prop', 'selectionEnd', 1);\n        });\n\n        it('Empty input => $42$ => $42|', () => {\n            cy.get('@input')\n                .focus()\n                .type('$42$')\n                .should('have.value', '$42')\n                .should('have.prop', 'selectionStart', '$42'.length)\n                .should('have.prop', 'selectionEnd', '$42'.length);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/recipes/textarea/textarea-latin-letters-digits.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Textarea (mask latin letters + digits)', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.Textarea);\n        cy.get('#latin textarea[autocomplete=\"street-address\"]')\n            .should('be.visible')\n            .should('have.value', '')\n            .focus()\n            .as('textArea');\n    });\n\n    describe('Line break (Enter)', () => {\n        it('can insert line break at the beginning', () => {\n            cy.get('@textArea')\n                .type('{enter}')\n                .type('Taiga UI')\n                .should('have.value', '\\nTaiga UI');\n        });\n\n        it('can insert line break at the end', () => {\n            cy.get('@textArea')\n                .type('Taiga')\n                .type('{enter}')\n                .type('UI')\n                .should('have.value', 'Taiga\\nUI');\n        });\n\n        it('can insert line break at the middle', () => {\n            cy.get('@textArea')\n                .type('TaigaUI')\n                .type('{leftArrow}{leftArrow}')\n                .type('{enter}')\n                .should('have.value', 'Taiga\\nUI');\n        });\n\n        it('can insert many line breaks', () => {\n            cy.get('@textArea')\n                .type('Taiga')\n                .type('{enter}{enter}{enter}')\n                .type('UI')\n                .should('have.value', 'Taiga\\n\\n\\nUI');\n        });\n\n        it('`deleteSoftLineBackward` works', () => {\n            cy.get('@textArea')\n                .type('Taiga')\n                .type('{enter}')\n                .type('UI and Maskito')\n                .trigger('beforeinput', {inputType: 'deleteSoftLineBackward'})\n                .trigger('input', {inputType: 'deleteSoftLineBackward'})\n                .should('have.value', 'Taiga\\n');\n        });\n\n        it('`deleteSoftLineForward` works', () => {\n            cy.get('@textArea')\n                .type('Taiga')\n                .type('{enter}')\n                .type('UI and Maskito')\n                .type('{moveToStart}')\n                .trigger('beforeinput', {inputType: 'deleteSoftLineForward'})\n                .trigger('input', {inputType: 'deleteSoftLineForward'})\n                .should('have.value', 'UI and Maskito');\n        });\n    });\n\n    it('accepts spaces', () => {\n        const TYPED_VALUE = '1 2  3   4    5';\n\n        cy.get('@textArea').type(TYPED_VALUE).should('have.value', TYPED_VALUE);\n    });\n\n    it('rejects cyrillic symbols', () => {\n        cy.get('@textArea')\n            .type('123абвгдеёЖзийклмноGgпрстуфхцчшщъыьэюя456')\n            .should('have.value', '123Gg456');\n    });\n\n    describe('Type `deleteWordBackward` of `InputEvent`', () => {\n        const tests = [\n            {initialValue: '1 34 678', newValue: '1 34 '},\n            {initialValue: '1 34 ', newValue: '1 '},\n            {initialValue: '1 34', newValue: '1 '},\n            {initialValue: '1 ', newValue: ''},\n            {initialValue: '1', newValue: ''},\n        ] as const;\n\n        tests.forEach(({initialValue, newValue}) => {\n            it(`\"${initialValue}|\" => Ctrl + Backspace => \"${newValue}|\"`, () => {\n                cy.get('@textArea')\n                    .type(initialValue)\n                    .type('{ctrl+backspace}')\n                    .should('have.value', newValue)\n                    .should('have.prop', 'selectionStart', newValue.length)\n                    .should('have.prop', 'selectionEnd', newValue.length);\n            });\n        });\n    });\n\n    it('Type `deleteWordBackward` of `InputEvent`', () => {\n        cy.get('@textArea')\n            .type('1 34 678')\n            .type('{moveToStart}')\n            .type('{ctrl+del}')\n            .should('have.value', ' 34 678')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 0)\n            .type('{ctrl+del}')\n            .should('have.value', ' 678')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 0)\n            .type('{ctrl+del}')\n            .should('have.value', '')\n            .should('have.prop', 'selectionStart', 0)\n            .should('have.prop', 'selectionEnd', 0);\n    });\n\n    it('allows to paste dot with following space after Backspace', () => {\n        cy.get('@textArea')\n            .type('123')\n            .type('#') // rejected by mask\n            .type('{backspace}')\n            .paste('. ')\n            .should('have.value', '12. ');\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/ssr/ssr.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('Server side rendering', () => {\n    beforeEach(() => {\n        // Just a workaround for correct work of global run-time error handler\n        // See projects/demo-integrations/src/support/e2e.ts\n        cy.visit(DemoPath.WhatIsMaskito);\n    });\n\n    const baseUrl = Cypress.config('baseUrl') ?? '/';\n\n    it('should serve statics and favicon.ico', () => {\n        cy.request(`${baseUrl}/favicon.ico`).its('status').should('equal', 200);\n    });\n\n    it('should successfully render lazy url', () => {\n        cy.request(`${baseUrl}/${DemoPath.Time}`)\n            .its('body')\n            .should('include.match', /<h1.*>\\s+Time/);\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/utils.ts",
    "content": "import {type realPress} from 'cypress-real-events/commands/realPress';\n\nexport function range(from: number, to: number): number[] {\n    return Array.from({length: to - from + 1}).map((_, i) => from + i);\n}\n\nexport function withCaretLabel(value: string, caretIndex: number): string {\n    return `${value.slice(0, caretIndex)}|${value.slice(caretIndex)}`;\n}\n\nexport function repeatKey<T extends Parameters<typeof realPress>[0]>(\n    key: T,\n    times: number,\n): readonly T[] {\n    return Array.from({length: times}).map(() => key);\n}\n"
  },
  {
    "path": "projects/demo-integrations/src/tests/vue/vue.cy.ts",
    "content": "import {DemoPath} from '@demo/constants';\n\ndescribe('@maskito/vue | Basic', () => {\n    beforeEach(() => {\n        cy.visit(DemoPath.Vue);\n        cy.get('#example input').should('be.visible').clear().as('input');\n    });\n\n    it('rejects invalid characters', () => {\n        cy.get('@input').type('1a2b3c').should('have.value', '123');\n    });\n\n    it('accepts valid digits', () => {\n        cy.get('@input').type('123456789').should('have.value', '123_456_789');\n    });\n});\n"
  },
  {
    "path": "projects/demo-integrations/tsconfig.json",
    "content": "{\n    \"extends\": \"../../tsconfig.json\",\n    \"compilerOptions\": {\n        \"typeRoots\": [\"../../node_modules/@types\", \"../../node_modules/cypress/types\"],\n        \"types\": [\"cypress\", \"node\"]\n    },\n    \"include\": [\n        \"**/*.ts\",\n        \"**/*.tsx\",\n        \"**/*.d.ts\"\n    ],\n    \"exclude\": []\n}\n"
  },
  {
    "path": "projects/demo-integrations/vite.config.ts",
    "content": "import {nxViteTsPaths} from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\nimport react from '@vitejs/plugin-react';\nimport {defineConfig} from 'vite';\n\nexport default defineConfig({\n    plugins: [\n        nxViteTsPaths(), // https://nx.dev/recipes/vite/configure-vite#typescript-paths\n        react(), // https://nx.dev/recipes/vite/configure-vite#framework-plugins\n    ],\n});\n"
  },
  {
    "path": "projects/kit/README.md",
    "content": "# @maskito/kit\n\n[![npm version](https://img.shields.io/npm/v/@maskito/kit.svg)](https://npmjs.com/package/@maskito/kit)\n[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@maskito/kit)](https://bundlephobia.com/result?p=@maskito/kit)\n\n<p align=\"center\">\n    <img src=\"https://raw.githubusercontent.com/taiga-family/maskito/main/projects/demo/src/assets/icons/maskito.svg\" alt=\"Maskito logo\" width=\"120px\">\n</p>\n\n<p align=\"center\">\n    <a href=\"https://maskito.dev\">Documentation</a> •\n    <a href=\"https://github.com/taiga-family/maskito/issues/new/choose\">Submit an Issue</a> •\n    <a href=\"https://t.me/taiga_ui/10600\">Contact Us</a>\n</p>\n\n> The optional framework-agnostic Maskito's package.<br />It contains ready-to-use masks with configurable parameters.\n\n## How to install\n\n```bash\nnpm i @maskito/{core,kit}\n```\n"
  },
  {
    "path": "projects/kit/jest.config.ts",
    "content": "export default {\n    displayName: 'kit',\n    preset: '../../jest.preset.js',\n    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],\n    coverageDirectory: '../../coverage/kit',\n};\n"
  },
  {
    "path": "projects/kit/package.json",
    "content": "{\n    \"name\": \"@maskito/kit\",\n    \"version\": \"5.2.2\",\n    \"description\": \"The optional framework-agnostic Maskito's package with ready-to-use masks\",\n    \"keywords\": [\n        \"input\",\n        \"mask\",\n        \"inputmask\",\n        \"input-mask\",\n        \"text-mask\",\n        \"format\",\n        \"input-format\",\n        \"input-formatting\",\n        \"javascript\",\n        \"typescript\"\n    ],\n    \"homepage\": \"https://maskito.dev\",\n    \"bugs\": \"https://github.com/taiga-family/maskito/issues\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/taiga-family/maskito.git\"\n    },\n    \"license\": \"Apache-2.0\",\n    \"author\": {\n        \"email\": \"nikita.s.barsukov@gmail.com\",\n        \"name\": \"Nikita Barsukov\",\n        \"url\": \"https://github.com/nsbarsukov\"\n    },\n    \"contributors\": [\n        {\n            \"email\": \"alexander@inkin.ru\",\n            \"name\": \"Alex Inkin\"\n        },\n        {\n            \"email\": \"vladimir.potekh@gmail.com\",\n            \"name\": \"Vladimir Potekhin\"\n        },\n        {\n            \"email\": \"nikita.s.barsukov@gmail.com\",\n            \"name\": \"Nikita Barsukov\"\n        },\n        {\n            \"email\": \"nextzeddicus@gmail.com\",\n            \"name\": \"Georgiy Lunin\"\n        }\n    ],\n    \"peerDependencies\": {\n        \"@maskito/core\": \"^5.2.2\"\n    }\n}\n"
  },
  {
    "path": "projects/kit/project.json",
    "content": "{\n    \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n    \"name\": \"kit\",\n    \"implicitDependencies\": [\"!demo\"],\n    \"projectType\": \"library\",\n    \"sourceRoot\": \"projects/kit/src\",\n    \"tags\": [],\n    \"targets\": {\n        \"build\": {\n            \"dependsOn\": [\n                {\n                    \"dependencies\": true,\n                    \"params\": \"forward\",\n                    \"target\": \"build\"\n                }\n            ],\n            \"executor\": \"@nx/rollup:rollup\",\n            \"options\": {\n                \"assets\": [\n                    {\n                        \"glob\": \"README.md\",\n                        \"input\": \"{projectRoot}\",\n                        \"output\": \".\"\n                    }\n                ],\n                \"compiler\": \"tsc\",\n                \"external\": \"all\",\n                \"format\": [\"esm\", \"cjs\"],\n                \"main\": \"{projectRoot}/src/index.ts\",\n                \"outputPath\": \"dist/{projectName}\",\n                \"project\": \"{projectRoot}/package.json\",\n                \"tsConfig\": \"tsconfig.build.json\",\n                \"useLegacyTypescriptPlugin\": false\n            },\n            \"outputs\": [\"{options.outputPath}\"]\n        },\n        \"publish\": {\n            \"dependsOn\": [\n                {\n                    \"params\": \"ignore\",\n                    \"target\": \"build\"\n                }\n            ],\n            \"executor\": \"nx:run-commands\",\n            \"options\": {\n                \"command\": \"npm publish ./dist/{projectName} --ignore-scripts\"\n            }\n        },\n        \"test\": {\n            \"executor\": \"@nx/jest:jest\",\n            \"options\": {\n                \"jestConfig\": \"{projectRoot}/jest.config.ts\"\n            },\n            \"outputs\": [\"{workspaceRoot}/coverage/{projectName}\"]\n        }\n    }\n}\n"
  },
  {
    "path": "projects/kit/src/index.ts",
    "content": "export {\n    maskitoDateOptionsGenerator,\n    type MaskitoDateParams,\n    maskitoParseDate,\n    maskitoStringifyDate,\n} from './lib/masks/date';\nexport {maskitoDateRangeOptionsGenerator} from './lib/masks/date-range';\nexport {\n    maskitoDateTimeOptionsGenerator,\n    type MaskitoDateTimeParams,\n    maskitoParseDateTime,\n    maskitoStringifyDateTime,\n} from './lib/masks/date-time';\nexport {\n    maskitoNumberOptionsGenerator,\n    type MaskitoNumberParams,\n    maskitoParseNumber,\n    maskitoStringifyNumber,\n} from './lib/masks/number';\nexport {\n    maskitoParseTime,\n    maskitoStringifyTime,\n    maskitoTimeOptionsGenerator,\n    type MaskitoTimeParams,\n} from './lib/masks/time';\nexport {\n    maskitoAddOnFocusPlugin,\n    maskitoCaretGuard,\n    maskitoEventHandler,\n    maskitoRejectEvent,\n    maskitoRemoveOnBlurPlugin,\n    maskitoSelectionChangeHandler,\n} from './lib/plugins';\nexport {\n    maskitoPostfixPostprocessorGenerator,\n    maskitoPrefixPostprocessorGenerator,\n    maskitoWithPlaceholder,\n} from './lib/processors';\nexport type {\n    MaskitoDateMode,\n    MaskitoDateSegments,\n    MaskitoTimeMode,\n    MaskitoTimeSegments,\n} from './lib/types';\n"
  },
  {
    "path": "projects/kit/src/lib/constants/date-segment-max-values.ts",
    "content": "import type {MaskitoDateSegments} from '../types';\n\nexport const DATE_SEGMENTS_MAX_VALUES: MaskitoDateSegments<number> = {\n    day: 31,\n    month: 12,\n    year: 9999,\n};\n"
  },
  {
    "path": "projects/kit/src/lib/constants/default-decimal-pseudo-separators.ts",
    "content": "export const DEFAULT_DECIMAL_PSEUDO_SEPARATORS = ['.', ',', 'б', 'ю'];\n"
  },
  {
    "path": "projects/kit/src/lib/constants/default-min-max-dates.ts",
    "content": "export const DEFAULT_MIN_DATE = new Date('0001-01-01T00:00');\nexport const DEFAULT_MAX_DATE = new Date('9999-12-31T23:59:59.999');\n"
  },
  {
    "path": "projects/kit/src/lib/constants/default-pseudo-minuses.ts",
    "content": "import {\n    CHAR_EM_DASH,\n    CHAR_EN_DASH,\n    CHAR_HYPHEN,\n    CHAR_JP_HYPHEN,\n    CHAR_MINUS,\n} from './unicode-characters';\n\nexport const DEFAULT_PSEUDO_MINUSES = [\n    CHAR_HYPHEN,\n    CHAR_EN_DASH,\n    CHAR_EM_DASH,\n    CHAR_JP_HYPHEN,\n    CHAR_MINUS,\n];\n"
  },
  {
    "path": "projects/kit/src/lib/constants/default-time-segment-bounds.ts",
    "content": "import type {MaskitoTimeSegments} from '../types';\n\nexport const DEFAULT_TIME_SEGMENT_MAX_VALUES: MaskitoTimeSegments<number> = {\n    hours: 23,\n    minutes: 59,\n    seconds: 59,\n    milliseconds: 999,\n};\n\nexport const DEFAULT_TIME_SEGMENT_MIN_VALUES: MaskitoTimeSegments<number> = {\n    hours: 0,\n    minutes: 0,\n    seconds: 0,\n    milliseconds: 0,\n};\n"
  },
  {
    "path": "projects/kit/src/lib/constants/index.ts",
    "content": "export * from './date-segment-max-values';\nexport * from './default-decimal-pseudo-separators';\nexport * from './default-min-max-dates';\nexport * from './default-pseudo-minuses';\nexport * from './default-time-segment-bounds';\nexport * from './meridiem';\nexport * from './time-fixed-characters';\nexport * from './time-segment-value-lengths';\nexport * from './unicode-characters';\n"
  },
  {
    "path": "projects/kit/src/lib/constants/meridiem.ts",
    "content": "import {CHAR_NO_BREAK_SPACE} from './unicode-characters';\n\nexport const ANY_MERIDIEM_CHARACTER_RE = new RegExp(`[${CHAR_NO_BREAK_SPACE}APM]+$`, 'g');\nexport const ALL_MERIDIEM_CHARACTERS_RE = new RegExp(`${CHAR_NO_BREAK_SPACE}[AP]M$`, 'g');\n"
  },
  {
    "path": "projects/kit/src/lib/constants/time-fixed-characters.ts",
    "content": "export const TIME_FIXED_CHARACTERS = [':', '.'];\n"
  },
  {
    "path": "projects/kit/src/lib/constants/time-segment-value-lengths.ts",
    "content": "import type {MaskitoTimeSegments} from '../types';\n\nexport const TIME_SEGMENT_VALUE_LENGTHS: MaskitoTimeSegments<number> = {\n    hours: 2,\n    minutes: 2,\n    seconds: 2,\n    milliseconds: 3,\n};\n"
  },
  {
    "path": "projects/kit/src/lib/constants/unicode-characters.ts",
    "content": "/**\n * {@link https://unicode-table.com/en/00A0/ Non-breaking space}.\n */\nexport const CHAR_NO_BREAK_SPACE = '\\u00A0';\n\n/**\n * {@link https://symbl.cc/en/200B/ Zero width space}.\n */\nexport const CHAR_ZERO_WIDTH_SPACE = '\\u200B';\n\n/**\n * {@link https://unicode-table.com/en/2013/ EN dash}\n * is used to indicate a range of numbers or a span of time.\n * @example 2006–2022\n */\nexport const CHAR_EN_DASH = '\\u2013';\n\n/**\n * {@link https://unicode-table.com/en/2014/ EM dash}\n * is used to mark a break in a sentence.\n * @example Taiga UI — powerful set of open source components for Angular\n * ___\n * Don't confuse with {@link CHAR_EN_DASH} or {@link CHAR_HYPHEN}!\n */\nexport const CHAR_EM_DASH = '\\u2014';\n\n/**\n * {@link https://unicode-table.com/en/002D/ Hyphen (minus sign)}\n * is used to combine words.\n * @example well-behaved\n * ___\n * Don't confuse with {@link CHAR_EN_DASH} or {@link CHAR_EM_DASH}!\n */\nexport const CHAR_HYPHEN = '\\u002D';\n\n/**\n * {@link https://unicode-table.com/en/2212/ Minus}\n * is used as math operator symbol or before negative digits.\n * ---\n * Can be used as `&minus;`. Don't confuse with {@link CHAR_HYPHEN}\n */\nexport const CHAR_MINUS = '\\u2212';\n\n/**\n * {@link https://symbl.cc/en/30FC/ Katakana-Hiragana Prolonged Sound Mark}\n * is used as prolonged sounds in Japanese.\n */\nexport const CHAR_JP_HYPHEN = '\\u30FC';\n\n/**\n * {@link https://symbl.cc/en/003A/ Colon}\n * is a punctuation mark that connects parts of a text logically.\n * ---\n * is also used as separator in time.\n */\nexport const CHAR_COLON = '\\u003A';\n\n/**\n * {@link https://symbl.cc/en/FF1A/ Full-width colon}\n * is a full-width punctuation mark used to separate parts of a text commonly in Japanese.\n */\nexport const CHAR_JP_COLON = '\\uFF1A';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date/date-mask.ts",
    "content": "import {MASKITO_DEFAULT_OPTIONS, type MaskitoOptions} from '@maskito/core';\n\nimport {\n    createDateSegmentsZeroPaddingPostprocessor,\n    createFullWidthToHalfWidthPreprocessor,\n    createMinMaxDatePostprocessor,\n    createValidDatePreprocessor,\n    createZeroPlaceholdersPreprocessor,\n    normalizeDatePreprocessor,\n} from '../../processors';\nimport type {MaskitoDateParams} from './date-params';\n\nexport function maskitoDateOptionsGenerator({\n    mode,\n    separator = '.',\n    max,\n    min,\n}: MaskitoDateParams): Required<MaskitoOptions> {\n    const dateModeTemplate = mode.split('/').join(separator);\n\n    return {\n        ...MASKITO_DEFAULT_OPTIONS,\n        mask: Array.from(dateModeTemplate).map((char) =>\n            separator.includes(char) ? char : /\\d/,\n        ),\n        overwriteMode: 'replace',\n        preprocessors: [\n            createFullWidthToHalfWidthPreprocessor(),\n            createZeroPlaceholdersPreprocessor(),\n            normalizeDatePreprocessor({\n                dateModeTemplate,\n                dateSegmentsSeparator: separator,\n            }),\n            createValidDatePreprocessor({\n                dateModeTemplate,\n                dateSegmentsSeparator: separator,\n            }),\n        ],\n        postprocessors: [\n            createDateSegmentsZeroPaddingPostprocessor({\n                dateModeTemplate,\n                dateSegmentSeparator: separator,\n                splitFn: (value) => ({dateStrings: [value]}),\n                uniteFn: ([dateString = '']) => dateString,\n            }),\n            createMinMaxDatePostprocessor({\n                min,\n                max,\n                dateModeTemplate,\n                dateSegmentSeparator: separator,\n            }),\n        ],\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date/date-params.ts",
    "content": "import type {MaskitoDateMode} from '../../types';\n\nexport interface MaskitoDateParams {\n    mode: MaskitoDateMode;\n    separator?: string;\n    max?: Date;\n    min?: Date;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date/index.ts",
    "content": "export * from './date-mask';\nexport * from './date-params';\nexport * from './utils';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date/tests/date-mask.spec.ts",
    "content": "/**\n * If any of these tests fail,\n * it can mean that browser autofill or composition are not working properly\n * for Date mask\n */\nimport {beforeEach, describe, expect, it} from '@jest/globals';\nimport {\n    MASKITO_DEFAULT_OPTIONS,\n    type MaskitoOptions,\n    maskitoTransform,\n} from '@maskito/core';\nimport {maskitoDateOptionsGenerator} from '@maskito/kit';\n\ndescribe('Date (maskitoTransform)', () => {\n    describe('[mode]=\"yyyy/mm/dd\"', () => {\n        let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n        beforeEach(() => {\n            options = maskitoDateOptionsGenerator({\n                mode: 'yyyy/mm/dd',\n                separator: '/',\n            });\n        });\n\n        describe('pads digits with zero if date segment exceeds its max possible value', () => {\n            describe('pads digit > 1 with zero for months', () => {\n                [0, 1].forEach((digit) => {\n                    it(`1234/${digit} => 1234/${digit}`, () => {\n                        expect(maskitoTransform(`1234${digit}`, options)).toBe(\n                            `1234/${digit}`,\n                        );\n                        expect(maskitoTransform(`1234/${digit}`, options)).toBe(\n                            `1234/${digit}`,\n                        );\n                    });\n                });\n\n                [2, 3, 4, 5, 6, 7, 8, 9].forEach((digit) => {\n                    it(`1234/${digit} => 1234/0${digit}`, () => {\n                        expect(maskitoTransform(`1234${digit}`, options)).toBe(\n                            `1234/0${digit}`,\n                        );\n                        expect(maskitoTransform(`1234/${digit}`, options)).toBe(\n                            `1234/0${digit}`,\n                        );\n                    });\n                });\n            });\n\n            describe('pads digit > 3 with zero for days', () => {\n                [0, 1, 2, 3].forEach((digit) => {\n                    it(`1234/12/${digit} => 1234/12/${digit}`, () => {\n                        expect(maskitoTransform(`123412${digit}`, options)).toBe(\n                            `1234/12/${digit}`,\n                        );\n                        expect(maskitoTransform(`1234/12/${digit}`, options)).toBe(\n                            `1234/12/${digit}`,\n                        );\n                    });\n                });\n\n                [4, 5, 6, 7, 8, 9].forEach((digit) => {\n                    it(`1234/12/${digit} => 1234/12/0${digit}`, () => {\n                        expect(maskitoTransform(`123412${digit}`, options)).toBe(\n                            `1234/12/0${digit}`,\n                        );\n                        expect(maskitoTransform(`1234/12/${digit}`, options)).toBe(\n                            `1234/12/0${digit}`,\n                        );\n                    });\n                });\n            });\n        });\n\n        it('accepts full width characters', () => {\n            expect(maskitoTransform('１２３４５', options)).toBe('1234/05');\n            expect(maskitoTransform('１２３４１２２６', options)).toBe('1234/12/26');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date/utils/index.ts",
    "content": "export {maskitoParseDate} from './parse-date';\nexport {maskitoStringifyDate} from './stringify-date';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date/utils/parse-date.ts",
    "content": "import {DEFAULT_MAX_DATE, DEFAULT_MIN_DATE} from '../../../constants';\nimport {clamp, parseDateString, segmentsToDate} from '../../../utils';\nimport type {MaskitoDateParams} from '../date-params';\n\nexport function maskitoParseDate(\n    value: string,\n    {mode, min = DEFAULT_MIN_DATE, max = DEFAULT_MAX_DATE}: MaskitoDateParams,\n): Date | null {\n    const digitsPattern = mode.replaceAll(/[^dmy]/g, '');\n    const digits = value.replaceAll(/\\D+/g, '');\n\n    if (digits.length !== digitsPattern.length) {\n        return null;\n    }\n\n    const dateSegments = parseDateString(value, mode);\n\n    const parsedDate = segmentsToDate(dateSegments);\n\n    return mode.includes('y') ? clamp(parsedDate, min, max) : parsedDate;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date/utils/stringify-date.ts",
    "content": "import {DEFAULT_MAX_DATE, DEFAULT_MIN_DATE} from '../../../constants';\nimport {clamp, toDateString} from '../../../utils';\nimport type {MaskitoDateParams} from '../date-params';\nimport {toDateSegments} from './to-date-segments';\n\nexport function maskitoStringifyDate(\n    date: Date,\n    {\n        mode,\n        separator = '.',\n        min = DEFAULT_MIN_DATE,\n        max = DEFAULT_MAX_DATE,\n    }: MaskitoDateParams,\n): string {\n    const validatedDate = clamp(date, min, max);\n    const {year, ...segments} = toDateSegments(validatedDate);\n\n    return toDateString(\n        {...segments, year: year.padStart(mode.match(/y/g)?.length ?? 0, '0')},\n        {dateMode: mode.replaceAll('/', separator)},\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date/utils/tests/parse-date.spec.ts",
    "content": "import type {MaskitoDateParams} from '../../date-params';\nimport {maskitoParseDate} from '../parse-date';\n\ndescribe('maskitoParseDate', () => {\n    describe('mode = mm/dd/yyyy, separator = default', () => {\n        let params: MaskitoDateParams;\n\n        beforeEach(() => {\n            params = {\n                mode: 'mm/dd/yyyy',\n                min: new Date('2004-02-05T00:00:00.000'),\n                max: new Date('2024-01-01T00:00:00.000'),\n            };\n        });\n\n        it('should correctly parse 04/29/2012', () => {\n            const parsedDate = maskitoParseDate('04/29/2012', params);\n\n            expect(parsedDate?.getTime()).toBe(Date.parse('2012-04-29T00:00:00.000'));\n        });\n\n        it('should return min date on 02/04/2004', () => {\n            const parsedDate = maskitoParseDate('02/04/2004', params);\n\n            expect(parsedDate?.getTime()).toBe(params.min?.getTime());\n        });\n\n        it('should return max date on 01/02/2024', () => {\n            const parsedDate = maskitoParseDate('01/02/2024', params);\n\n            expect(parsedDate?.getTime()).toBe(params.max?.getTime());\n        });\n    });\n\n    describe('incomplete, mode = mm/dd/yyyy, separator = default', () => {\n        let params: MaskitoDateParams;\n\n        beforeEach(() => {\n            params = {\n                mode: 'mm/dd/yyyy',\n                min: new Date('2004-02-05T00:00:00.000'),\n                max: new Date('2024-01-03T00:00:00.000'),\n            };\n        });\n\n        it('should return null, 04/mm/yyyy', () => {\n            const parsedDate = maskitoParseDate('04', params);\n\n            expect(parsedDate).toBeNull();\n        });\n\n        it('should return null 02/04/yyyy', () => {\n            const parsedDate = maskitoParseDate('02/04', params);\n\n            expect(parsedDate).toBeNull();\n        });\n\n        it('should return null 01/02/2yyy', () => {\n            const parsedDate = maskitoParseDate('01/02/2', params);\n\n            expect(parsedDate).toBeNull();\n        });\n\n        it('should return null 01/02/20yy', () => {\n            const parsedDate = maskitoParseDate('01/02/20', params);\n\n            expect(parsedDate).toBeNull();\n        });\n\n        it('should return null 01/02/202y', () => {\n            const parsedDate = maskitoParseDate('01/02/202', params);\n\n            expect(parsedDate).toBeNull();\n        });\n\n        it('should return date on 01/02/2024', () => {\n            const parsedDate = maskitoParseDate('01/02/2024', params);\n\n            expect(parsedDate?.getTime()).toBe(Date.parse('2024-01-02T00:00:00.000'));\n        });\n    });\n\n    describe('mode = mm/dd/yyyy, separator = -', () => {\n        let params: MaskitoDateParams;\n\n        beforeEach(() => {\n            params = {\n                mode: 'mm/dd/yyyy',\n                separator: '-',\n                min: new Date('2004-02-28T00:00:00.000'),\n                max: new Date('2030-01-01T00:00:00.000'),\n            };\n        });\n\n        it('should correctly parse 12-31-2020', () => {\n            const parsedDate = maskitoParseDate('12-31-2020', params);\n\n            expect(parsedDate?.getTime()).toBe(Date.parse('2020-12-31T00:00:00.000'));\n        });\n\n        it('should return min date on 02-27-2004', () => {\n            const parsedDate = maskitoParseDate('02-27-2004', params);\n\n            expect(parsedDate?.getTime()).toBe(params.min?.getTime());\n        });\n\n        it('should return max date on 01/02/2030', () => {\n            const parsedDate = maskitoParseDate('01/02/2030', params);\n\n            expect(parsedDate?.getTime()).toBe(params.max?.getTime());\n        });\n    });\n\n    describe('mode = mm/yy, separator = :', () => {\n        let params: MaskitoDateParams;\n\n        beforeEach(() => {\n            params = {\n                mode: 'mm/yy',\n                separator: ':',\n                min: new Date('2004-02-28T00:00:00.000'),\n                max: new Date('2030-01-01T00:00:00.000'),\n            };\n        });\n\n        it('should correctly parse 02:12', () => {\n            const parsedDate = maskitoParseDate('02:12', params);\n\n            expect(parsedDate?.getTime()).toBe(Date.parse('2012-02-01T00:00:00.000'));\n        });\n\n        it('should return min date on 01:03', () => {\n            const parsedDate = maskitoParseDate('01:03', params);\n\n            expect(parsedDate?.getTime()).toBe(params.min?.getTime());\n        });\n\n        it('should return max date on 02:31', () => {\n            const parsedDate = maskitoParseDate('02:30', params);\n\n            expect(parsedDate?.getTime()).toBe(params.max?.getTime());\n        });\n    });\n\n    describe('mode = yyyy/mm, separator = .', () => {\n        let params: MaskitoDateParams;\n\n        beforeEach(() => {\n            params = {\n                mode: 'yyyy/mm',\n                separator: '.',\n                min: new Date('2004-02-28T00:00:00.000'),\n                max: new Date('2030-01-01T00:00:00.000'),\n            };\n        });\n\n        it('should correctly parse 2012.08', () => {\n            const parsedDate = maskitoParseDate('2012.08', params);\n\n            expect(parsedDate?.getTime()).toBe(Date.parse('2012-08-01T00:00:00.000'));\n        });\n\n        it('should return min date on 1991.01', () => {\n            const parsedDate = maskitoParseDate('1991.01', params);\n\n            expect(parsedDate?.getTime()).toBe(params.min?.getTime());\n        });\n\n        it('should return max date on 2033.12', () => {\n            const parsedDate = maskitoParseDate('2033.12', params);\n\n            expect(parsedDate?.getTime()).toBe(params.max?.getTime());\n        });\n    });\n\n    describe('mode without year segment', () => {\n        it('should parse mm/dd and preserve month/day values', () => {\n            const parsedDate = maskitoParseDate('12/25', {\n                mode: 'mm/dd',\n                separator: '/',\n            });\n\n            expect(parsedDate?.getTime()).toBe(Date.parse('0000-12-25T00:00:00.000'));\n        });\n\n        it('should parse dd/mm and preserve day/month values', () => {\n            const parsedDate = maskitoParseDate('25/12', {\n                mode: 'dd/mm',\n                separator: '/',\n            });\n\n            expect(parsedDate?.getTime()).toBe(Date.parse('0000-12-25T00:00:00.000'));\n        });\n    });\n\n    describe('invalid date strings', () => {\n        const params: MaskitoDateParams = {\n            mode: 'mm/dd/yyyy',\n            min: new Date('2000-01-01T00:00:00.000'),\n            max: new Date('2030-01-01T00:00:00.000'),\n        };\n\n        it.each([\n            'this-is-not-a-date',\n            '',\n            '   ',\n            '12//2020',\n            '/31/2020',\n            '12/31/',\n            '12/31/20',\n            '12/31/abcd',\n            '1a/31/2020',\n        ])('should return null for \"%s\"', (value) => {\n            expect(maskitoParseDate(value, params)).toBeNull();\n        });\n\n        it('should return null for a date with incomplete year and multi-char separator', () => {\n            expect(\n                maskitoParseDate('16-01-20', {\n                    mode: 'dd/mm/yyyy',\n                    separator: '-',\n                }),\n            ).toBeNull();\n\n            expect(\n                maskitoParseDate('16--01--20', {\n                    mode: 'dd/mm/yyyy',\n                    separator: '--',\n                }),\n            ).toBeNull();\n        });\n\n        it('should parse a date with complete year and multi-char separator', () => {\n            expect(\n                maskitoParseDate('16-01-2026', {\n                    mode: 'dd/mm/yyyy',\n                    separator: '-',\n                })?.getTime(),\n            ).toBe(Date.parse('2026-01-16T00:00:00.000'));\n\n            expect(\n                maskitoParseDate('16--01--2026', {\n                    mode: 'dd/mm/yyyy',\n                    separator: '--',\n                })?.getTime(),\n            ).toBe(Date.parse('2026-01-16T00:00:00.000'));\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date/utils/tests/stringify-date.spec.ts",
    "content": "import type {MaskitoDateMode} from '../../../../types';\nimport type {MaskitoDateParams} from '../../date-params';\nimport {maskitoStringifyDate} from '../stringify-date';\n\ndescribe('maskitoStringifyDate', () => {\n    const testCases = new Map<\n        MaskitoDateMode,\n        Array<Partial<MaskitoDateParams> & {date: Date; text: string}>\n    >([\n        [\n            'dd/mm',\n            [\n                {date: new Date('2004-02-01'), text: '01.02'},\n                {date: new Date('2012-12-30'), separator: '-', text: '30-12'},\n                {\n                    date: new Date('2012-12-30'),\n                    max: new Date('2012-12-29'),\n                    text: '29.12',\n                },\n                {\n                    date: new Date('2012-12-12'),\n                    min: new Date('2012-12-13'),\n                    text: '13.12',\n                },\n            ],\n        ],\n        [\n            'dd/mm/yyyy',\n            [\n                {date: new Date('2012-03-25'), text: '25.03.2012'},\n                {\n                    date: new Date('2024-03-25'),\n                    max: new Date('2024-01-31'),\n                    separator: '/',\n                    text: '31/01/2024',\n                },\n                {\n                    date: new Date('2024-01-31'),\n                    min: new Date('2024-03-25'),\n                    separator: ':',\n                    text: '25:03:2024',\n                },\n            ],\n        ],\n        [\n            'mm/dd',\n            [\n                {date: new Date('2004-02-01'), text: '02.01'},\n                {date: new Date('2012-12-30'), separator: '-', text: '12-30'},\n                {\n                    date: new Date('2012-12-30'),\n                    max: new Date('2012-12-29'),\n                    text: '12.29',\n                },\n                {\n                    date: new Date('2012-12-12'),\n                    min: new Date('2012-12-13'),\n                    text: '12.13',\n                },\n            ],\n        ],\n        [\n            'mm/dd/yyyy',\n            [\n                {date: new Date('2000-10-01'), text: '10.01.2000'},\n                {\n                    date: new Date('2024-03-25'),\n                    max: new Date('2024-01-31'),\n                    separator: '/',\n                    text: '01/31/2024',\n                },\n                {\n                    date: new Date('2024-01-31'),\n                    min: new Date('2024-03-25'),\n                    separator: '-',\n                    text: '03-25-2024',\n                },\n            ],\n        ],\n        [\n            'mm/yy',\n            [\n                {date: new Date('2000-10-01'), text: '10.00'},\n                {\n                    date: new Date('2012-10-20'),\n                    max: new Date('2012-10-19'),\n                    separator: '/',\n                    text: '10/12',\n                },\n                {\n                    date: new Date('2024-01-31'),\n                    min: new Date('2024-03-25'),\n                    text: '03.24',\n                },\n            ],\n        ],\n        [\n            'mm/yyyy',\n            [\n                {date: new Date('2000-10-01'), text: '10.2000'},\n                {\n                    date: new Date('2012-10-20'),\n                    max: new Date('2012-10-19'),\n                    separator: '/',\n                    text: '10/2012',\n                },\n                {\n                    date: new Date('2024-01-31'),\n                    min: new Date('2024-03-25'),\n                    text: '03.2024',\n                },\n            ],\n        ],\n        [\n            'yyyy',\n            [\n                {date: new Date('2000-10-01'), text: '2000'},\n                {\n                    date: new Date('2012-10-20'),\n                    max: new Date('2011-10-19'),\n                    text: '2011',\n                },\n                {\n                    date: new Date('2024-01-31'),\n                    min: new Date('2050-03-25'),\n                    text: '2050',\n                },\n            ],\n        ],\n        [\n            'yyyy/mm',\n            [\n                {date: new Date('2000-10-01'), text: '2000.10'},\n                {\n                    date: new Date('2012-10-20'),\n                    max: new Date('2012-09-19'),\n                    separator: '-',\n                    text: '2012-09',\n                },\n                {\n                    date: new Date('2024-01-31'),\n                    min: new Date('2024-03-25'),\n                    text: '2024.03',\n                },\n            ],\n        ],\n        [\n            'yyyy/mm/dd',\n            [\n                {date: new Date('2000-10-01'), text: '2000.10.01'},\n                {\n                    date: new Date('2024-03-25'),\n                    max: new Date('2024-01-31'),\n                    separator: '/',\n                    text: '2024/01/31',\n                },\n                {\n                    date: new Date('2024-01-31'),\n                    min: new Date('2024-03-25'),\n                    text: '2024.03.25',\n                },\n            ],\n        ],\n    ]);\n\n    testCases.forEach((cases, mode) => {\n        describe(`mode ${mode}`, () => {\n            cases.forEach(({date, separator, text, min, max}) => {\n                it(`${date.toString()} => '${text}'`, () => {\n                    expect(maskitoStringifyDate(date, {mode, separator, min, max})).toBe(\n                        text,\n                    );\n                });\n            });\n        });\n    });\n\n    describe('year contains leading zeroes', () => {\n        const date = new Date('0042-02-13T00:00:00.000');\n\n        it('dd/mm/yyyy', () => {\n            expect(maskitoStringifyDate(date, {mode: 'dd/mm/yyyy'})).toBe('13.02.0042');\n        });\n\n        it('yyyy/mm/dd', () => {\n            expect(maskitoStringifyDate(date, {mode: 'yyyy/mm/dd'})).toBe('0042.02.13');\n        });\n\n        it('mm/yy', () => {\n            expect(maskitoStringifyDate(date, {mode: 'mm/yy'})).toBe('02.42');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date/utils/to-date-segments.ts",
    "content": "import type {MaskitoDateSegments} from '../../../types';\n\nconst formatter = Intl.DateTimeFormat('en-US', {\n    month: '2-digit',\n    day: '2-digit',\n    year: 'numeric',\n});\n\nexport function toDateSegments(date: Date): MaskitoDateSegments {\n    return formatter\n        .formatToParts(date)\n        .reduce(\n            (acc, part) => ({...acc, [part.type]: part.value}),\n            {} as MaskitoDateSegments,\n        );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-range/constants.ts",
    "content": "import {\n    CHAR_EM_DASH,\n    CHAR_EN_DASH,\n    CHAR_HYPHEN,\n    CHAR_JP_HYPHEN,\n    CHAR_MINUS,\n} from '../../constants';\n\nexport const POSSIBLE_DATE_RANGE_SEPARATOR = [\n    CHAR_HYPHEN,\n    CHAR_EN_DASH,\n    CHAR_EM_DASH,\n    CHAR_MINUS,\n    CHAR_JP_HYPHEN,\n];\n\nexport const MIN_DAY = 1;\n\nexport const MONTHS_IN_YEAR = 12;\n\nexport const MonthNumber = {\n    January: 0,\n    February: 1,\n    March: 2,\n    April: 3,\n    May: 4,\n    June: 5,\n    July: 6,\n    August: 7,\n    September: 8,\n    October: 9,\n    November: 10,\n    December: 11,\n} as const;\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-range/date-range-mask.ts",
    "content": "import {MASKITO_DEFAULT_OPTIONS, type MaskitoOptions} from '@maskito/core';\n\nimport {CHAR_EN_DASH, CHAR_NO_BREAK_SPACE} from '../../constants';\nimport {\n    createDateSegmentsZeroPaddingPostprocessor,\n    createFirstDateEndSeparatorPreprocessor,\n    createFullWidthToHalfWidthPreprocessor,\n    createMinMaxDatePostprocessor,\n    createValidDatePreprocessor,\n    createZeroPlaceholdersPreprocessor,\n    normalizeDatePreprocessor,\n} from '../../processors';\nimport type {MaskitoDateMode, MaskitoDateSegments} from '../../types';\nimport {parseDateRangeString} from '../../utils';\nimport {POSSIBLE_DATE_RANGE_SEPARATOR} from './constants';\nimport {createMinMaxRangeLengthPostprocessor} from './processors/min-max-range-length-postprocessor';\nimport {createSwapDatesPostprocessor} from './processors/swap-dates-postprocessor';\n\nexport function maskitoDateRangeOptionsGenerator({\n    mode,\n    min,\n    max,\n    minLength,\n    maxLength,\n    dateSeparator = '.',\n    rangeSeparator = `${CHAR_NO_BREAK_SPACE}${CHAR_EN_DASH}${CHAR_NO_BREAK_SPACE}`,\n}: {\n    mode: MaskitoDateMode;\n    min?: Date;\n    max?: Date;\n    minLength?: Partial<MaskitoDateSegments<number>>;\n    maxLength?: Partial<MaskitoDateSegments<number>>;\n    dateSeparator?: string;\n    rangeSeparator?: string;\n}): Required<MaskitoOptions> {\n    const dateModeTemplate = mode.split('/').join(dateSeparator);\n    const dateMask = Array.from(dateModeTemplate).map((char) =>\n        dateSeparator.includes(char) ? char : /\\d/,\n    );\n\n    return {\n        ...MASKITO_DEFAULT_OPTIONS,\n        mask: [...dateMask, ...Array.from(rangeSeparator), ...dateMask],\n        overwriteMode: 'replace',\n        preprocessors: [\n            createFullWidthToHalfWidthPreprocessor(),\n            createFirstDateEndSeparatorPreprocessor({\n                dateModeTemplate,\n                dateSegmentSeparator: dateSeparator,\n                firstDateEndSeparator: rangeSeparator,\n                pseudoFirstDateEndSeparators: POSSIBLE_DATE_RANGE_SEPARATOR,\n            }),\n            createZeroPlaceholdersPreprocessor(),\n            normalizeDatePreprocessor({\n                dateModeTemplate,\n                rangeSeparator,\n                dateSegmentsSeparator: dateSeparator,\n            }),\n            createValidDatePreprocessor({\n                dateModeTemplate,\n                rangeSeparator,\n                dateSegmentsSeparator: dateSeparator,\n            }),\n        ],\n        postprocessors: [\n            createDateSegmentsZeroPaddingPostprocessor({\n                dateModeTemplate,\n                dateSegmentSeparator: dateSeparator,\n                splitFn: (value) => ({\n                    dateStrings: parseDateRangeString(\n                        value,\n                        dateModeTemplate,\n                        rangeSeparator,\n                    ),\n                }),\n                uniteFn: (validatedDateStrings, initialValue) =>\n                    validatedDateStrings.reduce(\n                        (acc, dateString, dateIndex) =>\n                            `${acc}${dateString}${\n                                !dateIndex && initialValue.includes(rangeSeparator)\n                                    ? rangeSeparator\n                                    : ''\n                            }`,\n                        '',\n                    ),\n            }),\n            createMinMaxDatePostprocessor({\n                min,\n                max,\n                dateModeTemplate,\n                rangeSeparator,\n                dateSegmentSeparator: dateSeparator,\n            }),\n            createMinMaxRangeLengthPostprocessor({\n                dateModeTemplate,\n                minLength,\n                maxLength,\n                max,\n                rangeSeparator,\n            }),\n            createSwapDatesPostprocessor({\n                dateModeTemplate,\n                rangeSeparator,\n            }),\n        ],\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-range/index.ts",
    "content": "export * from './date-range-mask';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-range/processors/min-max-range-length-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\n\nimport {DEFAULT_MAX_DATE} from '../../../constants';\nimport type {MaskitoDateSegments} from '../../../types';\nimport {\n    appendDate,\n    clamp,\n    dateToSegments,\n    identity,\n    isDateStringComplete,\n    isEmpty,\n    parseDateRangeString,\n    parseDateString,\n    segmentsToDate,\n    toDateString,\n} from '../../../utils';\n\nexport function createMinMaxRangeLengthPostprocessor({\n    dateModeTemplate,\n    rangeSeparator,\n    minLength,\n    maxLength,\n    max = DEFAULT_MAX_DATE,\n}: {\n    dateModeTemplate: string;\n    rangeSeparator: string;\n    max?: Date;\n    minLength?: Partial<MaskitoDateSegments<number>>;\n    maxLength?: Partial<MaskitoDateSegments<number>>;\n}): MaskitoPostprocessor {\n    if (isEmpty(minLength) && isEmpty(maxLength)) {\n        return identity;\n    }\n\n    return ({value, selection}) => {\n        const dateStrings = parseDateRangeString(value, dateModeTemplate, rangeSeparator);\n\n        if (\n            dateStrings.length !== 2 ||\n            dateStrings.some((date) => !isDateStringComplete(date, dateModeTemplate))\n        ) {\n            return {value, selection};\n        }\n\n        const [fromDate, toDate] = dateStrings.map((dateString) =>\n            segmentsToDate(parseDateString(dateString, dateModeTemplate)),\n        );\n\n        if (!fromDate || !toDate) {\n            return {value, selection};\n        }\n\n        const minDistantToDate = appendDate(fromDate, minLength);\n        const maxDistantToDate = isEmpty(maxLength)\n            ? max\n            : appendDate(fromDate, maxLength);\n\n        const minLengthClampedToDate = clamp(toDate, minDistantToDate, max);\n        const minMaxLengthClampedToDate =\n            minLengthClampedToDate > maxDistantToDate\n                ? maxDistantToDate\n                : minLengthClampedToDate;\n\n        return {\n            selection,\n            value:\n                dateStrings[0] +\n                rangeSeparator +\n                toDateString(dateToSegments(minMaxLengthClampedToDate), {\n                    dateMode: dateModeTemplate,\n                }),\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-range/processors/swap-dates-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\n\nimport {\n    isDateStringComplete,\n    parseDateRangeString,\n    parseDateString,\n    segmentsToDate,\n} from '../../../utils';\n\nexport function createSwapDatesPostprocessor({\n    dateModeTemplate,\n    rangeSeparator,\n}: {\n    dateModeTemplate: string;\n    rangeSeparator: string;\n}): MaskitoPostprocessor {\n    return ({value, selection}) => {\n        const dateStrings = parseDateRangeString(value, dateModeTemplate, rangeSeparator);\n        const isDateRangeComplete =\n            dateStrings.length === 2 &&\n            dateStrings.every((date) => isDateStringComplete(date, dateModeTemplate));\n        const [from, to] = selection;\n        const caretAtTheEnd = from >= value.length;\n        const allValueSelected = from === 0 && to >= value.length; // dropping text inside with a pointer\n\n        if (!(caretAtTheEnd || allValueSelected) || !isDateRangeComplete) {\n            return {value, selection};\n        }\n\n        const [fromDate, toDate] = dateStrings.map((dateString) =>\n            segmentsToDate(parseDateString(dateString, dateModeTemplate)),\n        );\n\n        return {\n            selection,\n            value:\n                fromDate && toDate && fromDate > toDate\n                    ? dateStrings.reverse().join(rangeSeparator)\n                    : value,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-range/tests/date-segments-zero-padding.spec.ts",
    "content": "import {beforeEach, describe, expect, it} from '@jest/globals';\nimport {\n    MASKITO_DEFAULT_OPTIONS,\n    type MaskitoOptions,\n    maskitoTransform,\n} from '@maskito/core';\nimport {maskitoDateRangeOptionsGenerator} from '@maskito/kit';\n\ndescribe('DateRange (maskitoTransform) | Date segments zero padding', () => {\n    describe('[mode]=\"yyyy/mm/dd\"', () => {\n        let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n        beforeEach(() => {\n            options = maskitoDateRangeOptionsGenerator({\n                mode: 'yyyy/mm/dd',\n                dateSeparator: '/',\n                rangeSeparator: '-',\n            });\n        });\n\n        describe('pads digits with zero if date segment exceeds its max possible value', () => {\n            describe('pads digit > 1 with zero for months', () => {\n                [0, 1].forEach((digit) => {\n                    it(`1234/${digit} => 1234/${digit}`, () => {\n                        expect(maskitoTransform(`1234${digit}`, options)).toBe(\n                            `1234/${digit}`,\n                        );\n                        expect(\n                            maskitoTransform(`1234/01/01-1234/${digit}`, options),\n                        ).toBe(`1234/01/01-1234/${digit}`);\n                    });\n                });\n\n                [2, 3, 4, 5, 6, 7, 8, 9].forEach((digit) => {\n                    it(`1234/${digit} => 1234/0${digit}`, () => {\n                        expect(maskitoTransform(`1234${digit}`, options)).toBe(\n                            `1234/0${digit}`,\n                        );\n                        expect(\n                            maskitoTransform(`1234/01/01-1234/${digit}`, options),\n                        ).toBe(`1234/01/01-1234/0${digit}`);\n                    });\n                });\n            });\n\n            describe('pads digit > 3 with zero for days', () => {\n                [0, 1, 2, 3].forEach((digit) => {\n                    it(`1234/12/${digit} => 1234/12/${digit}`, () => {\n                        expect(maskitoTransform(`123412${digit}`, options)).toBe(\n                            `1234/12/${digit}`,\n                        );\n                        expect(\n                            maskitoTransform(`1234/01/01-1234/12/${digit}`, options),\n                        ).toBe(`1234/01/01-1234/12/${digit}`);\n                    });\n                });\n\n                [4, 5, 6, 7, 8, 9].forEach((digit) => {\n                    it(`1234/12/${digit} => 1234/12/0${digit}`, () => {\n                        expect(maskitoTransform(`123412${digit}`, options)).toBe(\n                            `1234/12/0${digit}`,\n                        );\n                        expect(\n                            maskitoTransform(`1234/01/01-1234/12/${digit}`, options),\n                        ).toBe(`1234/01/01-1234/12/0${digit}`);\n                    });\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-range/tests/pseudo-range-separators.spec.ts",
    "content": "import {beforeEach, describe, expect, it} from '@jest/globals';\nimport {\n    MASKITO_DEFAULT_OPTIONS,\n    type MaskitoOptions,\n    maskitoTransform,\n} from '@maskito/core';\nimport {maskitoDateRangeOptionsGenerator} from '@maskito/kit';\n\nimport {CHAR_EM_DASH, CHAR_EN_DASH, CHAR_HYPHEN, CHAR_MINUS} from '../../../constants';\n\ndescribe('DateRange (maskitoTransform) | Pseudo range separators', () => {\n    let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n    beforeEach(() => {\n        options = maskitoDateRangeOptionsGenerator({\n            mode: 'dd/mm/yyyy',\n            dateSeparator: '.',\n            rangeSeparator: CHAR_EN_DASH,\n        });\n    });\n\n    it('works with already valid range separator', () => {\n        expect(maskitoTransform(`01012000${CHAR_EN_DASH}10102000`, options)).toBe(\n            `01.01.2000${CHAR_EN_DASH}10.10.2000`,\n        );\n        expect(maskitoTransform(`01012000 ${CHAR_EN_DASH} 10102000`, options)).toBe(\n            `01.01.2000${CHAR_EN_DASH}10.10.2000`,\n        );\n    });\n\n    it('replaces hyphen with valid range separator', () => {\n        expect(maskitoTransform(`01012000${CHAR_HYPHEN}10102000`, options)).toBe(\n            `01.01.2000${CHAR_EN_DASH}10.10.2000`,\n        );\n    });\n\n    it('replaces em-dash with valid range separator', () => {\n        expect(maskitoTransform(`01012000${CHAR_EM_DASH}10102000`, options)).toBe(\n            `01.01.2000${CHAR_EN_DASH}10.10.2000`,\n        );\n    });\n\n    it('replaces minus with valid range separator', () => {\n        expect(maskitoTransform(`01012000${CHAR_MINUS}10102000`, options)).toBe(\n            `01.01.2000${CHAR_EN_DASH}10.10.2000`,\n        );\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/constants/date-time-separator.ts",
    "content": "export const DATE_TIME_SEPARATOR = ', ';\nexport const POSSIBLE_DATE_TIME_SEPARATOR = [',', ' '];\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/constants/index.ts",
    "content": "export * from './date-time-separator';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/date-time-mask.ts",
    "content": "import {MASKITO_DEFAULT_OPTIONS, type MaskitoOptions} from '@maskito/core';\n\nimport {\n    DEFAULT_TIME_SEGMENT_MAX_VALUES,\n    DEFAULT_TIME_SEGMENT_MIN_VALUES,\n} from '../../constants';\nimport {\n    createMeridiemSteppingPlugin,\n    createTimeSegmentsSteppingPlugin,\n} from '../../plugins';\nimport {\n    createColonConvertPreprocessor,\n    createDateSegmentsZeroPaddingPostprocessor,\n    createFirstDateEndSeparatorPreprocessor,\n    createFullWidthToHalfWidthPreprocessor,\n    createInvalidTimeSegmentInsertionPreprocessor,\n    createMeridiemPostprocessor,\n    createMeridiemPreprocessor,\n    createZeroPlaceholdersPreprocessor,\n    normalizeDatePreprocessor,\n} from '../../processors';\nimport type {MaskitoTimeSegments} from '../../types';\nimport {createTimeMaskExpression} from '../../utils/time';\nimport {DATE_TIME_SEPARATOR} from './constants';\nimport type {MaskitoDateTimeParams} from './date-time-params';\nimport {createMinMaxDateTimePostprocessor} from './postprocessors';\nimport {createValidDateTimePreprocessor} from './preprocessors';\nimport {splitDateTimeString} from './utils';\n\nexport function maskitoDateTimeOptionsGenerator({\n    dateMode,\n    timeMode,\n    dateSeparator = '.',\n    min,\n    max,\n    dateTimeSeparator = DATE_TIME_SEPARATOR,\n    timeStep = 0,\n}: MaskitoDateTimeParams): Required<MaskitoOptions> {\n    const hasMeridiem = timeMode.includes('AA');\n    const dateModeTemplate = dateMode.split('/').join(dateSeparator);\n    const timeSegmentMaxValues: MaskitoTimeSegments<number> = {\n        ...DEFAULT_TIME_SEGMENT_MAX_VALUES,\n        ...(hasMeridiem ? {hours: 12} : {}),\n    };\n    const timeSegmentMinValues: MaskitoTimeSegments<number> = {\n        ...DEFAULT_TIME_SEGMENT_MIN_VALUES,\n        ...(hasMeridiem ? {hours: 1} : {}),\n    };\n    const fullMode = `${dateModeTemplate}${dateTimeSeparator}${timeMode}`;\n\n    return {\n        ...MASKITO_DEFAULT_OPTIONS,\n        mask: [\n            ...Array.from(dateModeTemplate).map((char) =>\n                dateSeparator.includes(char) ? char : /\\d/,\n            ),\n            ...dateTimeSeparator.split(''),\n            ...createTimeMaskExpression(timeMode),\n        ],\n        overwriteMode: 'replace',\n        preprocessors: [\n            createFullWidthToHalfWidthPreprocessor(),\n            createColonConvertPreprocessor(),\n            createFirstDateEndSeparatorPreprocessor({\n                dateModeTemplate,\n                dateSegmentSeparator: dateSeparator,\n                firstDateEndSeparator: dateTimeSeparator,\n                pseudoFirstDateEndSeparators: dateTimeSeparator.split(''),\n            }),\n            createZeroPlaceholdersPreprocessor(),\n            createMeridiemPreprocessor(timeMode),\n            normalizeDatePreprocessor({\n                dateModeTemplate,\n                dateSegmentsSeparator: dateSeparator,\n                dateTimeSeparator,\n            }),\n            createInvalidTimeSegmentInsertionPreprocessor({\n                timeMode,\n                timeSegmentMinValues,\n                timeSegmentMaxValues,\n                parseValue: (x) => {\n                    const [dateString, timeString] = splitDateTimeString(\n                        x,\n                        dateModeTemplate,\n                    );\n\n                    return {timeString, restValue: `${dateString}${dateTimeSeparator}`};\n                },\n            }),\n            createValidDateTimePreprocessor({\n                dateModeTemplate,\n                dateSegmentsSeparator: dateSeparator,\n                dateTimeSeparator,\n                timeMode,\n                timeSegmentMaxValues,\n            }),\n        ],\n        postprocessors: [\n            createMeridiemPostprocessor(timeMode),\n            createDateSegmentsZeroPaddingPostprocessor({\n                dateModeTemplate,\n                dateSegmentSeparator: dateSeparator,\n                splitFn: (value) => {\n                    const [dateString, timeString] = splitDateTimeString(\n                        value,\n                        dateModeTemplate,\n                    );\n\n                    return {dateStrings: [dateString], restPart: timeString};\n                },\n                uniteFn: ([validatedDateString], initialValue) =>\n                    validatedDateString +\n                    (initialValue.includes(dateTimeSeparator) ? dateTimeSeparator : ''),\n            }),\n            createMinMaxDateTimePostprocessor({\n                min,\n                max,\n                dateModeTemplate,\n                timeMode,\n                dateTimeSeparator,\n            }),\n        ],\n        plugins: [\n            createTimeSegmentsSteppingPlugin({\n                step: timeStep,\n                fullMode,\n                timeSegmentMinValues,\n                timeSegmentMaxValues,\n            }),\n            createMeridiemSteppingPlugin(fullMode.indexOf('AA')),\n        ],\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/date-time-params.ts",
    "content": "import type {MaskitoDateMode, MaskitoTimeMode} from '../../types';\n\nexport interface MaskitoDateTimeParams {\n    dateMode: MaskitoDateMode;\n    timeMode: MaskitoTimeMode;\n    dateSeparator?: string;\n    max?: Date;\n    min?: Date;\n    dateTimeSeparator?: string;\n    timeStep?: number;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/index.ts",
    "content": "export * from './constants';\nexport * from './date-time-mask';\nexport * from './date-time-params';\nexport * from './postprocessors';\nexport * from './preprocessors';\nexport * from './utils';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/postprocessors/index.ts",
    "content": "export * from './min-max-date-time-postprocessor';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/postprocessors/min-max-date-time-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\n\nimport {DEFAULT_MAX_DATE, DEFAULT_MIN_DATE} from '../../../constants';\nimport type {MaskitoTimeMode} from '../../../types';\nimport {\n    clamp,\n    dateToSegments,\n    isDateStringComplete,\n    parseDateString,\n    segmentsToDate,\n    toDateString,\n} from '../../../utils';\nimport {raiseSegmentValueToMin} from '../../../utils/date/raise-segment-value-to-min';\nimport {parseTimeString} from '../../../utils/time';\nimport {isDateTimeStringComplete, splitDateTimeString} from '../utils';\n\nexport function createMinMaxDateTimePostprocessor({\n    dateModeTemplate,\n    timeMode,\n    min = DEFAULT_MIN_DATE,\n    max = DEFAULT_MAX_DATE,\n    dateTimeSeparator,\n}: {\n    dateModeTemplate: string;\n    timeMode: MaskitoTimeMode;\n    min?: Date;\n    max?: Date;\n    dateTimeSeparator: string;\n}): MaskitoPostprocessor {\n    return ({value, selection}) => {\n        const [dateString, timeString] = splitDateTimeString(value, dateModeTemplate);\n        const parsedDate = parseDateString(dateString, dateModeTemplate);\n        const parsedTime = parseTimeString(timeString, timeMode);\n\n        if (\n            !isDateTimeStringComplete(value, {\n                dateMode: dateModeTemplate,\n                timeMode,\n                dateTimeSeparator,\n            })\n        ) {\n            const fixedDate = raiseSegmentValueToMin(parsedDate, dateModeTemplate);\n            const {year, month, day} = isDateStringComplete(dateString, dateModeTemplate)\n                ? dateToSegments(clamp(segmentsToDate(fixedDate), min, max))\n                : fixedDate;\n\n            const fixedValue = toDateString(\n                {\n                    year,\n                    month,\n                    day,\n                    ...parsedTime,\n                },\n                {dateMode: dateModeTemplate, dateTimeSeparator, timeMode},\n            );\n            const tail = value.slice(fixedValue.length);\n\n            return {\n                selection,\n                value: `${fixedValue}${tail}`,\n            };\n        }\n\n        const date = segmentsToDate(parsedDate, parsedTime);\n        const clampedDate = clamp(date, min, max);\n        // trailing segment separators or meridiem characters\n        const [trailingNonDigitCharacters = ''] = value.match(/\\D+$/g) || [];\n\n        const validatedValue = `${toDateString(dateToSegments(clampedDate), {\n            dateMode: dateModeTemplate,\n            dateTimeSeparator,\n            timeMode,\n        })}${trailingNonDigitCharacters}`;\n\n        return {\n            selection,\n            value: validatedValue,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/preprocessors/index.ts",
    "content": "export * from './valid-date-time-preprocessor';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/preprocessors/valid-date-time-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport type {MaskitoTimeMode, MaskitoTimeSegments} from '../../../types';\nimport {validateDateString} from '../../../utils';\nimport {enrichTimeSegmentsWithZeroes} from '../../../utils/time';\nimport {splitDateTimeString} from '../utils';\n\nexport function createValidDateTimePreprocessor({\n    dateModeTemplate,\n    dateSegmentsSeparator,\n    dateTimeSeparator,\n    timeMode,\n    timeSegmentMaxValues,\n}: {\n    dateModeTemplate: string;\n    dateSegmentsSeparator: string;\n    dateTimeSeparator: string;\n    timeMode: MaskitoTimeMode;\n    timeSegmentMaxValues: MaskitoTimeSegments<number>;\n}): MaskitoPreprocessor {\n    return ({elementState, data}) => {\n        const {value, selection} = elementState;\n\n        if (data === dateSegmentsSeparator) {\n            return {\n                elementState,\n                data: selection[0] === value.length ? data : '',\n            };\n        }\n\n        const newCharacters = data.replaceAll(/\\D/g, '');\n\n        if (!newCharacters) {\n            return {elementState, data};\n        }\n\n        const [from, rawTo] = selection;\n        let to = rawTo + data.length;\n        const newPossibleValue = `${value.slice(0, from)}${newCharacters}${value.slice(to)}`;\n\n        const [dateString, timeString] = splitDateTimeString(\n            newPossibleValue,\n            dateModeTemplate,\n        );\n        let validatedValue = '';\n        const hasDateTimeSeparator = newPossibleValue.includes(dateTimeSeparator);\n\n        const {validatedDateString, updatedSelection} = validateDateString({\n            dateString,\n            dateSegmentsSeparator,\n            dateModeTemplate,\n            offset: 0,\n            selection: [from, to],\n        });\n\n        if (dateString && !validatedDateString) {\n            return {elementState, data: ''}; // prevent changes\n        }\n\n        to = updatedSelection[1];\n\n        validatedValue += validatedDateString;\n\n        const updatedTimeState = enrichTimeSegmentsWithZeroes(\n            {value: timeString, selection: [from, to]},\n            {mode: timeMode, timeSegmentMaxValues},\n        );\n\n        to = updatedTimeState.selection[1];\n\n        validatedValue += hasDateTimeSeparator\n            ? `${dateTimeSeparator}${updatedTimeState.value}`\n            : updatedTimeState.value;\n\n        const newData = validatedValue.slice(from, to);\n\n        return {\n            elementState: {\n                selection,\n                value: `${validatedValue.slice(0, from)}${newData\n                    .split(dateSegmentsSeparator)\n                    .map((segment) => '0'.repeat(segment.length))\n                    .join(dateSegmentsSeparator)}${validatedValue.slice(to)}`,\n            },\n            data: newData,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/tests/date-segments-zero-padding.spec.ts",
    "content": "import {beforeEach, describe, expect, it} from '@jest/globals';\nimport {\n    MASKITO_DEFAULT_OPTIONS,\n    type MaskitoOptions,\n    maskitoTransform,\n} from '@maskito/core';\nimport {maskitoDateTimeOptionsGenerator} from '@maskito/kit';\n\ndescribe('DateTime (maskitoTransform) | Date segments zero padding', () => {\n    describe('[dateMode]=\"dd/mm/yyyy\" & [timeMode]=\"HH:MM:SS.MSS\"', () => {\n        let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n        beforeEach(() => {\n            options = maskitoDateTimeOptionsGenerator({\n                dateMode: 'dd/mm/yyyy',\n                timeMode: 'HH:MM:SS.MSS',\n                dateSeparator: '/',\n            });\n        });\n\n        describe('pads digits with zero if date segment exceeds its max possible value', () => {\n            describe('pads digit > 1 with zero for months', () => {\n                [0, 1].forEach((digit) => {\n                    it(`01/${digit} => 01/${digit}`, () => {\n                        expect(maskitoTransform(`01${digit}`, options)).toBe(\n                            `01/${digit}`,\n                        );\n                    });\n                });\n\n                [2, 3, 4, 5, 6, 7, 8, 9].forEach((digit) => {\n                    it(`01/${digit} => 01/0${digit}`, () => {\n                        expect(maskitoTransform(`01${digit}`, options)).toBe(\n                            `01/0${digit}`,\n                        );\n                    });\n                });\n            });\n\n            describe('pads digit > 3 with zero for days', () => {\n                [0, 1, 2, 3].forEach((digit) => {\n                    it(`${digit} => ${digit}`, () => {\n                        expect(maskitoTransform(`${digit}`, options)).toBe(`${digit}`);\n                    });\n                });\n\n                [4, 5, 6, 7, 8, 9].forEach((digit) => {\n                    it(`${digit} => 0${digit}`, () => {\n                        expect(maskitoTransform(`${digit}`, options)).toBe(`0${digit}`);\n                    });\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/tests/date-time-separator.spec.ts",
    "content": "import {beforeEach, describe, expect, it} from '@jest/globals';\nimport {MASKITO_DEFAULT_OPTIONS, maskitoTransform} from '@maskito/core';\n\nimport type {MaskitoTimeMode} from '../../../types';\nimport {maskitoDateTimeOptionsGenerator} from '../date-time-mask';\n\ndescribe('DateTime | dateTimeSeparator', () => {\n    const dateTimeSeparators = [':', ';_', '_-_', '_at_'];\n    let options = MASKITO_DEFAULT_OPTIONS;\n\n    dateTimeSeparators.forEach((dateTimeSeparator) => {\n        const testCases: Array<{\n            typedDigits: string;\n            formattedValue: string;\n            timeMode: MaskitoTimeMode;\n        }> = [\n            {\n                typedDigits: '050220040341',\n                formattedValue: `05.02.2004${dateTimeSeparator}03:41`,\n                timeMode: 'HH:MM',\n            },\n            {\n                typedDigits: '10062007034111',\n                formattedValue: `10.06.2007${dateTimeSeparator}03:41:11`,\n                timeMode: 'HH:MM:SS',\n            },\n            {\n                typedDigits: '15081999034111111',\n                formattedValue: `15.08.1999${dateTimeSeparator}03:41:11.111`,\n                timeMode: 'HH:MM:SS.MSS',\n            },\n        ];\n\n        describe(`correctly applies \"${dateTimeSeparator}\" as dateTimeSeparator`, () => {\n            testCases.forEach(({typedDigits, formattedValue, timeMode}) => {\n                beforeEach(() => {\n                    options = maskitoDateTimeOptionsGenerator({\n                        dateMode: 'dd/mm/yyyy',\n                        timeMode,\n                        dateTimeSeparator,\n                    });\n                });\n\n                it(`${typedDigits} => ${formattedValue}`, () => {\n                    expect(maskitoTransform(typedDigits, options)).toBe(formattedValue);\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/tests/pseudo-date-end-separator.spec.ts",
    "content": "import {beforeEach, describe, expect, it} from '@jest/globals';\nimport {\n    MASKITO_DEFAULT_OPTIONS,\n    type MaskitoOptions,\n    maskitoTransform,\n} from '@maskito/core';\nimport {maskitoDateTimeOptionsGenerator} from '@maskito/kit';\n\nimport {DATE_TIME_SEPARATOR} from '../constants';\n\ndescribe('DateTime (maskitoTransform) | Pseudo date end separators', () => {\n    let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n    beforeEach(() => {\n        options = maskitoDateTimeOptionsGenerator({\n            dateMode: 'dd/mm/yyyy',\n            timeMode: 'HH:MM:SS.MSS',\n            dateSeparator: '.',\n        });\n    });\n\n    it('works with already valid range separator', () => {\n        expect(maskitoTransform(`01012000${DATE_TIME_SEPARATOR}235959999`, options)).toBe(\n            `01.01.2000${DATE_TIME_SEPARATOR}23:59:59.999`,\n        );\n    });\n\n    it('replaces space with valid date end separator', () => {\n        expect(maskitoTransform('01012000 ', options)).toBe(\n            `01.01.2000${DATE_TIME_SEPARATOR}`,\n        );\n        expect(maskitoTransform('01012000 2359', options)).toBe(\n            `01.01.2000${DATE_TIME_SEPARATOR}23:59`,\n        );\n    });\n\n    it('replaces comma with valid range separator', () => {\n        expect(maskitoTransform('01012000,', options)).toBe(\n            `01.01.2000${DATE_TIME_SEPARATOR}`,\n        );\n        expect(maskitoTransform('01012000,235959999', options)).toBe(\n            `01.01.2000${DATE_TIME_SEPARATOR}23:59:59.999`,\n        );\n    });\n\n    it('does not add anything if separator does not initially exist', () => {\n        expect(maskitoTransform('01012000', options)).toBe('01.01.2000');\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/utils/index.ts",
    "content": "export * from './is-date-time-string-complete';\nexport * from './parse-date-time';\nexport * from './split-date-time-string';\nexport * from './stringify-date-time';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/utils/is-date-time-string-complete.ts",
    "content": "import type {MaskitoTimeMode} from '../../../types';\nimport {DATE_TIME_SEPARATOR} from '../constants';\n\nexport function isDateTimeStringComplete(\n    dateTimeString: string,\n    {\n        dateMode,\n        timeMode,\n        dateTimeSeparator = DATE_TIME_SEPARATOR,\n    }: {\n        dateMode: string;\n        timeMode: MaskitoTimeMode;\n        dateTimeSeparator: string;\n    },\n): boolean {\n    return (\n        dateTimeString.length >=\n            dateMode.length + timeMode.length + dateTimeSeparator.length &&\n        (dateTimeString.split(dateTimeSeparator)[0] ?? '')\n            .split(/\\D/)\n            .every((segment) => !/^0+$/.exec(segment))\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/utils/parse-date-time.ts",
    "content": "import {DEFAULT_MAX_DATE, DEFAULT_MIN_DATE} from '../../../constants';\nimport {clamp} from '../../../utils';\nimport {maskitoParseDate} from '../../date/utils';\nimport {maskitoParseTime} from '../../time';\nimport {DATE_TIME_SEPARATOR} from '../constants';\nimport type {MaskitoDateTimeParams} from '../date-time-params';\n\nexport function maskitoParseDateTime(\n    value: string,\n    {\n        dateMode,\n        timeMode,\n        min = DEFAULT_MIN_DATE,\n        max = DEFAULT_MAX_DATE,\n        dateTimeSeparator = DATE_TIME_SEPARATOR,\n    }: MaskitoDateTimeParams,\n): Date | null {\n    const [dateSegment = '', timeSegment = ''] = value.split(dateTimeSeparator);\n    const digitsPattern = timeMode.replaceAll(/[^HMS]/g, '');\n    const digits = timeSegment.replaceAll(/\\D+/g, '');\n\n    if (digits.length !== digitsPattern.length) {\n        return null;\n    }\n\n    const date = maskitoParseDate(dateSegment, {mode: dateMode});\n    const time = maskitoParseTime(timeSegment, {mode: timeMode});\n\n    if (!date) {\n        return null;\n    }\n\n    const dateTime = new Date(Number(date) + time);\n\n    return dateMode.includes('y') ? clamp(dateTime, min, max) : dateTime;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/utils/split-date-time-string.ts",
    "content": "const NON_DIGIT_PLACEHOLDER_RE = /[^dmy]/g;\nconst LEADING_NON_DIGIT_RE = /^\\D*/;\n\nexport function splitDateTimeString(\n    dateTime: string,\n    dateModeTemplate: string,\n): [date: string, time: string] {\n    const dateDigitsCount = dateModeTemplate.replaceAll(\n        NON_DIGIT_PLACEHOLDER_RE,\n        '',\n    ).length;\n    const [date = ''] =\n        new RegExp(String.raw`(\\d[^\\d]*){0,${dateDigitsCount - 1}}\\d?`).exec(dateTime) ||\n        [];\n    const [dateTimeSeparator = ''] =\n        LEADING_NON_DIGIT_RE.exec(dateTime.slice(date.length)) || [];\n\n    return [date, dateTime.slice(date.length + dateTimeSeparator.length)];\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/utils/stringify-date-time.ts",
    "content": "import {DEFAULT_MAX_DATE, DEFAULT_MIN_DATE} from '../../../constants';\nimport {clamp} from '../../../utils';\nimport {maskitoStringifyDate} from '../../date/utils';\nimport {maskitoStringifyTime} from '../../time';\nimport {DATE_TIME_SEPARATOR} from '../constants';\nimport type {MaskitoDateTimeParams} from '../date-time-params';\n\nexport function maskitoStringifyDateTime(\n    date: Date,\n    {\n        dateMode,\n        timeMode,\n        dateTimeSeparator = DATE_TIME_SEPARATOR,\n        dateSeparator = '.',\n        min = DEFAULT_MIN_DATE,\n        max = DEFAULT_MAX_DATE,\n    }: MaskitoDateTimeParams,\n): string {\n    const validatedDate = clamp(date, min, max);\n\n    const dateString = maskitoStringifyDate(validatedDate, {\n        mode: dateMode,\n        separator: dateSeparator,\n        min,\n        max,\n    });\n\n    const extractedTime =\n        Number(validatedDate) -\n        Number(\n            new Date(\n                validatedDate.getFullYear(),\n                validatedDate.getMonth(),\n                validatedDate.getDate(),\n            ),\n        );\n    const timeString = maskitoStringifyTime(extractedTime, {mode: timeMode});\n\n    return `${dateString}${dateTimeSeparator}${timeString}`;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/utils/tests/parse-date-time.spec.ts",
    "content": "import {DEFAULT_MAX_DATE, DEFAULT_MIN_DATE} from '../../../../constants';\nimport type {MaskitoDateTimeParams} from '../../date-time-params';\nimport {maskitoParseDateTime} from '../parse-date-time';\n\ndescribe('maskitoParseDateTime', () => {\n    const dateMode = 'dd/mm/yyyy';\n    const timeMode = 'HH:MM';\n    const dateTimeSeparator = ', ';\n\n    it('returns null for incomplete date-time string', () => {\n        expect(\n            maskitoParseDateTime('02/11/2018', {dateMode, timeMode, dateTimeSeparator}),\n        ).toBeNull();\n        expect(\n            maskitoParseDateTime('16:20', {dateMode, timeMode, dateTimeSeparator}),\n        ).toBeNull();\n    });\n\n    it('parses valid date-time string', () => {\n        expect(\n            maskitoParseDateTime('02/11/2018, 16:20', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator,\n            }),\n        ).toEqual(new Date(2018, 10, 2, 16, 20));\n    });\n\n    it('clamps date-time to min and max bounds', () => {\n        const min = new Date(2020, 0, 1, 10, 1);\n        const max = new Date(2025, 11, 31, 11, 1);\n\n        expect(\n            maskitoParseDateTime('01/01/2019, 10:00', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator,\n                min,\n                max,\n            }),\n        ).toEqual(min);\n\n        expect(\n            maskitoParseDateTime('01/01/2030, 10:00', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator,\n                min,\n                max,\n            }),\n        ).toEqual(max);\n    });\n\n    it('handles default min and max bounds', () => {\n        expect(\n            maskitoParseDateTime('01/01/0001, 00:00', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator,\n            }),\n        ).toEqual(DEFAULT_MIN_DATE);\n\n        expect(\n            maskitoParseDateTime('31/12/9999, 23:59:59.999', {\n                dateMode,\n                timeMode: 'HH:MM:SS.MSS',\n                dateTimeSeparator,\n            }),\n        ).toEqual(DEFAULT_MAX_DATE);\n    });\n\n    it('parses date-time with custom separator', () => {\n        const customSeparator = 'T';\n\n        expect(\n            maskitoParseDateTime('02/11/2018T16:20', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator: customSeparator,\n            }),\n        ).toEqual(new Date(2018, 10, 2, 16, 20));\n    });\n\n    it('returns null for missing date-time separator', () => {\n        expect(\n            maskitoParseDateTime('02/11/201816:20', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator,\n            }),\n        ).toBeNull();\n    });\n\n    it('handles edge cases for leap years', () => {\n        expect(\n            maskitoParseDateTime('29/02/2020, 12:00', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator,\n            }),\n        ).toEqual(new Date(2020, 1, 29, 12, 0));\n        expect(\n            maskitoParseDateTime('29/02/2019, 12:00', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator,\n            }),\n        ).toEqual(new Date(2019, 2, 1, 12, 0));\n    });\n\n    it('handles edge cases for time boundaries', () => {\n        expect(\n            maskitoParseDateTime('31/12/2018, 00:00', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator,\n            }),\n        ).toEqual(new Date(2018, 11, 31, 0, 0));\n        expect(\n            maskitoParseDateTime('31/12/2018, 23:59', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator,\n            }),\n        ).toEqual(new Date(2018, 11, 31, 23, 59));\n    });\n\n    it('returns null for empty or whitespace-only input', () => {\n        expect(\n            maskitoParseDateTime('', {dateMode, timeMode, dateTimeSeparator}),\n        ).toBeNull();\n        expect(\n            maskitoParseDateTime('   ', {dateMode, timeMode, dateTimeSeparator}),\n        ).toBeNull();\n    });\n\n    it('parses date-time with dd/mm date mode without year', () => {\n        expect(\n            maskitoParseDateTime('25/12, 16:20', {\n                dateMode: 'dd/mm',\n                timeMode,\n                dateTimeSeparator,\n            })?.getTime(),\n        ).toBe(Date.parse('0000-12-25T16:20:00.000'));\n    });\n\n    it('parses date-time with mm/dd date mode without year', () => {\n        expect(\n            maskitoParseDateTime('12/25, 16:20', {\n                dateMode: 'mm/dd',\n                timeMode,\n                dateTimeSeparator,\n            })?.getTime(),\n        ).toBe(Date.parse('0000-12-25T16:20:00.000'));\n    });\n\n    it('handles invalid date-time separator', () => {\n        expect(\n            maskitoParseDateTime('31/12/2018-16:20', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator,\n            }),\n        ).toBeNull();\n        expect(\n            maskitoParseDateTime('31/12/2018 16:20', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator,\n            }),\n        ).toBeNull();\n    });\n\n    it('handles mixed valid and invalid inputs', () => {\n        expect(\n            maskitoParseDateTime('31/12/2018, invalid', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator,\n            }),\n        ).toBeNull();\n        expect(\n            maskitoParseDateTime('invalid, 16:20', {\n                dateMode,\n                timeMode,\n                dateTimeSeparator,\n            }),\n        ).toBeNull();\n    });\n\n    describe('invalid date-time strings', () => {\n        const params: MaskitoDateTimeParams = {dateMode, timeMode, dateTimeSeparator};\n\n        it.each([\n            'this-is-not-a-datetime',\n            '',\n            '   ',\n            '02//2018, 16:20',\n            '/11/2018, 16:20',\n            '02/11/, 16:20',\n            '02/11/20, 16:20',\n            '02/11/abcd, 16:20',\n            '1a/11/2018, 16:20',\n        ])('should return null for invalid date part \"%s\"', (value) =>\n            expect(maskitoParseDateTime(value, params)).toBeNull(),\n        );\n\n        it.each(['02/11/2018, 16:ab', '02/11/2018, aa:20'])(\n            'should return null for invalid time part \"%s\"',\n            (value) => expect(maskitoParseDateTime(value, params)).toBeNull(),\n        );\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/utils/tests/split-date-time-string.spec.ts",
    "content": "import {splitDateTimeString} from '../split-date-time-string';\n\ndescribe('splitDateTimeString', () => {\n    describe('dd.mm.yyyy', () => {\n        const split = (value: string): [string, string] =>\n            splitDateTimeString(value, 'dd.mm.yyyy');\n\n        (\n            [\n                {input: '', output: ['', '']},\n                {input: '02', output: ['02', '']},\n                {input: '02.', output: ['02.', '']},\n                {input: '0211', output: ['0211', '']},\n                {input: '0211.', output: ['0211.', '']},\n                {input: '02.112018', output: ['02.112018', '']},\n                {input: '02.112018,', output: ['02.112018', '']},\n                {input: '02.112018, ', output: ['02.112018', '']},\n                {input: '02.11.2018, ', output: ['02.11.2018', '']},\n                {input: '021120181620', output: ['02112018', '1620']},\n                {input: '02112018,1620', output: ['02112018', '1620']},\n                {input: '02112018, 1620', output: ['02112018', '1620']},\n                {input: '02112018, 16:20', output: ['02112018', '16:20']},\n                {input: '02112018,16:20', output: ['02112018', '16:20']},\n                {input: '02.11.2018,1620', output: ['02.11.2018', '1620']},\n                {input: '02.11.2018, 1620', output: ['02.11.2018', '1620']},\n                {input: '02.11.2018, 16:20', output: ['02.11.2018', '16:20']},\n                {input: '02.11.2018,16:20', output: ['02.11.2018', '16:20']},\n            ] as const\n        ).forEach(({input, output}) => {\n            it(`${input} -> ${JSON.stringify(output)}`, () => {\n                expect(split(input)).toEqual(output);\n            });\n        });\n    });\n\n    describe('dd. mm. yyyy (date segment separator consists of space and dot)', () => {\n        const parse = (value: string): [string, string] =>\n            splitDateTimeString(value, 'dd. mm. yyyy');\n\n        (\n            [\n                {input: '', output: ['', '']},\n                {input: '02', output: ['02', '']},\n                {input: '02.', output: ['02.', '']},\n                {input: '02. ', output: ['02. ', '']},\n                {input: '0211', output: ['0211', '']},\n                {input: '0211. ', output: ['0211. ', '']},\n                {input: '02. 112018', output: ['02. 112018', '']},\n                {input: '02. 112018,', output: ['02. 112018', '']},\n                {input: '02. 112018, ', output: ['02. 112018', '']},\n                {input: '02. 11. 2018, ', output: ['02. 11. 2018', '']},\n                {input: '02. 11. 2018,1620', output: ['02. 11. 2018', '1620']},\n                {input: '02. 11. 2018, 1620', output: ['02. 11. 2018', '1620']},\n                {input: '02. 11. 2018, 16:20', output: ['02. 11. 2018', '16:20']},\n                {input: '02. 11. 2018,16:20', output: ['02. 11. 2018', '16:20']},\n            ] as const\n        ).forEach(({input, output}) => {\n            it(`${input} -> ${JSON.stringify(output)}`, () => {\n                expect(parse(input)).toEqual(output);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/date-time/utils/tests/stringify-date-time.spec.ts",
    "content": "import {maskitoStringifyDateTime} from '../stringify-date-time';\n\ndescribe('maskitoStringifyDateTime', () => {\n    const date = new Date(2025, 3, 11, 15, 30, 45);\n    const dateMode = 'dd/mm/yyyy';\n    const timeMode = 'HH:MM:SS';\n    const dateSeparator = '/';\n    const dateTimeSeparator = ', ';\n\n    it('should stringify date and time with default separator', () => {\n        const result = maskitoStringifyDateTime(date, {\n            dateMode,\n            timeMode,\n            dateSeparator,\n            dateTimeSeparator,\n        });\n\n        expect(result).toBe('11/04/2025, 15:30:45');\n    });\n\n    it('should stringify date and time with custom separator', () => {\n        const result = maskitoStringifyDateTime(date, {\n            dateMode,\n            timeMode,\n            dateSeparator,\n            dateTimeSeparator: ' | ',\n        });\n\n        expect(result).toBe('11/04/2025 | 15:30:45');\n    });\n\n    it('should clamp date to min boundary', () => {\n        const minDate = new Date('2025-04-12T00:00:00.000');\n        const result = maskitoStringifyDateTime(date, {\n            dateMode,\n            timeMode,\n            dateSeparator,\n            min: minDate,\n        });\n\n        expect(result).toBe('12/04/2025, 00:00:00');\n    });\n\n    it('should clamp date to max boundary', () => {\n        const maxDate = new Date('2025-04-10T23:59:59.999');\n        const result = maskitoStringifyDateTime(date, {\n            dateMode,\n            timeMode,\n            dateSeparator,\n            max: maxDate,\n        });\n\n        expect(result).toBe('10/04/2025, 23:59:59');\n    });\n\n    it('should handle edge cases for leap years', () => {\n        const leapYearDate = new Date('2024-02-29T12:00:00.000');\n        const result = maskitoStringifyDateTime(leapYearDate, {\n            dateMode,\n            timeMode,\n            dateSeparator,\n        });\n\n        expect(result).toBe('29/02/2024, 12:00:00');\n    });\n\n    it('should handle edge cases for time boundaries', () => {\n        const midnight = new Date('2025-04-11T00:00:00.000');\n        const resultMidnight = maskitoStringifyDateTime(midnight, {\n            dateMode,\n            timeMode,\n            dateSeparator,\n        });\n\n        expect(resultMidnight).toBe('11/04/2025, 00:00:00');\n\n        const endOfDay = new Date('2025-04-11T23:59:59.999');\n        const resultEndOfDay = maskitoStringifyDateTime(endOfDay, {\n            dateMode,\n            timeMode,\n            dateSeparator,\n        });\n\n        expect(resultEndOfDay).toBe('11/04/2025, 23:59:59');\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/index.ts",
    "content": "export * from './number-mask';\nexport * from './number-params';\nexport * from './plugins';\nexport * from './processors';\nexport * from './utils';\n/*\nDon't put it inside `./utils` entrypoint to avoid circular dependency\n- `stringify-number.ts` uses `maskitoNumberOptionsGenerator`\n- almost all processors and plugins (which are part of `maskitoNumberOptionsGenerator`) uses `./utils`\n */\nexport * from './utils/stringify-number';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/number-mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nimport {\n    createFullWidthToHalfWidthPreprocessor,\n    maskitoPostfixPostprocessorGenerator,\n} from '../../processors';\nimport {type MaskitoNumberParams} from './number-params';\nimport {\n    createLeadingZeroesValidationPlugin,\n    createMinMaxPlugin,\n    createNotEmptyIntegerPlugin,\n} from './plugins';\nimport {\n    createAffixesFilterPreprocessor,\n    createDecimalZeroPaddingPostprocessor,\n    createInitializationOnlyPreprocessor,\n    createLeadingMinusDeletionPreprocessor,\n    createMinMaxPostprocessor,\n    createNonRemovableCharsDeletionPreprocessor,\n    createNotEmptyIntegerPartPreprocessor,\n    createNumberPrefixPostprocessor,\n    createPseudoCharactersPreprocessor,\n    createRepeatedDecimalSeparatorPreprocessor,\n    createThousandSeparatorPostprocessor,\n    createZeroPrecisionPreprocessor,\n    emptyPostprocessor,\n} from './processors';\nimport {generateMaskExpression, withNumberDefaults} from './utils';\n\nexport function maskitoNumberOptionsGenerator(\n    optionalParams?: MaskitoNumberParams,\n): Required<MaskitoOptions> {\n    const params = withNumberDefaults(optionalParams);\n\n    return {\n        mask: generateMaskExpression(params),\n        preprocessors: [\n            createFullWidthToHalfWidthPreprocessor(),\n            createInitializationOnlyPreprocessor(params),\n            createAffixesFilterPreprocessor(params),\n            createPseudoCharactersPreprocessor({\n                ...params,\n                validCharacter: params.minusSign,\n                pseudoCharacters: params.minusPseudoSigns,\n            }),\n            createPseudoCharactersPreprocessor({\n                ...params,\n                validCharacter: params.decimalSeparator,\n                pseudoCharacters: params.decimalPseudoSeparators,\n            }),\n            createNotEmptyIntegerPartPreprocessor(params),\n            createNonRemovableCharsDeletionPreprocessor(params),\n            createZeroPrecisionPreprocessor(params),\n            createRepeatedDecimalSeparatorPreprocessor(params),\n            createLeadingMinusDeletionPreprocessor(params),\n        ],\n        postprocessors: [\n            createMinMaxPostprocessor(params),\n            createNumberPrefixPostprocessor(params),\n            maskitoPostfixPostprocessorGenerator(params.postfix),\n            createThousandSeparatorPostprocessor(params),\n            createDecimalZeroPaddingPostprocessor(params),\n            emptyPostprocessor(params),\n        ],\n        plugins: [\n            createLeadingZeroesValidationPlugin(params),\n            createNotEmptyIntegerPlugin(params),\n            createMinMaxPlugin(params),\n        ],\n        overwriteMode:\n            params.minimumFractionDigits > 0\n                ? ({value, selection: [from]}) =>\n                      from <= value.indexOf(params.decimalSeparator) ? 'shift' : 'replace'\n                : 'shift',\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/number-params.ts",
    "content": "export interface MaskitoNumberParams extends Pick<\n    Intl.NumberFormatOptions,\n    'maximumFractionDigits' | 'minimumFractionDigits'\n> {\n    min?: bigint | number;\n    max?: bigint | number;\n    decimalSeparator?: string;\n    decimalPseudoSeparators?: readonly string[];\n    thousandSeparator?: string;\n    thousandSeparatorPattern?: (digits: string) => readonly string[];\n    prefix?: string;\n    postfix?: string;\n    minusSign?: string;\n    minusPseudoSigns?: readonly string[];\n    negativePattern?: 'minusFirst' | 'prefixFirst';\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/plugins/index.ts",
    "content": "export * from './leading-zeroes-validation.plugin';\nexport * from './min-max.plugin';\nexport * from './not-empty-integer.plugin';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/plugins/leading-zeroes-validation.plugin.ts",
    "content": "import {type MaskitoPlugin, maskitoUpdateElement} from '@maskito/core';\n\nimport {maskitoEventHandler} from '../../../plugins';\nimport type {MaskitoNumberParams} from '../number-params';\nimport {createLeadingZeroesValidationPostprocessor} from '../processors';\n\nconst DUMMY_SELECTION = [0, 0] as const;\n\n/**\n * It removes repeated leading zeroes for integer part on blur-event.\n * @example 000000 => blur => 0\n * @example 00005 => blur => 5\n */\nexport function createLeadingZeroesValidationPlugin(\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'negativePattern'\n        | 'postfix'\n        | 'prefix'\n        | 'thousandSeparator'\n    >,\n): MaskitoPlugin {\n    const dropRepeatedLeadingZeroes = createLeadingZeroesValidationPostprocessor(params);\n\n    return maskitoEventHandler(\n        'blur',\n        (element) => {\n            const newValue = dropRepeatedLeadingZeroes(\n                {\n                    value: element.value,\n                    selection: DUMMY_SELECTION,\n                },\n                {value: '', selection: DUMMY_SELECTION},\n            ).value;\n\n            maskitoUpdateElement(element, newValue);\n        },\n        {capture: true},\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/plugins/min-max.plugin.ts",
    "content": "import {type MaskitoPlugin, maskitoTransform, maskitoUpdateElement} from '@maskito/core';\n\nimport {maskitoEventHandler} from '../../../plugins';\nimport {clamp} from '../../../utils';\nimport type {MaskitoNumberParams} from '../number-params';\nimport {maskitoParseNumber, stringifyNumberWithoutExp} from '../utils';\n\n/**\n * This plugin is connected with {@link createMinMaxPostprocessor}:\n * both validate `min`/`max` bounds of entered value (but at the different point of time).\n */\nexport function createMinMaxPlugin(params: Required<MaskitoNumberParams>): MaskitoPlugin {\n    const {decimalSeparator, maximumFractionDigits, min, max} = params;\n\n    return maskitoEventHandler(\n        'blur',\n        (element, options) => {\n            const parsedNumber =\n                maskitoParseNumber(element.value, {\n                    ...params,\n                    bigint:\n                        !maximumFractionDigits &&\n                        !element.value.includes(decimalSeparator),\n                }) ?? Number.NaN;\n            const clampedNumber = clamp(parsedNumber, min, max);\n\n            if (!Number.isNaN(parsedNumber) && parsedNumber !== clampedNumber) {\n                maskitoUpdateElement(\n                    element,\n                    maskitoTransform(stringifyNumberWithoutExp(clampedNumber), options),\n                );\n            }\n        },\n        {capture: true},\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/plugins/not-empty-integer.plugin.ts",
    "content": "import {type MaskitoPlugin, maskitoUpdateElement} from '@maskito/core';\n\nimport {maskitoEventHandler} from '../../../plugins';\nimport {escapeRegExp, noop} from '../../../utils';\nimport type {MaskitoNumberParams} from '../number-params';\nimport {fromNumberParts, toNumberParts} from '../utils';\n\n/**\n * It pads EMPTY integer part with zero if decimal parts exists.\n * It works on blur event only!\n * @example 1|,23 => Backspace => Blur => 0,23\n */\nexport function createNotEmptyIntegerPlugin(\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'negativePattern'\n        | 'postfix'\n        | 'prefix'\n    >,\n): MaskitoPlugin {\n    const {decimalSeparator} = params;\n\n    if (!decimalSeparator) {\n        return noop;\n    }\n\n    return maskitoEventHandler(\n        'blur',\n        (element) => {\n            const {prefix, postfix, ...numberParts} = toNumberParts(\n                element.value,\n                params,\n            );\n            const onlyNumber = fromNumberParts(numberParts, params).replace(\n                new RegExp(String.raw`^(\\D+)?${escapeRegExp(decimalSeparator)}`),\n                `$10${decimalSeparator}`,\n            );\n            const newValue = fromNumberParts(\n                {\n                    ...toNumberParts(onlyNumber, params),\n                    prefix,\n                    postfix,\n                },\n                params,\n            );\n\n            maskitoUpdateElement(element, newValue);\n        },\n        {capture: true},\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/affixes-filter-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport type {MaskitoNumberParams} from '../number-params';\nimport {fromNumberParts, toNumberParts} from '../utils';\n\n/**\n * It drops prefix and postfix from data\n * Needed for case, when prefix or postfix contain decimalSeparator, to ignore it in resulting number\n * @example User pastes '{prefix}123.45{postfix}' => 123.45\n */\nexport function createAffixesFilterPreprocessor(\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'negativePattern'\n        | 'postfix'\n        | 'prefix'\n    >,\n): MaskitoPreprocessor {\n    return ({elementState, data}) => {\n        const {value} = elementState;\n        const {prefix, postfix, ...numberParts} = toNumberParts(data, params);\n\n        return {\n            elementState,\n            data: fromNumberParts(\n                {\n                    ...numberParts,\n                    prefix: value.startsWith(prefix) ? '' : prefix,\n                    postfix: value.endsWith(postfix) ? '' : postfix,\n                },\n                params,\n            ),\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/decimal-zero-padding-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\n\nimport {identity} from '../../../utils';\nimport type {MaskitoNumberParams} from '../number-params';\nimport {fromNumberParts, maskitoParseNumber, toNumberParts} from '../utils';\n\n/**\n * If `minimumFractionDigits` is `>0`, it pads decimal part with zeroes\n * (until number of digits after decimalSeparator is equal to the `minimumFractionDigits`).\n * @example 1,42 => (`minimumFractionDigits` is equal to 4) => 1,4200.\n */\nexport function createDecimalZeroPaddingPostprocessor(\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minimumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'negativePattern'\n        | 'postfix'\n        | 'prefix'\n    >,\n): MaskitoPostprocessor {\n    const {minimumFractionDigits} = params;\n\n    if (!minimumFractionDigits) {\n        return identity;\n    }\n\n    return ({value, selection}) => {\n        if (Number.isNaN(maskitoParseNumber(value, params))) {\n            return {value, selection};\n        }\n\n        const {decimalPart, ...numberParts} = toNumberParts(value, params);\n\n        return {\n            value: fromNumberParts(\n                {\n                    ...numberParts,\n                    decimalPart: decimalPart.padEnd(minimumFractionDigits, '0'),\n                },\n                params,\n            ),\n            selection,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/empty-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\n\nimport type {MaskitoNumberParams} from '../number-params';\nimport {fromNumberParts, toNumberParts} from '../utils';\n\n/**\n * Make textfield empty if there is no integer part and all decimal digits are zeroes.\n * @example 0|,00 => Backspace => Empty.\n * @example -0|,00 => Backspace => -.\n * @example ,42| => Backspace x2 => ,|00 => Backspace => Empty\n */\nexport function emptyPostprocessor(\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'negativePattern'\n        | 'postfix'\n        | 'prefix'\n    >,\n): MaskitoPostprocessor {\n    return ({value, selection}) => {\n        const [caretIndex] = selection;\n        const {prefix, minus, integerPart, decimalSeparator, decimalPart, postfix} =\n            toNumberParts(value, params);\n        const aloneDecimalSeparator = !integerPart && !decimalPart && decimalSeparator;\n\n        if (\n            (!integerPart &&\n                !Number(decimalPart) &&\n                caretIndex === `${minus}${prefix}`.length) ||\n            aloneDecimalSeparator\n        ) {\n            return {\n                selection,\n                value: fromNumberParts({prefix, minus, postfix}, params),\n            };\n        }\n\n        return {value, selection};\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/index.ts",
    "content": "export * from './affixes-filter-preprocessor';\nexport * from './decimal-zero-padding-postprocessor';\nexport * from './empty-postprocessor';\nexport * from './initialization-only-preprocessor';\nexport * from './leading-minus-deletion-preprocessor';\nexport * from './leading-zeroes-validation-postprocessor';\nexport * from './min-max-postprocessor';\nexport * from './non-removable-chars-deletion-preprocessor';\nexport * from './not-empty-integer-part-preprocessor';\nexport * from './number-prefix-postprocessor';\nexport * from './pseudo-character-preprocessor';\nexport * from './repeated-decimal-separator-preprocessor';\nexport * from './thousand-separator-postprocessor';\nexport * from './zero-precision-preprocessor';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/initialization-only-preprocessor.ts",
    "content": "import {type MaskitoPreprocessor, maskitoTransform} from '@maskito/core';\n\nimport type {MaskitoNumberParams} from '../number-params';\nimport {fromNumberParts, generateMaskExpression, toNumberParts} from '../utils';\n\n/**\n * This preprocessor works only once at initialization phase (when `new Maskito(...)` is executed).\n * This preprocessor helps to avoid conflicts during transition from one mask to another (for the same input).\n * For example, the developer changes postfix (or other mask's props) during run-time.\n * ```\n * let maskitoOptions = maskitoNumberOptionsGenerator({postfix: ' year'});\n * // [3 seconds later]\n * maskitoOptions = maskitoNumberOptionsGenerator({postfix: ' years'});\n * ```\n */\nexport function createInitializationOnlyPreprocessor(\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'negativePattern'\n        | 'postfix'\n        | 'prefix'\n    >,\n): MaskitoPreprocessor {\n    let isInitializationPhase = true;\n    const cleanNumberMask = generateMaskExpression({\n        ...params,\n        prefix: '',\n        postfix: '',\n        thousandSeparator: '',\n        maximumFractionDigits: Infinity,\n        min: -Infinity,\n    });\n\n    return ({elementState, data}) => {\n        if (!isInitializationPhase) {\n            return {elementState, data};\n        }\n\n        isInitializationPhase = false;\n\n        const {value, selection} = elementState;\n        const [from, to] = selection;\n        const {prefix, postfix, ...numberParts} = toNumberParts(value, params);\n        const onlyNumber = fromNumberParts(numberParts, params);\n        const cleanState = maskitoTransform(\n            {\n                selection: [\n                    Math.max(from - prefix.length, 0),\n                    Math.max(to - prefix.length, 0),\n                ],\n                value: onlyNumber,\n            },\n            {mask: cleanNumberMask},\n        );\n\n        return {\n            elementState: {\n                selection: selection.map((position, i) => {\n                    const deleted =\n                        onlyNumber.slice(0, Math.max(position - prefix.length, 0))\n                            .length -\n                        cleanState.value.slice(0, cleanState.selection[i]).length;\n\n                    return Math.max(position - deleted, 0);\n                }) as [number, number],\n                value: fromNumberParts(\n                    {\n                        ...toNumberParts(cleanState.value, params),\n                        prefix: prefix && params.prefix,\n                        postfix: postfix && params.postfix,\n                    },\n                    params,\n                ),\n            },\n            data,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/leading-minus-deletion-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport type {MaskitoNumberParams} from '../number-params';\nimport {fromNumberParts, toNumberParts} from '../utils';\n\n/**\n * If minus sign is positioned before prefix,\n * any attempt to erase prefix deletes minus (without deletion of non-removable prefix)\n * @example -$|42 => Backspace => $|42\n */\nexport function createLeadingMinusDeletionPreprocessor(\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'negativePattern'\n        | 'postfix'\n        | 'prefix'\n    >,\n): MaskitoPreprocessor {\n    return ({elementState}, inputType) => {\n        const {value, selection} = elementState;\n        const [from, to] = selection;\n        const {prefix, minusSign, negativePattern} = params;\n        const beginning =\n            negativePattern === 'prefixFirst' ? prefix : `${minusSign}${prefix}`;\n        const newValue = fromNumberParts(\n            {...toNumberParts(value, params), minus: ''},\n            params,\n        );\n        const diff = value.length - newValue.length;\n\n        return {\n            elementState:\n                inputType.includes('delete') &&\n                value.includes(minusSign) &&\n                from < beginning.length\n                    ? {\n                          value: newValue,\n                          selection: [\n                              Math.max(from - diff, beginning.length - 1),\n                              Math.max(to - diff, beginning.length - 1),\n                          ],\n                      }\n                    : elementState,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/leading-zeroes-validation-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\n\nimport {escapeRegExp} from '../../../utils';\nimport type {MaskitoNumberParams} from '../number-params';\nimport {fromNumberParts, toNumberParts} from '../utils';\n\n/**\n * It removes repeated leading zeroes for integer part.\n * @example 0,|00005 => Backspace => |5\n * @example -0,|00005 => Backspace => -|5\n * @example User types \"000000\" => 0|\n * @example 0| => User types \"5\" => 5|\n */\nexport function createLeadingZeroesValidationPostprocessor(\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'negativePattern'\n        | 'postfix'\n        | 'prefix'\n        | 'thousandSeparator'\n    >,\n): MaskitoPostprocessor {\n    const {thousandSeparator} = params;\n    const trimLeadingZeroes = (value: string): string => {\n        const escapedThousandSeparator = escapeRegExp(thousandSeparator);\n\n        return value\n            .replace(\n                // all leading zeroes followed by another zero\n                new RegExp(String.raw`^(\\D+)?[0${escapedThousandSeparator}]+(?=0)`),\n                '$1',\n            )\n            .replace(\n                // zero followed by not-zero digit\n                new RegExp(String.raw`^(\\D+)?[0${escapedThousandSeparator}]+(?=[1-9])`),\n                '$1',\n            );\n    };\n\n    const countTrimmedZeroesBefore = (value: string, index: number): number => {\n        const valueBefore = value.slice(0, index);\n        const followedByZero = value.slice(index).startsWith('0');\n\n        return (\n            valueBefore.length -\n            trimLeadingZeroes(valueBefore).length +\n            (followedByZero ? 1 : 0)\n        );\n    };\n\n    return ({value, selection}) => {\n        const [from, to] = selection;\n        const {integerPart, ...numberParts} = toNumberParts(value, params);\n        const zeroTrimmedIntegerPart = trimLeadingZeroes(integerPart);\n\n        if (integerPart === zeroTrimmedIntegerPart) {\n            return {value, selection};\n        }\n\n        const newFrom = from - countTrimmedZeroesBefore(value, from);\n        const newTo = to - countTrimmedZeroesBefore(value, to);\n\n        return {\n            value: fromNumberParts(\n                {...numberParts, integerPart: zeroTrimmedIntegerPart},\n                params,\n            ),\n            selection: [Math.max(newFrom, 0), Math.max(newTo, 0)],\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/min-max-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\n\nimport {CHAR_HYPHEN} from '../../../constants';\nimport {clamp} from '../../../utils';\nimport type {MaskitoNumberParams} from '../number-params';\nimport {maskitoParseNumber} from '../utils';\n\n/**\n * This postprocessor is connected with {@link createMinMaxPlugin}:\n * both validate `min`/`max` bounds of entered value (but at the different point of time).\n */\nexport function createMinMaxPostprocessor(\n    params: Required<MaskitoNumberParams>,\n): MaskitoPostprocessor {\n    const {decimalSeparator, maximumFractionDigits, min, max, minusSign} = params;\n\n    return ({value, selection}) => {\n        const parsedNumber =\n            maskitoParseNumber(value, {\n                ...params,\n                bigint: !maximumFractionDigits && !value.includes(decimalSeparator),\n            }) ?? Number.NaN;\n        const limitedValue =\n            /**\n             * We cannot limit lower bound if user enters positive number.\n             * The same for upper bound and negative number.\n             * ___\n             * @example (min = 5)\n             * Empty input => Without this condition user cannot type 42 (the first digit will be rejected)\n             * ___\n             * @example (max = -10)\n             * Value is -10 => Without this condition user cannot delete 0 to enter another digit\n             */\n            parsedNumber > 0 ? clamp(parsedNumber, null, max) : clamp(parsedNumber, min);\n\n        if (parsedNumber && limitedValue !== parsedNumber) {\n            const newValue = `${limitedValue}`\n                .replace('.', decimalSeparator)\n                .replace(CHAR_HYPHEN, minusSign);\n\n            return {\n                value: newValue,\n                selection: [newValue.length, newValue.length],\n            };\n        }\n\n        return {\n            value,\n            selection,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/non-removable-chars-deletion-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport type {MaskitoNumberParams} from '../number-params';\n\n/**\n * Manage caret-navigation when user \"deletes\" non-removable digits or separators\n * @example 1,|42 => Backspace => 1|,42 (only if `minimumFractionDigits` is `>0`)\n * @example 1|,42 => Delete => 1,|42 (only if `minimumFractionDigits` is `>0`)\n * @example 0,|00 => Delete => 0,0|0 (only if `minimumFractionDigits` is `>0`)\n * @example 1 |000 => Backspace => 1| 000 (always)\n */\nexport function createNonRemovableCharsDeletionPreprocessor({\n    decimalSeparator,\n    thousandSeparator,\n    minimumFractionDigits,\n}: Pick<\n    Required<MaskitoNumberParams>,\n    'decimalSeparator' | 'minimumFractionDigits' | 'thousandSeparator'\n>): MaskitoPreprocessor {\n    return ({elementState, data}, actionType) => {\n        const {value, selection} = elementState;\n        const [from, to] = selection;\n        const selectedCharacters = value.slice(from, to);\n        const nonRemovableSeparators = minimumFractionDigits\n            ? [decimalSeparator, thousandSeparator]\n            : [thousandSeparator];\n        const areNonRemovableZeroesSelected =\n            Boolean(minimumFractionDigits) &&\n            from > value.indexOf(decimalSeparator) &&\n            Boolean(selectedCharacters.match(/^0+$/g));\n\n        if (\n            (actionType !== 'deleteBackward' && actionType !== 'deleteForward') ||\n            (!nonRemovableSeparators.includes(selectedCharacters) &&\n                !areNonRemovableZeroesSelected)\n        ) {\n            return {\n                elementState,\n                data,\n            };\n        }\n\n        return {\n            elementState: {\n                value,\n                selection: actionType === 'deleteForward' ? [to, to] : [from, from],\n            },\n            data,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/not-empty-integer-part-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport {clamp, escapeRegExp} from '../../../utils';\nimport type {MaskitoNumberParams} from '../number-params';\nimport {fromNumberParts, toNumberParts} from '../utils';\n\n/**\n * It pads integer part with zero if user types decimal separator (for empty input).\n * @example Empty input => User types \",\" (decimal separator) => 0,|\n */\nexport function createNotEmptyIntegerPartPreprocessor(\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'negativePattern'\n        | 'postfix'\n        | 'prefix'\n    >,\n): MaskitoPreprocessor {\n    const {maximumFractionDigits, decimalSeparator} = params;\n    const startWithDecimalSepRegExp = new RegExp(\n        String.raw`^\\D*${escapeRegExp(decimalSeparator)}`,\n    );\n\n    return ({elementState, data}) => {\n        const {value, selection} = elementState;\n        // eslint-disable-next-line @typescript-eslint/no-unused-vars\n        const {prefix, postfix, ...numberParts} = toNumberParts(value, params);\n        const onlyNumber = fromNumberParts(numberParts, params);\n        const [from, to] = selection;\n        const cleanFrom = clamp(from - prefix.length, 0, onlyNumber.length);\n        const cleanTo = clamp(to - prefix.length, 0, onlyNumber.length);\n\n        if (\n            maximumFractionDigits <= 0 ||\n            onlyNumber.slice(0, cleanFrom).includes(decimalSeparator) ||\n            onlyNumber.slice(cleanTo).includes(decimalSeparator) ||\n            !data.match(startWithDecimalSepRegExp)\n        ) {\n            return {elementState, data};\n        }\n\n        const digitsBeforeCursor = /\\d+/.exec(onlyNumber.slice(0, cleanFrom));\n\n        return {\n            elementState,\n            data: digitsBeforeCursor ? data : `0${data}`,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/number-prefix-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\n\nimport {maskitoPrefixPostprocessorGenerator} from '../../../processors';\nimport type {MaskitoNumberParams} from '../number-params';\n\nexport function createNumberPrefixPostprocessor({\n    prefix,\n    minusSign,\n    negativePattern,\n}: Pick<\n    Required<MaskitoNumberParams>,\n    'minusSign' | 'negativePattern' | 'prefix'\n>): MaskitoPostprocessor {\n    return ({value, selection}, initialElementState) =>\n        maskitoPrefixPostprocessorGenerator(\n            value.includes(minusSign) && negativePattern === 'minusFirst'\n                ? `${minusSign}${prefix}`\n                : prefix,\n        )(\n            {\n                value:\n                    negativePattern === 'minusFirst' &&\n                    value.startsWith(`${prefix}${minusSign}`) // $-100 => -$100\n                        ? value.replace(`${prefix}${minusSign}`, `${minusSign}${prefix}`)\n                        : value,\n                selection,\n            },\n            initialElementState,\n        );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/pseudo-character-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport type {MaskitoNumberParams} from '../number-params';\nimport {fromNumberParts, toNumberParts} from '../utils';\n\n/**\n * It replaces pseudo characters with valid one.\n * @example User types '.' (but separator is equal to comma) => dot is replaced with comma.\n * @example User types hyphen / en-dash / em-dash => it is replaced with minus.\n */\nexport function createPseudoCharactersPreprocessor({\n    validCharacter,\n    pseudoCharacters,\n    ...params\n}: Pick<\n    Required<MaskitoNumberParams>,\n    | 'decimalPseudoSeparators'\n    | 'decimalSeparator'\n    | 'maximumFractionDigits'\n    | 'minusPseudoSigns'\n    | 'minusSign'\n    | 'negativePattern'\n    | 'postfix'\n    | 'prefix'\n> & {\n    validCharacter: string;\n    pseudoCharacters: readonly string[];\n}): MaskitoPreprocessor {\n    const pseudoCharactersRegExp = new RegExp(`[${pseudoCharacters.join('')}]`, 'gi');\n\n    return ({elementState, data}) => {\n        const {value, selection} = elementState;\n        const {prefix, postfix, ...numberParts} = toNumberParts(value, params);\n        const onlyNumber = fromNumberParts(numberParts, params).replace(\n            pseudoCharactersRegExp,\n            validCharacter,\n        );\n\n        return {\n            elementState: {\n                selection,\n                value: fromNumberParts(\n                    {\n                        ...toNumberParts(onlyNumber, params),\n                        prefix,\n                        postfix,\n                    },\n                    params,\n                ),\n            },\n            data: data.replace(pseudoCharactersRegExp, validCharacter),\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/repeated-decimal-separator-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport {escapeRegExp, identity} from '../../../utils';\nimport type {MaskitoNumberParams} from '../number-params';\nimport {toNumberParts} from '../utils';\n\n/**\n * It rejects new typed decimal separator if it already exists in text field.\n * Behaviour is similar to native <input type=\"number\"> (Chrome).\n * @example 1|23,45 => Press comma (decimal separator) => 1|23,45 (do nothing).\n */\nexport function createRepeatedDecimalSeparatorPreprocessor(\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'postfix'\n        | 'prefix'\n    >,\n): MaskitoPreprocessor {\n    const {decimalSeparator} = params;\n\n    if (!decimalSeparator) {\n        return identity;\n    }\n\n    return ({elementState, data}) => {\n        const {value, selection} = elementState;\n        const [from, to] = selection;\n\n        return {\n            elementState,\n            data:\n                !toNumberParts(value, params).decimalSeparator ||\n                value.slice(from, to + 1).includes(decimalSeparator)\n                    ? data\n                    : data.replaceAll(\n                          new RegExp(escapeRegExp(decimalSeparator), 'gi'),\n                          '',\n                      ),\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/tests/leading-zeroes-validation-postprocessor.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\nimport type {MaskitoPostprocessor} from '@maskito/core';\nimport type {MaskitoNumberParams} from '@maskito/kit';\n\nimport {createLeadingZeroesValidationPostprocessor} from '../leading-zeroes-validation-postprocessor';\n\nconst DEFAULT_PARAMS = {\n    prefix: '',\n    postfix: '',\n    minusPseudoSigns: [],\n    decimalPseudoSeparators: [','],\n    negativePattern: 'prefixFirst',\n} as const satisfies MaskitoNumberParams;\n\ndescribe('createLeadingZeroesValidationPostprocessor', () => {\n    const DUMMY_INITIAL_STATE = {value: '', selection: [0, 0]} as const;\n    const params: Parameters<typeof createLeadingZeroesValidationPostprocessor>[0] = {\n        ...DEFAULT_PARAMS,\n        decimalSeparator: ',',\n        thousandSeparator: '',\n        minusSign: '−',\n        maximumFractionDigits: 0,\n    };\n    let processor: MaskitoPostprocessor;\n\n    const process = (\n        value: string,\n        selection: [number, number],\n    ): {selection: readonly [number, number]; value: string} =>\n        processor({value, selection}, DUMMY_INITIAL_STATE);\n\n    beforeEach(() => {\n        processor = createLeadingZeroesValidationPostprocessor(params);\n    });\n\n    it('0|0005 => |5', () => {\n        const {value, selection} = process('00005', [1, 1]);\n\n        expect(value).toBe('5');\n        expect(selection).toEqual([0, 0]);\n    });\n\n    it('−0|0005 => −|5', () => {\n        const {value, selection} = process('−00005', [2, 2]);\n\n        expect(value).toBe('−5');\n        expect(selection).toEqual([1, 1]);\n    });\n\n    it('0000,4|2 => 0,4|2', () => {\n        const {value, selection} = process('0000,42', ['0000,4'.length, '0000,4'.length]);\n\n        expect(value).toBe('0,42');\n        expect(selection).toEqual([3, 3]);\n    });\n\n    it('−0000,4|2 => -0.4|2', () => {\n        const {value, selection} = process('−0000,42', [\n            '−0000,4'.length,\n            '−0000,4'.length,\n        ]);\n\n        expect(value).toBe('−0,42');\n        expect(selection).toEqual([4, 4]);\n    });\n\n    it('00005|,42 => 5|,42', () => {\n        const {value, selection} = process('00005,42', ['00005'.length, '00005'.length]);\n\n        expect(value).toBe('5,42');\n        expect(selection).toEqual([1, 1]);\n    });\n\n    it('−0005,42| => -5,42|', () => {\n        const {value, selection} = process('−00005,42', [\n            '−00005,42'.length,\n            '−00005,42'.length,\n        ]);\n\n        expect(value).toBe('−5,42');\n        expect(selection).toEqual(['−5,42'.length, '−5,42'.length]);\n    });\n\n    it('empty string => empty string', () => {\n        const {value, selection} = process('', [0, 0]);\n\n        expect(value).toBe('');\n        expect(selection).toEqual([0, 0]);\n    });\n\n    it('− => -', () => {\n        const {value, selection} = process('−', [1, 1]);\n\n        expect(value).toBe('−');\n        expect(selection).toEqual([1, 1]);\n    });\n\n    describe('with prefix', () => {\n        it('$0000,4|2 => 0,4|2', () => {\n            processor = createLeadingZeroesValidationPostprocessor({\n                ...params,\n                prefix: '$',\n            });\n            const {value, selection} = process('$0000,42', [\n                '$0000,4'.length,\n                '$0000,4'.length,\n            ]);\n\n            expect(value).toBe('$0,42');\n            expect(selection).toEqual([4, 4]);\n        });\n\n        it('$ 0000,4|2 => 0,4|2', () => {\n            processor = createLeadingZeroesValidationPostprocessor({\n                ...params,\n                prefix: '$ ',\n            });\n            const {value, selection} = process('$ 0000,42', [\n                '$ 0000,4'.length,\n                '$ 0000,4'.length,\n            ]);\n\n            expect(value).toBe('$ 0,42');\n            expect(selection).toEqual([5, 5]);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/tests/not-empty-integer-part-preprocessor.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\nimport type {MaskitoNumberParams} from '@maskito/kit';\n\nimport {createNotEmptyIntegerPartPreprocessor} from '../not-empty-integer-part-preprocessor';\n\nconst EMPTY_ELEMENT_STATE = {\n    value: '',\n    selection: [0, 0] as const,\n};\n\nconst DEFAULT_PARAMS = {\n    prefix: '',\n    postfix: '',\n    minusPseudoSigns: [],\n    decimalPseudoSeparators: [','],\n    negativePattern: 'prefixFirst',\n} as const satisfies MaskitoNumberParams;\n\ndescribe('createNotEmptyIntegerPartPreprocessor', () => {\n    describe('maximumFractionDigits === 2', () => {\n        const preprocessor = createNotEmptyIntegerPartPreprocessor({\n            ...DEFAULT_PARAMS,\n            decimalSeparator: ',',\n            maximumFractionDigits: 2,\n            minusSign: '-',\n        });\n\n        it('should pad integer part with zero if user inserts \"a,\"', () => {\n            expect(\n                preprocessor(\n                    {\n                        elementState: EMPTY_ELEMENT_STATE,\n                        data: 'a,',\n                    },\n                    'insert',\n                ),\n            ).toEqual({\n                elementState: EMPTY_ELEMENT_STATE,\n                data: '0a,',\n            });\n        });\n\n        it('should NOT pad integer part with zero if user inserts \"aaa1aaa,\"', () => {\n            expect(\n                preprocessor(\n                    {\n                        elementState: EMPTY_ELEMENT_STATE,\n                        data: 'aaa1aaa,',\n                    },\n                    'insert',\n                ),\n            ).toEqual({\n                elementState: EMPTY_ELEMENT_STATE,\n                data: 'aaa1aaa,',\n            });\n        });\n\n        it('should pad integer part with zero if user inserts \",3123\"', () => {\n            expect(\n                preprocessor(\n                    {\n                        elementState: EMPTY_ELEMENT_STATE,\n                        data: ',3123',\n                    },\n                    'insert',\n                ),\n            ).toEqual({\n                elementState: EMPTY_ELEMENT_STATE,\n                data: '0,3123',\n            });\n        });\n\n        it('should NOT pad integer part with zero if user inserts \"aaa0aaa,3123\"', () => {\n            expect(\n                preprocessor(\n                    {\n                        elementState: EMPTY_ELEMENT_STATE,\n                        data: 'aaa0aaa,3123',\n                    },\n                    'insert',\n                ),\n            ).toEqual({\n                elementState: EMPTY_ELEMENT_STATE,\n                data: 'aaa0aaa,3123',\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/thousand-separator-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\n\nimport {identity} from '../../../utils';\nimport type {MaskitoNumberParams} from '../number-params';\nimport {fromNumberParts, toNumberParts} from '../utils';\n\nconst SPACE_REG = /\\s/;\nconst SPACE_GLOBAL_REG = /\\s/g;\n\n/**\n * It adds symbol for separating thousands.\n * @example 1000000 => (thousandSeparator is equal to space) => 1 000 000.\n */\nexport function createThousandSeparatorPostprocessor(\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'negativePattern'\n        | 'postfix'\n        | 'prefix'\n        | 'thousandSeparator'\n        | 'thousandSeparatorPattern'\n    >,\n): MaskitoPostprocessor {\n    const {thousandSeparator, thousandSeparatorPattern} = params;\n\n    if (!thousandSeparator) {\n        return identity;\n    }\n\n    const isSeparatorWhitespace = SPACE_REG.test(thousandSeparator);\n    const isSeparator = isSeparatorWhitespace\n        ? (char: string): boolean => SPACE_REG.test(char)\n        : (char: string): boolean => char === thousandSeparator;\n    const stripSeparators = isSeparatorWhitespace\n        ? (str: string): string => str.replaceAll(SPACE_GLOBAL_REG, '')\n        : (str: string): string => str.replaceAll(thousandSeparator, '');\n\n    return ({value, selection}) => {\n        const [initialFrom, initialTo] = selection;\n        let [from, to] = selection;\n\n        const {prefix, minus, integerPart, decimalSeparator, decimalPart, postfix} =\n            toNumberParts(value, params);\n        const rawLength =\n            `${minus}${integerPart}${decimalSeparator ? `${decimalSeparator}${decimalPart}` : ''}`\n                .length;\n        const normalizedLength = fromNumberParts(\n            {minus, integerPart, decimalSeparator, decimalPart},\n            params,\n        ).length;\n        const deletedChars = normalizedLength - rawLength;\n\n        if (deletedChars > 0 && initialFrom && initialFrom <= deletedChars) {\n            from -= deletedChars;\n        }\n\n        if (deletedChars > 0 && initialTo && initialTo <= deletedChars) {\n            to -= deletedChars;\n        }\n\n        const integerStart = prefix.length + minus.length;\n\n        const groups = thousandSeparatorPattern(stripSeparators(integerPart));\n        const digitAt: number[] = [];\n        let pos = 0;\n\n        for (const [i, group] of groups.entries()) {\n            if (i > 0) {\n                pos += thousandSeparator.length;\n            }\n\n            for (let j = 0; j < group.length; j++) {\n                digitAt.push(pos + j);\n            }\n\n            pos += group.length;\n        }\n\n        const formatted = groups.join(thousandSeparator);\n\n        const mapCursor = (cursor: number): number => {\n            const offset = cursor - integerStart;\n\n            if (offset <= 0) {\n                return cursor;\n            }\n\n            if (offset >= integerPart.length) {\n                return cursor + formatted.length - integerPart.length;\n            }\n\n            const digitCount = stripSeparators(integerPart.slice(0, offset)).length;\n            const prevWasSep = isSeparator(integerPart.charAt(offset - 1));\n\n            if (prevWasSep) {\n                return integerStart + (digitAt[digitCount] ?? formatted.length);\n            }\n\n            return integerStart + (digitAt[digitCount - 1] ?? -1) + 1;\n        };\n\n        return {\n            value: fromNumberParts(\n                {\n                    prefix,\n                    minus,\n                    integerPart: formatted,\n                    decimalSeparator,\n                    decimalPart,\n                    postfix,\n                },\n                params,\n            ),\n            selection: [mapCursor(from), mapCursor(to)],\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/processors/zero-precision-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport {escapeRegExp, identity} from '../../../utils';\nimport type {MaskitoNumberParams} from '../number-params';\nimport {fromNumberParts, toNumberParts} from '../utils';\n\n/**\n * It drops decimal part if `maximumFractionDigits` is zero.\n * @example User pastes '123.45' (but `maximumFractionDigits` is zero) => 123\n */\nexport function createZeroPrecisionPreprocessor(\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'negativePattern'\n        | 'postfix'\n        | 'prefix'\n    >,\n): MaskitoPreprocessor {\n    const {maximumFractionDigits, decimalSeparator} = params;\n\n    if (\n        maximumFractionDigits > 0 ||\n        !decimalSeparator // all separators should be treated only as thousand separators\n    ) {\n        return identity;\n    }\n\n    const decimalPartRegExp = new RegExp(`${escapeRegExp(decimalSeparator)}.*$`, 'g');\n\n    return ({elementState, data}) => {\n        const {value, selection} = elementState;\n        const {prefix, postfix, ...numberParts} = toNumberParts(value, params);\n        const [from, to] = selection;\n        const onlyNumber = fromNumberParts(numberParts, params).replace(\n            decimalPartRegExp,\n            '',\n        );\n        const newValue = fromNumberParts(\n            {...toNumberParts(onlyNumber, params), prefix, postfix},\n            params,\n        );\n\n        return {\n            elementState: {\n                selection: [\n                    Math.min(from, newValue.length),\n                    Math.min(to, newValue.length),\n                ],\n                value: newValue,\n            },\n            data: data.replace(decimalPartRegExp, ''),\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/tests/number-mask.spec.ts",
    "content": "import {beforeEach, describe, expect, it} from '@jest/globals';\nimport {\n    MASKITO_DEFAULT_OPTIONS,\n    type MaskitoOptions,\n    maskitoTransform,\n} from '@maskito/core';\nimport {type MaskitoNumberParams, maskitoParseNumber} from '@maskito/kit';\n\n// TODO: fix later, drop implicit dependencies\nimport {intlPattern} from '../../../../../../demo/src/pages/kit/number/examples/9-thousand-separator-pattern-intl/mask';\nimport {\n    CHAR_EM_DASH,\n    CHAR_EN_DASH,\n    CHAR_HYPHEN,\n    CHAR_JP_HYPHEN,\n    CHAR_MINUS,\n    CHAR_NO_BREAK_SPACE,\n    CHAR_ZERO_WIDTH_SPACE,\n} from '../../../constants';\nimport {maskitoNumberOptionsGenerator} from '../number-mask';\n\ndescribe('Number (maskitoTransform)', () => {\n    describe('`maximumFractionDigits` is `0`', () => {\n        let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n        beforeEach(() => {\n            options = maskitoNumberOptionsGenerator({\n                decimalSeparator: ',',\n                decimalPseudoSeparators: ['.'],\n                maximumFractionDigits: 0,\n            });\n        });\n\n        it('drops decimal part (123,45)', () => {\n            expect(maskitoTransform('123,45', options)).toBe('123');\n        });\n\n        it('drops decimal part (123.45)', () => {\n            expect(maskitoTransform('123.45', options)).toBe('123');\n        });\n\n        it('keeps minus sign (-123)', () => {\n            expect(maskitoTransform('-123', options)).toBe('−123');\n        });\n    });\n\n    describe('`thousandSeparator` is equal to the item from `decimalPseudoSeparators`', () => {\n        let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n        beforeEach(() => {\n            options = maskitoNumberOptionsGenerator({\n                decimalSeparator: ',',\n                decimalPseudoSeparators: ['.', ','],\n                thousandSeparator: '.',\n                maximumFractionDigits: 2,\n            });\n        });\n\n        it('replace space to dot (121.321)', () => {\n            expect(maskitoTransform('121 321', options)).toBe('121.321');\n        });\n\n        it('should`t have changes (121.321)', () => {\n            expect(maskitoTransform('121.321', options)).toBe('121.321');\n        });\n\n        it('drops last symbol in decimal part (120,45)', () => {\n            expect(maskitoTransform('120,450', options)).toBe('120,45');\n        });\n\n        it('keeps minus sign (-123.434) and replace space to dot', () => {\n            expect(maskitoTransform('−120 343', options)).toBe('−120.343');\n        });\n    });\n\n    it('should accept simple and non-breaking spaces as interchangeable characters for [thousandSeparator]', () => {\n        const options = maskitoNumberOptionsGenerator({\n            postfix: ' $',\n            thousandSeparator: ' ',\n        });\n\n        expect(maskitoTransform('45 001 $', options)).toBe('45 001 $'); // initialization phase\n        expect(maskitoTransform('45 001 $', options)).toBe('45 001 $'); // next user interaction\n    });\n\n    describe('`thousandSeparator` is equal to the item from `decimalPseudoSeparators` with zero padding', () => {\n        let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n        beforeEach(() => {\n            options = maskitoNumberOptionsGenerator({\n                decimalSeparator: ',',\n                thousandSeparator: '.',\n                decimalPseudoSeparators: ['.', ','],\n                maximumFractionDigits: 2,\n                minimumFractionDigits: 2,\n            });\n        });\n\n        it('add dots and decimals (21.121.321,00)', () => {\n            expect(maskitoTransform('21121321', options)).toBe('21.121.321,00');\n        });\n    });\n\n    describe('`postfix` contains point and space (` lbs.`)', () => {\n        let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n        describe('maximumFractionDigits: 2', () => {\n            beforeEach(() => {\n                options = maskitoNumberOptionsGenerator({\n                    postfix: ' lbs.',\n                    maximumFractionDigits: 2,\n                });\n            });\n\n            it('empty textfield => empty textfield', () => {\n                expect(maskitoTransform('', options)).toBe('');\n            });\n\n            it('only postfix => Only postfix', () => {\n                expect(maskitoTransform(' lbs.', options)).toBe(' lbs.');\n            });\n\n            it('5 => 5 lbs.', () => {\n                expect(maskitoTransform('5', options)).toBe('5 lbs.');\n            });\n\n            it('0.42 => 0.42 lbs.', () => {\n                expect(maskitoTransform('0.42', options)).toBe('0.42 lbs.');\n            });\n\n            it('1 000 => 1 000 lbs.', () => {\n                expect(maskitoTransform('1 000', options)).toBe('1 000 lbs.');\n            });\n\n            it('1 000. => 1 000. lbs.', () => {\n                expect(maskitoTransform('1 000.', options)).toBe('1 000. lbs.');\n            });\n\n            it('paste 1 000<space> => 1 000 |lbs.', () => {\n                expect(\n                    maskitoTransform(\n                        {value: '1 000 ', selection: ['1 000 '.length, '1 000 '.length]},\n                        options,\n                    ),\n                ).toEqual({\n                    value: '1 000 lbs.',\n                    selection: ['1 000 '.length, '1 000 '.length],\n                });\n            });\n\n            it('1 000 lbs. => 1 000 lbs.', () => {\n                expect(maskitoTransform('1 000 lbs.', options)).toBe('1 000 lbs.');\n            });\n        });\n\n        describe('maximumFractionDigits: 0', () => {\n            beforeEach(() => {\n                options = maskitoNumberOptionsGenerator({\n                    postfix: ' lbs.',\n                    maximumFractionDigits: 0,\n                });\n            });\n\n            it('empty textfield => empty textfield', () => {\n                expect(maskitoTransform('', options)).toBe('');\n            });\n\n            it('only postfix => Only postfix', () => {\n                expect(maskitoTransform(' lbs.', options)).toBe(' lbs.');\n            });\n\n            it('5 => 5 lbs.', () => {\n                expect(maskitoTransform('5', options)).toBe('5 lbs.');\n            });\n\n            it('0.42 => 0 lbs.', () => {\n                expect(maskitoTransform('0.42', options)).toBe('0 lbs.');\n            });\n\n            it('1 000 => 1 000 lbs.', () => {\n                expect(maskitoTransform('1 000', options)).toBe('1 000 lbs.');\n            });\n\n            it('1 000. => 1 000 lbs.', () => {\n                expect(maskitoTransform('1 000.', options)).toBe('1 000 lbs.');\n            });\n\n            it('1 000 lbs. => 1 000 lbs.', () => {\n                expect(maskitoTransform('1 000 lbs.', options)).toBe('1 000 lbs.');\n            });\n        });\n    });\n\n    describe('`prefix` contains point and space (`lbs. `)', () => {\n        let params!: MaskitoNumberParams;\n        let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n        describe('maximumFractionDigits: 2', () => {\n            beforeEach(() => {\n                options = maskitoNumberOptionsGenerator({\n                    prefix: 'lbs. ',\n                    maximumFractionDigits: 2,\n                });\n            });\n\n            it('empty textfield => empty textfield', () => {\n                expect(maskitoTransform('', options)).toBe('');\n            });\n\n            it('only prefix => Only prefix', () => {\n                expect(maskitoTransform('lbs. ', options)).toBe('lbs. ');\n            });\n\n            it('5 => lbs. 5', () => {\n                expect(maskitoTransform('5', options)).toBe('lbs. 5');\n            });\n\n            it('0.42 => lbs. 0.42', () => {\n                expect(maskitoTransform('0.42', options)).toBe('lbs. 0.42');\n            });\n\n            it('1 000 => lbs. 1 000', () => {\n                expect(maskitoTransform('1 000', options)).toBe('lbs. 1 000');\n            });\n\n            it('1 000. => lbs. 1 000', () => {\n                expect(maskitoTransform('1 000.', options)).toBe('lbs. 1 000.');\n            });\n\n            it('lbs. 1 000  => lbs. 1 000', () => {\n                expect(maskitoTransform('lbs. 1 000', options)).toBe('lbs. 1 000');\n            });\n        });\n\n        describe('maximumFractionDigits: 0', () => {\n            beforeEach(() => {\n                options = maskitoNumberOptionsGenerator({\n                    prefix: 'lbs. ',\n                    maximumFractionDigits: 0,\n                });\n            });\n\n            it('empty textfield => empty textfield', () => {\n                expect(maskitoTransform('', options)).toBe('');\n            });\n\n            it('only prefix => Only prefix', () => {\n                expect(maskitoTransform('lbs. ', options)).toBe('lbs. ');\n            });\n\n            it('5 => lbs. 5', () => {\n                expect(maskitoTransform('5', options)).toBe('lbs. 5');\n            });\n\n            it('0.42 => lbs. 0', () => {\n                expect(maskitoTransform('0.42', options)).toBe('lbs. 0');\n            });\n\n            it('1 000 => lbs. 1 000', () => {\n                expect(maskitoTransform('1 000', options)).toBe('lbs. 1 000');\n            });\n\n            it('1 000. => lbs. 1 000', () => {\n                expect(maskitoTransform('1 000.', options)).toBe('lbs. 1 000');\n            });\n\n            it('lbs. 1 000 => lbs. 1 000', () => {\n                expect(maskitoTransform('lbs. 1 000', options)).toBe('lbs. 1 000');\n            });\n        });\n\n        describe('prefix ends with same character as decimal separator equals (zero-width space workaround)', () => {\n            beforeEach(() => {\n                params = {\n                    prefix: 'lbs.',\n                    decimalSeparator: '.',\n                    maximumFractionDigits: 2,\n                };\n                options = maskitoNumberOptionsGenerator(params);\n            });\n\n            it('empty textfield => empty textfield', () => {\n                expect(maskitoTransform('', options)).toBe('');\n            });\n\n            it('only prefix => prefix with zero-width space', () => {\n                expect(maskitoTransform('lbs.', options)).toBe(\n                    `lbs.${CHAR_ZERO_WIDTH_SPACE}`,\n                );\n            });\n\n            it('5 => lbs.5', () => {\n                expect(maskitoTransform('5', options)).toBe(\n                    `lbs.${CHAR_ZERO_WIDTH_SPACE}5`,\n                );\n            });\n\n            it('0.42 => lbs.0.42', () => {\n                const expected = `lbs.${CHAR_ZERO_WIDTH_SPACE}0.42`;\n\n                expect(maskitoTransform('0.42', options)).toBe(expected);\n                expect(maskitoParseNumber(expected, params)).toBe(0.42);\n            });\n\n            it('42 => lbs.42', () => {\n                const expected = `lbs.${CHAR_ZERO_WIDTH_SPACE}42`;\n\n                expect(maskitoTransform('42', options)).toBe(expected);\n                expect(maskitoParseNumber(expected, params)).toBe(42);\n            });\n\n            it('1 000 => lbs.1 000', () => {\n                const expected = `lbs.${CHAR_ZERO_WIDTH_SPACE}1${CHAR_NO_BREAK_SPACE}000`;\n\n                expect(maskitoTransform(`1${CHAR_NO_BREAK_SPACE}000`, options)).toBe(\n                    expected,\n                );\n                expect(maskitoParseNumber(expected, params)).toBe(1000);\n            });\n\n            it('1 000. => lbs.1 000.', () => {\n                const expected = `lbs.${CHAR_ZERO_WIDTH_SPACE}1${CHAR_NO_BREAK_SPACE}000.`;\n\n                expect(maskitoTransform('1 000.', options)).toBe(expected);\n                expect(maskitoParseNumber(expected, params)).toBe(1000);\n            });\n        });\n    });\n\n    describe('`prefix` is positioned after minus sign', () => {\n        let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n        beforeEach(() => {\n            options = maskitoNumberOptionsGenerator({\n                prefix: '$',\n                minusSign: '-',\n                negativePattern: 'minusFirst',\n                decimalSeparator: '.',\n                maximumFractionDigits: 2,\n            });\n        });\n\n        it('empty textfield => empty textfield', () => {\n            expect(maskitoTransform('', options)).toBe('');\n        });\n\n        it('only minus sign => add prefix too', () => {\n            expect(maskitoTransform('-', options)).toBe('-$');\n        });\n\n        it('minus + prefix', () => {\n            expect(maskitoTransform('-$', options)).toBe('-$');\n        });\n\n        it('-123 => -$123', () => {\n            expect(maskitoTransform('-123', options)).toBe('-$123');\n        });\n\n        it('123 => $123', () => {\n            expect(maskitoTransform('123', options)).toBe('$123');\n        });\n\n        it('-.42 => -$0.42', () => {\n            expect(maskitoTransform('-.42', options)).toBe('-$.42');\n        });\n    });\n\n    describe('`postfix` starts with point | [postfix]=\".000 km\" & [maximumFractionDigits]=\"0\"', () => {\n        let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n        beforeEach(() => {\n            options = maskitoNumberOptionsGenerator({\n                postfix: '.000 km',\n                // Ensure that default point as decimal separator is compatible with postfix\n                decimalSeparator: '.',\n                maximumFractionDigits: 0,\n            });\n        });\n\n        it('empty textfield => empty textfield', () => {\n            expect(maskitoTransform('', options)).toBe('');\n        });\n\n        it('only postfix => only postfix', () => {\n            expect(maskitoTransform('.000 km', options)).toBe('.000 km');\n        });\n\n        it('1.000 km => 1.000 km', () => {\n            expect(maskitoTransform('1.000 km', options)).toBe('1.000 km');\n        });\n\n        it('100.000 km => 100.000 km', () => {\n            expect(maskitoTransform('100.000 km', options)).toBe('100.000 km');\n        });\n\n        it('-1.000 km => -1.000 km', () => {\n            expect(maskitoTransform(`${CHAR_HYPHEN}1.000 km`, options)).toBe(\n                `${CHAR_MINUS}1.000 km`,\n            );\n        });\n\n        it('thousandSeparator equals to empty string', () => {\n            expect(\n                maskitoTransform(\n                    '123456789',\n                    maskitoNumberOptionsGenerator({\n                        postfix: '.000 km',\n                        thousandSeparator: '',\n                    }),\n                ),\n            ).toBe('123456789.000 km');\n        });\n\n        describe('thousandSeparator equals to point too', () => {\n            beforeEach(() => {\n                options = maskitoNumberOptionsGenerator({\n                    postfix: '.000 km',\n                    thousandSeparator: '.',\n                    // by default, decimalSeparator === '.' & maximumFractionDigits === 0\n                });\n            });\n\n            it('-123 => -123.000 km', () => {\n                expect(maskitoTransform('-123', options)).toBe(`${CHAR_MINUS}123.000 km`);\n            });\n\n            it('123456 => 123.456.000 km', () => {\n                expect(maskitoTransform('123456', options)).toBe('123.456.000 km');\n            });\n        });\n    });\n\n    describe('should transform full width number to half width', () => {\n        describe('at any time', () => {\n            it('at the 1st time (after initialization)', () => {\n                const options = maskitoNumberOptionsGenerator({thousandSeparator: '_'});\n\n                expect(maskitoTransform('１２３４５', options)).toBe('12_345');\n            });\n\n            it('at the 2nd time (after initialization)', () => {\n                const options = maskitoNumberOptionsGenerator({thousandSeparator: '_'});\n\n                maskitoTransform('１２３４５', options);\n\n                expect(maskitoTransform('１２３４５', options)).toBe('12_345');\n            });\n        });\n    });\n\n    describe('applies `minusSign` property correctly', () => {\n        const minuses = [\n            {value: CHAR_HYPHEN, name: 'hyphen'},\n            {value: CHAR_MINUS, name: 'unicode minus sign'},\n            {value: 'i', name: 'i'},\n        ];\n\n        const numbers = ['23', '321', '2 432'];\n\n        const pseudoMinuses = [\n            {value: CHAR_HYPHEN, name: 'hyphen'},\n            {value: CHAR_EN_DASH, name: 'en-dash'},\n            {value: CHAR_EM_DASH, name: 'em-dash'},\n            {value: CHAR_JP_HYPHEN, name: 'japanese prolonged sound mark'},\n            {value: CHAR_MINUS, name: 'unicode minus sign'},\n        ];\n\n        minuses.forEach((minus) => {\n            const options = maskitoNumberOptionsGenerator({\n                minusSign: minus.value,\n                thousandSeparator: ' ',\n            });\n\n            pseudoMinuses.forEach((pseudoMinus) => {\n                numbers.forEach((number) => {\n                    it(`transforms ${pseudoMinus.name} into ${minus.name}`, () => {\n                        expect(\n                            maskitoTransform(`${pseudoMinus.value}${number}`, options),\n                        ).toBe(`${minus.value}${number}`);\n                    });\n                });\n            });\n        });\n    });\n\n    describe('custom minus should properly work with min(max) value', () => {\n        let options = MASKITO_DEFAULT_OPTIONS;\n\n        [\n            {value: CHAR_HYPHEN, name: 'hyphen'},\n            {value: CHAR_EN_DASH, name: 'en-dash'},\n            {value: CHAR_EM_DASH, name: 'em-dash'},\n            {value: CHAR_JP_HYPHEN, name: 'japanese prolonged sound mark'},\n            {value: CHAR_MINUS, name: 'unicode minus sign'},\n        ].forEach((minus) => {\n            describe(`applies ${minus.name} properly`, () => {\n                beforeEach(() => {\n                    options = maskitoNumberOptionsGenerator({\n                        min: -123,\n                        minusSign: minus.value,\n                    });\n                });\n\n                it(`-94 => ${minus.value}94`, () => {\n                    expect(maskitoTransform(`${minus.value}94`, options)).toBe(\n                        `${minus.value}94`,\n                    );\n                });\n\n                it(`-432 => ${minus.value}123`, () => {\n                    expect(maskitoTransform(`${minus.value}432`, options)).toBe(\n                        `${minus.value}123`,\n                    );\n                });\n            });\n        });\n    });\n\n    describe('autofill value with extra leading and trailing whitespace (thousand separator is equal to whitespace too)', () => {\n        it('<space x3>123456<space x3>', () => {\n            const options = maskitoNumberOptionsGenerator({thousandSeparator: ' '});\n\n            expect(maskitoTransform('    123456    ', options)).toBe('123 456');\n        });\n\n        it('<space>|123 => |123', () => {\n            const options = maskitoNumberOptionsGenerator({thousandSeparator: ' '});\n\n            expect(maskitoTransform({value: ' 123', selection: [1, 1]}, options)).toEqual(\n                {\n                    value: '123',\n                    selection: [0, 0],\n                },\n            );\n            // Check when initial calibration processor already worked\n            expect(maskitoTransform({value: ' 123', selection: [1, 1]}, options)).toEqual(\n                {\n                    value: '123',\n                    selection: [0, 0],\n                },\n            );\n        });\n    });\n\n    describe('thousandSeparatorPattern', () => {\n        it('default behavior unchanged: 123456789 => 123 456 789 (non-breaking space)', () => {\n            const options = maskitoNumberOptionsGenerator({\n                thousandSeparator: CHAR_NO_BREAK_SPACE,\n            });\n\n            expect(maskitoTransform('123456789', options)).toBe(\n                `123${CHAR_NO_BREAK_SPACE}456${CHAR_NO_BREAK_SPACE}789`,\n            );\n        });\n\n        it('small number unaffected: 999 => 999', () => {\n            const options = maskitoNumberOptionsGenerator({thousandSeparator: ','});\n\n            expect(maskitoTransform('999', options)).toBe('999');\n        });\n\n        it('zero unaffected: 0 => 0', () => {\n            const options = maskitoNumberOptionsGenerator({thousandSeparator: ','});\n\n            expect(maskitoTransform('0', options)).toBe('0');\n        });\n\n        describe('Indian numbering system pattern', () => {\n            const indianPattern = (digits: string): readonly string[] => {\n                if (!digits) {\n                    return [];\n                }\n\n                const last3 = digits.slice(-3);\n                const rest = digits.slice(0, -3);\n                const groups: string[] = [];\n\n                for (let i = 0; i < rest.length; i += 2) {\n                    groups.push(rest.slice(i, i + 2));\n                }\n\n                return [...groups, last3].filter(Boolean);\n            };\n\n            it('123456789 => 12,34,56,789', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: indianPattern,\n                });\n\n                expect(maskitoTransform('123456789', options)).toBe('12,34,56,789');\n            });\n\n            it('single group: 1200 => 1,200', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: indianPattern,\n                });\n\n                expect(maskitoTransform('1200', options)).toBe('1,200');\n            });\n\n            it('negative number: -123456789 => -12,34,56,789', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: indianPattern,\n                });\n\n                expect(maskitoTransform('-123456789', options)).toBe(\n                    `${CHAR_MINUS}12,34,56,789`,\n                );\n            });\n\n            it('decimal part is unaffected: 1234567.89 => 12,34,567.89', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    decimalSeparator: '.',\n                    maximumFractionDigits: 2,\n                    thousandSeparatorPattern: indianPattern,\n                });\n\n                expect(maskitoTransform('1234567.89', options)).toBe('12,34,567.89');\n            });\n\n            it('idempotent: re-transform 12,34,56,789 => 12,34,56,789', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: indianPattern,\n                });\n\n                expect(maskitoTransform('12,34,56,789', options)).toBe('12,34,56,789');\n            });\n        });\n\n        describe('custom thousandSeparator \"_\"', () => {\n            it('1234567 => 1_234_567', () => {\n                const options = maskitoNumberOptionsGenerator({thousandSeparator: '_'});\n\n                expect(maskitoTransform('1234567', options)).toBe('1_234_567');\n            });\n\n            it('negative number: -1234567 => -1_234_567', () => {\n                const options = maskitoNumberOptionsGenerator({thousandSeparator: '_'});\n\n                expect(maskitoTransform('-1234567', options)).toBe(\n                    `${CHAR_MINUS}1_234_567`,\n                );\n            });\n\n            it('decimal part is unaffected: 1234567.89 => 1_234_567.89', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: '_',\n                    decimalSeparator: '.',\n                    maximumFractionDigits: 2,\n                });\n\n                expect(maskitoTransform('1234567.89', options)).toBe('1_234_567.89');\n            });\n\n            it('idempotent: re-transform 1_234_567 => 1_234_567', () => {\n                const options = maskitoNumberOptionsGenerator({thousandSeparator: '_'});\n\n                expect(maskitoTransform('1_234_567', options)).toBe('1_234_567');\n            });\n\n            it('with Indian 2+3 pattern: 1234567 => 12_34_567', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: '_',\n                    thousandSeparatorPattern: intlPattern('en-IN'),\n                });\n\n                expect(maskitoTransform('1234567', options)).toBe('12_34_567');\n            });\n        });\n\n        it('no-op pattern (single group): 1234567 => 1234567', () => {\n            const noGroupingPattern = (digits: string): readonly string[] =>\n                digits ? [digits] : [];\n            const options = maskitoNumberOptionsGenerator({\n                thousandSeparator: ',',\n                thousandSeparatorPattern: noGroupingPattern,\n            });\n\n            expect(maskitoTransform('1234567', options)).toBe('1234567');\n        });\n\n        it('prefix + custom pattern: ₹1234567 => ₹12,34,567', () => {\n            const options = maskitoNumberOptionsGenerator({\n                prefix: '₹',\n                thousandSeparator: ',',\n                thousandSeparatorPattern: intlPattern('en-IN'),\n            });\n\n            expect(maskitoTransform('1234567', options)).toBe('₹12,34,567');\n        });\n\n        it('postfix + custom pattern: 1234567 => 1.234.567 €', () => {\n            const options = maskitoNumberOptionsGenerator({\n                postfix: ' €',\n                thousandSeparator: '.',\n                decimalSeparator: ',',\n                thousandSeparatorPattern: intlPattern('de-DE'),\n            });\n\n            expect(maskitoTransform('1234567', options)).toBe('1.234.567 €');\n        });\n\n        describe('Intl.NumberFormat-based grouping', () => {\n            it('de-DE: 1234567 => 1.234.567 (3-digit grouping, dot separator)', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: '.',\n                    decimalSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('de-DE'),\n                });\n\n                expect(maskitoTransform('1234567', options)).toBe('1.234.567');\n            });\n\n            it('ja-JP: 1234567 => 1,234,567 (3-digit grouping)', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('ja-JP'),\n                });\n\n                expect(maskitoTransform('1234567', options)).toBe('1,234,567');\n            });\n\n            it('en-IN: 123456789 => 12,34,56,789 (Indian 2+3 grouping)', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('en-IN'),\n                });\n\n                expect(maskitoTransform('123456789', options)).toBe('12,34,56,789');\n            });\n\n            it('en-IN: single group 1200 => 1,200', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('en-IN'),\n                });\n\n                expect(maskitoTransform('1200', options)).toBe('1,200');\n            });\n\n            it('en-US: same as default 3-digit grouping', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('en-US'),\n                });\n\n                expect(maskitoTransform('123456789', options)).toBe('123,456,789');\n            });\n\n            it('en-US: large number 1234567890 => 1,234,567,890', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('en-US'),\n                });\n\n                expect(maskitoTransform('1234567890', options)).toBe('1,234,567,890');\n            });\n\n            it('en-IN: negative number -123456789 => -12,34,56,789', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('en-IN'),\n                });\n\n                expect(maskitoTransform('-123456789', options)).toBe(\n                    `${CHAR_MINUS}12,34,56,789`,\n                );\n            });\n\n            it('de-DE: decimal part unaffected: 1234567,89 => 1.234.567,89', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: '.',\n                    decimalSeparator: ',',\n                    maximumFractionDigits: 2,\n                    thousandSeparatorPattern: intlPattern('de-DE'),\n                });\n\n                expect(maskitoTransform('1234567,89', options)).toBe('1.234.567,89');\n            });\n\n            it('de-DE: minimumFractionDigits: 1234 => 1.234,00', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: '.',\n                    decimalSeparator: ',',\n                    maximumFractionDigits: 2,\n                    minimumFractionDigits: 2,\n                    thousandSeparatorPattern: intlPattern('de-DE'),\n                });\n\n                expect(maskitoTransform('1234', options)).toBe('1.234,00');\n            });\n\n            it('hi-IN: 1234567 => 12,34,567 (Hindi India, Indian grouping)', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('hi-IN'),\n                });\n\n                expect(maskitoTransform('1234567', options)).toBe('12,34,567');\n            });\n\n            it('hi-IN: 123456789 => 12,34,56,789 (scales same as en-IN)', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('hi-IN'),\n                });\n\n                expect(maskitoTransform('123456789', options)).toBe('12,34,56,789');\n            });\n\n            it('fr-FR: 1234567 => 1,234,567 (French, 3-digit grouping)', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('fr-FR'),\n                });\n\n                expect(maskitoTransform('1234567', options)).toBe('1,234,567');\n            });\n\n            it('ru-RU: 1234567 => 1,234,567 (Russian, 3-digit grouping)', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('ru-RU'),\n                });\n\n                expect(maskitoTransform('1234567', options)).toBe('1,234,567');\n            });\n\n            it('pt-BR: 1234567 => 1,234,567 (Brazilian Portuguese, 3-digit grouping)', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('pt-BR'),\n                });\n\n                expect(maskitoTransform('1234567', options)).toBe('1,234,567');\n            });\n\n            it('zh-CN: 1234567 => 1,234,567 (Chinese Simplified, 3-digit grouping)', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('zh-CN'),\n                });\n\n                expect(maskitoTransform('1234567', options)).toBe('1,234,567');\n            });\n\n            it('ko-KR: 1234567 => 1,234,567 (Korean, 3-digit grouping)', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('ko-KR'),\n                });\n\n                expect(maskitoTransform('1234567', options)).toBe('1,234,567');\n            });\n\n            it('tr-TR: 1234567 => 1,234,567 (Turkish, 3-digit grouping)', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('tr-TR'),\n                });\n\n                expect(maskitoTransform('1234567', options)).toBe('1,234,567');\n            });\n\n            describe('Indian 2+3 grouping', () => {\n                it('ne-NP: uses Indian 2+3 grouping', () => {\n                    const options = maskitoNumberOptionsGenerator({\n                        thousandSeparator: ',',\n                        thousandSeparatorPattern: intlPattern('ne-NP'),\n                    });\n\n                    expect(maskitoTransform('1234567', options)).toBe('12,34,567');\n                });\n\n                it('en-IN: 1200 => 1,200 (Indian grouping, 4-digit number)', () => {\n                    const options = maskitoNumberOptionsGenerator({\n                        thousandSeparator: ',',\n                        thousandSeparatorPattern: intlPattern('en-IN'),\n                    });\n\n                    expect(maskitoTransform('1200', options)).toBe('1,200');\n                });\n            });\n\n            it('es-ES: 1200 => 1200 (no separator for 4-digit numbers in Spanish locale)', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ',',\n                    thousandSeparatorPattern: intlPattern('es-ES'),\n                });\n\n                expect(maskitoTransform('1200', options)).toBe('1200');\n            });\n        });\n    });\n\n    describe('BigInt support', () => {\n        it('number beyond MAX_SAFE_INTEGER is formatted correctly', () => {\n            const options = maskitoNumberOptionsGenerator({thousandSeparator: ','});\n\n            expect(maskitoTransform('9007199254740993', options)).toBe(\n                '9,007,199,254,740,993',\n            );\n        });\n\n        it('negative number beyond MIN_SAFE_INTEGER is formatted correctly', () => {\n            const options = maskitoNumberOptionsGenerator({thousandSeparator: ','});\n\n            expect(maskitoTransform('-9007199254740993', options)).toBe(\n                `${CHAR_MINUS}9,007,199,254,740,993`,\n            );\n        });\n\n        it('max: BigInt clamps value exceeding max', () => {\n            const options = maskitoNumberOptionsGenerator({\n                thousandSeparator: ',',\n                max: BigInt('1000000'),\n            });\n\n            expect(maskitoTransform('9007199254740993', options)).toBe('1,000,000');\n        });\n\n        it('min: BigInt clamps negative value below min', () => {\n            const options = maskitoNumberOptionsGenerator({\n                thousandSeparator: ',',\n                min: BigInt('-1000000'),\n            });\n\n            expect(maskitoTransform('-9007199254740993', options)).toBe(\n                `${CHAR_MINUS}1,000,000`,\n            );\n        });\n\n        it('thousandSeparatorPattern + BigInt: large Indian-grouped number', () => {\n            const options = maskitoNumberOptionsGenerator({\n                thousandSeparator: ',',\n                thousandSeparatorPattern: intlPattern('en-IN'),\n            });\n\n            expect(maskitoTransform('12345678901', options)).toBe('12,34,56,78,901');\n        });\n    });\n\n    describe('min/max validation should ignore digits inside affixes', () => {\n        describe('postfix = cm3', () => {\n            it('value within max is NOT clamped', () => {\n                expect(\n                    maskitoTransform(\n                        '123',\n                        maskitoNumberOptionsGenerator({max: 123, postfix: 'cm3'}),\n                    ),\n                ).toBe('123cm3');\n\n                expect(\n                    maskitoTransform(\n                        '1234567890123456',\n                        maskitoNumberOptionsGenerator({\n                            postfix: 'cm3',\n                            max: Number.MAX_SAFE_INTEGER,\n                        }),\n                    ),\n                ).toBe('1 234 567 890 123 456cm3');\n            });\n\n            it('value exceeding max IS clamped without losing postfix', () => {\n                const options = maskitoNumberOptionsGenerator({\n                    thousandSeparator: ' ',\n                    max: 999,\n                    postfix: 'cm3',\n                });\n\n                expect(maskitoTransform('12345', options)).toBe('999cm3');\n            });\n\n            it('negative value within min is NOT clamped', () => {\n                expect(\n                    maskitoTransform(\n                        '-1234567890123456',\n                        maskitoNumberOptionsGenerator({postfix: 'cm3'}),\n                    ),\n                ).toBe(`${CHAR_MINUS}1 234 567 890 123 456cm3`);\n            });\n        });\n\n        describe('prefix = 100 x', () => {\n            it('value within max is NOT clamped', () => {\n                expect(\n                    maskitoTransform(\n                        '500',\n                        maskitoNumberOptionsGenerator({\n                            max: 999,\n                            prefix: '100 x ',\n                        }),\n                    ),\n                ).toBe('100 x 500');\n            });\n\n            it('value exceeding max IS clamped', () => {\n                expect(\n                    maskitoTransform(\n                        '1234',\n                        maskitoNumberOptionsGenerator({\n                            max: 999,\n                            prefix: '100 x ',\n                        }),\n                    ),\n                ).toBe('100 x 999');\n            });\n        });\n    });\n\n    it('[thousandSeparator] is equal to [decimalSeparator] when [maximumFractionDigits]=0', () => {\n        const options = maskitoNumberOptionsGenerator({\n            thousandSeparator: '.',\n            decimalSeparator: '.', // default value\n            maximumFractionDigits: 0, // default value\n        });\n\n        expect(maskitoTransform('123.456', options)).toBe('123.456');\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/extract-affixes.ts",
    "content": "import {escapeRegExp} from '../../../utils/escape-reg-exp';\nimport type {MaskitoNumberParams} from '../number-params';\n\nexport function extractAffixes(\n    value: string,\n    {\n        prefix,\n        postfix,\n        decimalSeparator,\n        decimalPseudoSeparators,\n        minusSign,\n        minusPseudoSigns,\n        maximumFractionDigits,\n    }: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'postfix'\n        | 'prefix'\n    >,\n): {\n    extractedPrefix: string;\n    extractedPostfix: string;\n    cleanValue: string;\n} {\n    const decimalSeparators = [...decimalPseudoSeparators, decimalSeparator]\n        .map((x) => `\\\\${x}`)\n        .join('');\n    const minuses = [...minusPseudoSigns, minusSign].map((x) => `\\\\${x}`).join('');\n    const prefixRegExp =\n        prefix &&\n        new RegExp(`^([${minuses}])?(${prefix.split('').map(escapeRegExp).join('?')}?)`);\n    const postfixRegExp =\n        postfix && new RegExp(`${postfix.split('').map(escapeRegExp).join('?')}?$`);\n\n    const [, , extractedPrefix = ''] = value.match(prefixRegExp) ?? [];\n    const [extractedPostfix = ''] = value.match(postfixRegExp) ?? [];\n\n    const cleanValue = value\n        .replace(prefixRegExp, prefix && '$1')\n        .replace(postfixRegExp, '');\n\n    const leadingDecimalSeparatorRE = new RegExp(\n        decimalSeparator && maximumFractionDigits > 0 ? `^[${decimalSeparators}]` : '',\n    );\n    const leadingDigitsRE = new RegExp(value.endsWith(postfix) ? '' : String.raw`^\\d+`);\n    const trailingDecimalSeparatorRE = new RegExp(\n        decimalSeparator && maximumFractionDigits > 0 ? `[${decimalSeparators}]$` : '',\n    );\n    const trailingDigitsRE = new RegExp(value.startsWith(prefix) ? '' : String.raw`\\d+$`);\n\n    return {\n        extractedPrefix: extractedPrefix\n            .replace(trailingDecimalSeparatorRE, '')\n            .replace(trailingDigitsRE, ''),\n        extractedPostfix: extractedPostfix\n            .replace(leadingDecimalSeparatorRE, '')\n            .replace(leadingDigitsRE, ''),\n        cleanValue: `${trailingDigitsRE.exec(extractedPrefix)?.[0] ?? ''}${trailingDecimalSeparatorRE.exec(extractedPrefix)?.[0] ?? ''}${cleanValue}${leadingDigitsRE.exec(extractedPostfix)?.[0] ?? ''}${leadingDecimalSeparatorRE.exec(extractedPostfix)?.[0] ?? ''}`,\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/generate-mask-expression.ts",
    "content": "import type {MaskitoMask} from '@maskito/core';\n\nimport {escapeRegExp} from '../../../utils';\nimport type {MaskitoNumberParams} from '../number-params';\n\nexport function generateMaskExpression({\n    decimalPseudoSeparators,\n    decimalSeparator,\n    maximumFractionDigits,\n    min,\n    minusSign,\n    minusPseudoSigns,\n    postfix,\n    prefix,\n    thousandSeparator,\n}: Pick<\n    Required<MaskitoNumberParams>,\n    | 'decimalPseudoSeparators'\n    | 'decimalSeparator'\n    | 'maximumFractionDigits'\n    | 'min'\n    | 'minusPseudoSigns'\n    | 'minusSign'\n    | 'postfix'\n    | 'prefix'\n    | 'thousandSeparator'\n>): MaskitoMask {\n    const computedPrefix =\n        min < 0 && [minusSign, ...minusPseudoSigns].includes(prefix)\n            ? ''\n            : computeAllOptionalCharsRegExp(prefix);\n    const digit = String.raw`\\d`;\n    const optionalMinus =\n        min < 0 ? `[${minusSign}${minusPseudoSigns.map((x) => `\\\\${x}`).join('')}]?` : '';\n    const integerPart = thousandSeparator\n        ? `[${digit}${escapeRegExp(thousandSeparator).replaceAll(/\\s/g, String.raw`\\s`)}]*`\n        : `[${digit}]*`;\n    const precisionPart = Number.isFinite(maximumFractionDigits)\n        ? maximumFractionDigits\n        : '';\n    const decimalPart =\n        maximumFractionDigits > 0\n            ? `([${escapeRegExp(decimalSeparator)}${decimalPseudoSeparators\n                  .map(escapeRegExp)\n                  .join('')}]${digit}{0,${precisionPart}})?`\n            : '';\n    const computedPostfix = computeAllOptionalCharsRegExp(postfix);\n    const beginning = `(${optionalMinus}${computedPrefix}|${computedPrefix}${optionalMinus})`;\n\n    return new RegExp(`^${beginning}${integerPart}${decimalPart}${computedPostfix}$`);\n}\n\nfunction computeAllOptionalCharsRegExp(str: string): string {\n    return str\n        ? str\n              .split('')\n              .map((char) => `${escapeRegExp(char)}?`)\n              .join('')\n        : '';\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/index.ts",
    "content": "export * from './extract-affixes';\nexport * from './generate-mask-expression';\nexport * from './number-parts';\nexport * from './parse-number';\nexport * from './stringify-number-without-exp';\nexport * from './validate-decimal-pseudo-separators';\nexport * from './with-number-defaults';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/number-parts.ts",
    "content": "import {escapeRegExp} from '../../../utils';\nimport type {MaskitoNumberParams} from '../number-params';\nimport {extractAffixes} from './extract-affixes';\n\ninterface NumberParts {\n    prefix: string;\n    minus: string;\n    integerPart: string;\n    decimalPart: string;\n    decimalSeparator: string;\n    postfix: string;\n}\n\nexport function toNumberParts(\n    value: string,\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        | 'decimalPseudoSeparators'\n        | 'decimalSeparator'\n        | 'maximumFractionDigits'\n        | 'minusPseudoSigns'\n        | 'minusSign'\n        | 'postfix'\n        | 'prefix'\n    >,\n): NumberParts {\n    const {extractedPrefix, cleanValue, extractedPostfix} = extractAffixes(value, params);\n    const {\n        decimalSeparator,\n        minusSign,\n        minusPseudoSigns,\n        decimalPseudoSeparators,\n        maximumFractionDigits,\n    } = params;\n    const [integerWithMinus = '', decimalPart = ''] = decimalSeparator\n        ? cleanValue.split(decimalSeparator)\n        : [cleanValue];\n    const minuses = [minusSign, ...minusPseudoSigns].map((x) => `\\\\${x}`).join('');\n    const [, minus = '', integerPart = ''] =\n        new RegExp(`^([${minuses}])?(.*)`).exec(integerWithMinus) || [];\n\n    return {\n        prefix: extractedPrefix,\n        minus,\n        integerPart,\n        decimalPart,\n        decimalSeparator:\n            decimalSeparator && maximumFractionDigits > 0\n                ? (new RegExp(\n                      `[${[decimalSeparator, ...decimalPseudoSeparators].map(escapeRegExp).join('')}]`,\n                      'i',\n                  ).exec(cleanValue)?.[0] ?? '')\n                : '',\n        postfix: extractedPostfix,\n    };\n}\n\nexport function fromNumberParts(\n    {\n        minus = '',\n        integerPart = '',\n        decimalPart = '',\n        prefix = '',\n        postfix = '',\n        decimalSeparator = '',\n    }: Partial<NumberParts>,\n    params: Pick<\n        Required<MaskitoNumberParams>,\n        'decimalSeparator' | 'minusSign' | 'negativePattern' | 'prefix'\n    >,\n): string {\n    const separator = decimalPart ? params.decimalSeparator : decimalSeparator;\n    const beginning =\n        params.negativePattern === 'minusFirst'\n            ? `${minus}${prefix}`\n            : `${prefix}${minus}`;\n\n    return `${beginning}${integerPart}${separator}${decimalPart}${postfix}`;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/parse-number.ts",
    "content": "import {CHAR_HYPHEN} from '../../../constants';\nimport {type MaskitoNumberParams} from '../number-params';\nimport {fromNumberParts, toNumberParts} from './number-parts';\nimport {withNumberDefaults} from './with-number-defaults';\n\nexport function maskitoParseNumber(\n    maskedNumber: string,\n    params?: MaskitoNumberParams & {bigint?: false},\n): number;\n\nexport function maskitoParseNumber(\n    maskedNumber: string,\n    params?: MaskitoNumberParams & {bigint: true},\n): bigint | null;\n\nexport function maskitoParseNumber(\n    maskedNumber: string,\n    params?: MaskitoNumberParams & {bigint: boolean},\n): bigint | number | null;\n\nexport function maskitoParseNumber(\n    maskedNumber: string,\n    {bigint = false, ...optionalParams}: MaskitoNumberParams & {bigint?: boolean} = {},\n): bigint | number | null {\n    const params = withNumberDefaults(optionalParams);\n    const {minus, integerPart, decimalSeparator, ...numberParts} = toNumberParts(\n        maskedNumber,\n        params,\n    );\n    const unmaskedNumber = fromNumberParts(\n        {\n            ...numberParts,\n            integerPart: integerPart.replaceAll(/\\D/g, ''),\n            decimalSeparator: decimalSeparator && '.',\n            prefix: '',\n            postfix: '',\n            minus: '',\n        },\n        {...params, decimalSeparator: '.'},\n    );\n\n    if (unmaskedNumber) {\n        const sign = minus ? CHAR_HYPHEN : '';\n\n        return bigint\n            ? BigInt(`${sign}${unmaskedNumber}`)\n            : Number(`${sign}${unmaskedNumber}`);\n    }\n\n    return bigint ? null : Number.NaN;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/stringify-number-without-exp.ts",
    "content": "import {CHAR_HYPHEN} from '../../../constants';\nimport {toNumberParts} from './number-parts';\n\nconst LOCALE: Intl.Locale[] = [];\nconst DEFAULT = {\n    minusSign: CHAR_HYPHEN,\n    minusPseudoSigns: [],\n    prefix: '',\n    postfix: '',\n    decimalSeparator: '.',\n    decimalPseudoSeparators: [],\n    maximumFractionDigits: Infinity,\n};\n\n/**\n * Converts a number to a decimal string without using exponential notation.\n *\n * - Numbers without exponent are returned as-is.\n * - Numbers with a positive exponent (`e+N`) are expanded using locale formatting.\n * - Numbers with a negative exponent (`e-N`) are expanded manually to avoid\n *   precision limits of `Number#toFixed` and locale rounding.\n *\n * The sign of the number and the sign of the exponent are handled independently.\n * This guarantees correct formatting for cases like `-1.23e-8`.\n *\n * @param value Number or bigint to convert.\n * @returns Full decimal string representation without exponent notation.\n *\n * @example\n * stringifyNumberWithoutExp(1e25)\n * // \"10000000000000000000000000\"\n *\n * @example\n * stringifyNumberWithoutExp(1.23e-8)\n * // \"0.0000000123\"\n *\n * @example\n * stringifyNumberWithoutExp(-1.23e-8)\n * // \"-0.0000000123\"\n */\nexport function stringifyNumberWithoutExp(value: bigint | number): string {\n    const valueAsString = String(value);\n    const [numberPart = '', exponent] = valueAsString.split('e');\n\n    if (!exponent) {\n        return valueAsString;\n    }\n\n    if (!exponent.startsWith(CHAR_HYPHEN)) {\n        return value.toLocaleString(LOCALE, {useGrouping: false});\n    }\n\n    const {minus, integerPart, decimalPart} = toNumberParts(numberPart, DEFAULT);\n    const digits = `${integerPart}${decimalPart}`;\n    const shift = Math.abs(Number(exponent));\n    const totalZeros = shift - integerPart.length;\n\n    let result: string;\n\n    if (totalZeros >= 0) {\n        result = `0.${'0'.repeat(totalZeros)}${digits}`;\n    } else {\n        const index = integerPart.length - shift;\n\n        result = `${digits.slice(0, index)}.${digits.slice(index)}`;\n    }\n\n    return `${minus}${result}`;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/stringify-number.ts",
    "content": "import {maskitoTransform} from '@maskito/core';\n\nimport {clamp} from '../../../utils';\nimport {maskitoNumberOptionsGenerator} from '../number-mask';\nimport type {MaskitoNumberParams} from '../number-params';\nimport {stringifyNumberWithoutExp} from './stringify-number-without-exp';\nimport {withNumberDefaults} from './with-number-defaults';\n\nexport function maskitoStringifyNumber(\n    number: bigint | number | null,\n    optionalParams: MaskitoNumberParams,\n): string {\n    if (Number.isNaN(number) || number === null) {\n        return '';\n    }\n\n    const params = withNumberDefaults(optionalParams);\n    const value = stringifyNumberWithoutExp(\n        clamp(number, params.min, params.max),\n    ).replace('.', params.decimalSeparator);\n\n    return maskitoTransform(value, maskitoNumberOptionsGenerator(params));\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/tests/parse-number.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {\n    CHAR_EM_DASH,\n    CHAR_EN_DASH,\n    CHAR_HYPHEN,\n    CHAR_JP_HYPHEN,\n    CHAR_MINUS,\n} from '../../../../constants';\nimport type {MaskitoNumberParams} from '../../number-params';\nimport {maskitoParseNumber} from '../parse-number';\n\ndescribe('maskitoParseNumber', () => {\n    describe('decimal separator is dot (default one)', () => {\n        it('thousand separator is space', () => {\n            expect(maskitoParseNumber('1 000 000.42')).toBe(1000000.42);\n        });\n\n        it('thousand separator is hyphen', () => {\n            expect(maskitoParseNumber('1-000-000.42')).toBe(1000000.42);\n        });\n\n        it('thousand separator is empty string', () => {\n            expect(maskitoParseNumber('1000000.42')).toBe(1000000.42);\n        });\n\n        it('thousand separator is point & maximumFractionDigits is unset (default `0`) & decimal separator is unset (default point)', () => {\n            expect(maskitoParseNumber('123.456.789', {thousandSeparator: '.'})).toBe(\n                123456789,\n            );\n        });\n\n        it('empty decimal part & thousand separator is comma', () => {\n            expect(maskitoParseNumber('1,000,000')).toBe(1000000);\n        });\n\n        it('trailing decimal separator', () => {\n            expect(maskitoParseNumber('0.')).toBe(0);\n        });\n    });\n\n    describe('decimal separator is comma', () => {\n        it('thousand separator is space', () => {\n            expect(maskitoParseNumber('42 111,42', {decimalSeparator: ','})).toBe(\n                42111.42,\n            );\n        });\n\n        it('thousand separator is hyphen', () => {\n            expect(maskitoParseNumber('42-111,42', {decimalSeparator: ','})).toBe(\n                42111.42,\n            );\n        });\n\n        it('thousand separator is empty string', () => {\n            expect(maskitoParseNumber('42111,42', {decimalSeparator: ','})).toBe(\n                42111.42,\n            );\n        });\n\n        it('empty decimal part & thousand separator is dot', () => {\n            expect(maskitoParseNumber('42.111', {decimalSeparator: ','})).toBe(42111);\n        });\n\n        it('trailing decimal separator', () => {\n            expect(maskitoParseNumber('42,', {decimalSeparator: ','})).toBe(42);\n        });\n    });\n\n    describe('decimal separator is empty string', () => {\n        it('thousand separator is point', () => {\n            expect(maskitoParseNumber('123.456.789', {decimalSeparator: ''})).toBe(\n                123456789,\n            );\n        });\n\n        it('thousand separator is empty string', () => {\n            expect(maskitoParseNumber('123456', {decimalSeparator: ''})).toBe(123456);\n        });\n    });\n\n    describe('negative numbers', () => {\n        describe('minus sign', () => {\n            it('can be minus', () => {\n                expect(maskitoParseNumber(`${CHAR_MINUS}1`)).toBe(-1);\n            });\n\n            it('can be hyphen', () => {\n                expect(maskitoParseNumber(`${CHAR_HYPHEN}123 456`)).toBe(-123456);\n            });\n\n            it('can be en-dash', () => {\n                expect(maskitoParseNumber(`${CHAR_EN_DASH}123 456 789`)).toBe(-123456789);\n            });\n\n            it('can be em-dash', () => {\n                expect(maskitoParseNumber(`${CHAR_EM_DASH}42`)).toBe(-42);\n            });\n\n            it('can be katakana-hiragana prolonged sound mark', () => {\n                expect(maskitoParseNumber(`${CHAR_JP_HYPHEN}42`)).toBe(-42);\n            });\n\n            it('can be any custom character', () => {\n                expect(maskitoParseNumber('x42', {minusSign: 'x'})).toBe(-42);\n                expect(maskitoParseNumber('!42', {minusSign: '!'})).toBe(-42);\n            });\n        });\n\n        it('parses negative integer number when thousand separator is hyphen & minus sign is hyphen', () => {\n            expect(maskitoParseNumber('-123-456')).toBe(-123456);\n        });\n\n        it('parses negative number with decimal part', () => {\n            expect(maskitoParseNumber('-123.456')).toBe(-123.456);\n        });\n    });\n\n    describe('Prefix & Postfix', () => {\n        it('parses number with only prefix', () => {\n            expect(maskitoParseNumber('$42')).toBe(42);\n        });\n\n        it('parses number with only postfix', () => {\n            expect(maskitoParseNumber('42%')).toBe(42);\n        });\n\n        it('parses number with both prefix and postfix', () => {\n            expect(maskitoParseNumber('$42 per day')).toBe(42);\n        });\n\n        it('parses negative number with prefix', () => {\n            expect(maskitoParseNumber('>-42', {prefix: '>'})).toBe(-42);\n            expect(maskitoParseNumber('> -42', {prefix: '> '})).toBe(-42);\n        });\n\n        describe('prefix/postfix includes point and space', () => {\n            const postfix = ' lbs.';\n            const prefix = 'lbs. ';\n\n            it('parses INTEGER number with postfix \" lbs.\"', () => {\n                expect(maskitoParseNumber('42 lbs.', {postfix})).toBe(42);\n                expect(maskitoParseNumber('1 000 lbs.', {postfix})).toBe(1000);\n                expect(maskitoParseNumber('1 000 lbs.', {postfix})).toBe(1000);\n            });\n\n            it('parses DECIMAL number with postfix \" lbs.\"', () => {\n                expect(maskitoParseNumber('0.42 lbs.', {postfix})).toBe(0.42);\n                expect(maskitoParseNumber('.42 lbs.', {postfix})).toBe(0.42);\n                expect(maskitoParseNumber('1 000.42 lbs.', {postfix})).toBe(1000.42);\n                expect(maskitoParseNumber('1 000. lbs.', {postfix})).toBe(1000);\n            });\n\n            it('parses INTEGER number with prefix \"lbs. \"', () => {\n                expect(maskitoParseNumber('lbs. 42', {prefix})).toBe(42);\n                expect(maskitoParseNumber('lbs. 1 000', {prefix})).toBe(1000);\n                expect(maskitoParseNumber('lbs. 1 000', {prefix})).toBe(1000);\n            });\n\n            it('parses DECIMAL number with prefix \"lbs. \"', () => {\n                expect(maskitoParseNumber('lbs. 0.42', {prefix})).toBe(0.42);\n                expect(maskitoParseNumber('lbs. .42', {prefix})).toBe(0.42);\n                expect(maskitoParseNumber('lbs. 1 000.42', {prefix})).toBe(1000.42);\n                expect(maskitoParseNumber('lbs. 1 000.42', {prefix})).toBe(1000.42);\n\n                const zeroWidthSpace = '\\u200B';\n\n                expect(\n                    maskitoParseNumber(`lbs.${zeroWidthSpace}1 000.42`, {prefix}),\n                ).toBe(1000.42);\n            });\n        });\n\n        describe('postfix includes digits, cm3', () => {\n            const postfix = 'cm3';\n\n            it('no value, only postfix', () => {\n                expect(maskitoParseNumber(postfix, {postfix})).toBeNaN();\n            });\n\n            it('0cm3 => 0', () => {\n                expect(maskitoParseNumber(`0${postfix}`, {postfix})).toBe(0);\n            });\n\n            it('3cm3 => 3', () => {\n                expect(maskitoParseNumber(`3${postfix}`, {postfix})).toBe(3);\n            });\n\n            it('123cm3 => 123', () => {\n                expect(maskitoParseNumber(`123${postfix}`, {postfix})).toBe(123);\n            });\n        });\n    });\n\n    describe('BigInt mode', () => {\n        it('parses large positive integer as BigInt', () => {\n            expect(\n                maskitoParseNumber('987.654.321.098.765.432.100', {\n                    thousandSeparator: '.',\n                    bigint: true,\n                }),\n            ).toEqual(BigInt('987654321098765432100'));\n        });\n\n        it('parses large negative integer as BigInt', () => {\n            expect(\n                maskitoParseNumber('-123 456 789 012 345 678 900', {\n                    thousandSeparator: ' ',\n                    bigint: Boolean('ALWAYS true, but boolean for TS'),\n                }),\n            ).toBe(-123456789012345678900n);\n        });\n\n        it('throws native error on attempt to use {bigint: true} with decimal number', () => {\n            expect(() =>\n                maskitoParseNumber('1_234.56', {\n                    thousandSeparator: '_',\n                    bigint: true,\n                }),\n            ).toThrow('Cannot convert 1234.56 to a BigInt');\n        });\n\n        describe('TypeScript overloads', () => {\n            it('returns `number` when `bigint` is unset', () => {\n                const result = maskitoParseNumber('42');\n\n                expect(typeof result === 'number').toBe(true);\n            });\n\n            describe('returns `number` type when `bigint` is explicitly set to `false`', () => {\n                it('and actual returned value is `number` for non-empty value', () => {\n                    const result = maskitoParseNumber('42', {bigint: false});\n\n                    expect(typeof result === 'number').toBe(true);\n                });\n\n                it('and actual returned value is `NaN` for empty value', () => {\n                    const result = maskitoParseNumber('-', {bigint: false});\n\n                    expect(Number.isNaN(result)).toBe(true);\n                });\n            });\n\n            describe('returns `bigint | null` type when `bigint` is explicitly set to `true`', () => {\n                it('and actual returned value is `bigint` for non-empty value', () => {\n                    const result = maskitoParseNumber('42', {bigint: true});\n\n                    expect(typeof result === 'bigint').toBe(true);\n                });\n\n                it('and actual returned value is `null` for empty value', () => {\n                    const result = maskitoParseNumber('', {bigint: true});\n\n                    expect(result === null).toBe(true);\n                });\n            });\n\n            it('returns `bigint | number | null` when `bigint` is computed dynamically', () => {\n                const result = maskitoParseNumber('42', {bigint: Boolean(1)});\n                const TRUE = true;\n\n                expect(typeof result === 'bigint').toBe(TRUE);\n            });\n        });\n    });\n\n    describe('Minus is positioned before prefix', () => {\n        const params: MaskitoNumberParams = {\n            decimalSeparator: '.',\n            minusSign: CHAR_MINUS,\n            minusPseudoSigns: [\n                CHAR_MINUS,\n                CHAR_HYPHEN,\n                CHAR_EN_DASH,\n                CHAR_EM_DASH,\n                CHAR_JP_HYPHEN,\n            ],\n            // Even without knowing `prefix` / `maximumFractionDigits` values `maskitoParseNumber` is capable to parse number\n        };\n\n        it('-$42 (with leading minus character)', () => {\n            expect(maskitoParseNumber(`${CHAR_MINUS}$42`, params)).toBe(-42);\n        });\n\n        describe('pseudo minuses', () => {\n            [CHAR_MINUS, CHAR_HYPHEN, CHAR_EN_DASH, CHAR_EM_DASH, CHAR_JP_HYPHEN].forEach(\n                (pseudoMinus) => {\n                    it(`${pseudoMinus}$42`, () => {\n                        expect(maskitoParseNumber(`${pseudoMinus}$42`, params)).toBe(-42);\n                    });\n                },\n            );\n        });\n\n        it('-$0.42', () => {\n            expect(maskitoParseNumber(`${CHAR_MINUS}$0.42`, params)).toBe(-0.42);\n        });\n\n        it('-$.42', () => {\n            expect(maskitoParseNumber('-$.42', params)).toBe(-0.42);\n        });\n\n        it('-$0', () => {\n            expect(maskitoParseNumber('-$0', params)).toBe(-0);\n        });\n\n        it('-$', () => {\n            expect(maskitoParseNumber(`${CHAR_MINUS}$`, params)).toBeNaN();\n        });\n    });\n\n    describe('NaN', () => {\n        it('empty string => NaN', () => {\n            expect(maskitoParseNumber('')).toBeNaN();\n        });\n\n        it('decimal separator only => NaN', () => {\n            expect(maskitoParseNumber('.')).toBeNaN();\n            expect(maskitoParseNumber(',', {decimalSeparator: ','})).toBeNaN();\n        });\n\n        it('negative sign only => NaN', () => {\n            expect(maskitoParseNumber(CHAR_MINUS)).toBeNaN();\n            expect(maskitoParseNumber(CHAR_HYPHEN)).toBeNaN();\n            expect(maskitoParseNumber(CHAR_EN_DASH)).toBeNaN();\n            expect(maskitoParseNumber(CHAR_EM_DASH)).toBeNaN();\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/tests/stringify-number-without-exp.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {stringifyNumberWithoutExp} from '../stringify-number-without-exp';\n\ndescribe('number converting to string without exponent', () => {\n    it('value with exponent and without fractional part and precision > 6', () => {\n        expect(stringifyNumberWithoutExp(1e-10)).toBe('0.0000000001');\n    });\n\n    it('value with exponent and fractional part and precision > 6', () => {\n        expect(stringifyNumberWithoutExp(1.23e-8)).toBe('0.0000000123');\n    });\n\n    it('negative value with exponent and fractional part and precision > 6', () => {\n        expect(stringifyNumberWithoutExp(-1.23e-8)).toBe('-0.0000000123');\n    });\n\n    it('integer value', () => {\n        expect(stringifyNumberWithoutExp(1)).toBe('1');\n    });\n\n    it('integer value with zeros', () => {\n        expect(stringifyNumberWithoutExp(100)).toBe('100');\n    });\n\n    it('fractional value without exponent', () => {\n        expect(stringifyNumberWithoutExp(0.111)).toBe('0.111');\n    });\n\n    it('negative integer value', () => {\n        expect(stringifyNumberWithoutExp(-100)).toBe('-100');\n    });\n\n    it('negative fractional value', () => {\n        expect(stringifyNumberWithoutExp(-1e-2)).toBe('-0.01');\n    });\n\n    it('fractional value with exponent and precision equals 4', () => {\n        expect(stringifyNumberWithoutExp(2.23e-2)).toBe('0.0223');\n    });\n\n    it('very small exponent that exceeds toFixed limit must expand manually', () => {\n        expect(stringifyNumberWithoutExp(102.282e-112)).toBe(\n            '0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000102282',\n        );\n    });\n\n    it('positive exponent should use full wide expansion', () => {\n        expect(stringifyNumberWithoutExp(1e25)).toBe('10000000000000000000000000');\n    });\n\n    it('zero', () => {\n        expect(stringifyNumberWithoutExp(0)).toBe('0');\n    });\n\n    it('negative zero formatted correctly', () => {\n        expect(stringifyNumberWithoutExp(-0)).toBe('0');\n    });\n\n    it('bigint basic', () => {\n        expect(stringifyNumberWithoutExp(123n)).toBe('123');\n    });\n\n    it('negative bigint', () => {\n        expect(stringifyNumberWithoutExp(-999999999999999999999n)).toBe(\n            '-999999999999999999999',\n        );\n    });\n\n    it('large negative exponent simple case', () => {\n        expect(stringifyNumberWithoutExp(5e-5)).toBe('0.00005');\n    });\n\n    it('decimal with many zeros inside exponent', () => {\n        expect(stringifyNumberWithoutExp(9.001e-4)).toBe('0.0009001');\n    });\n\n    it('positive exponent with fractional part', () => {\n        expect(stringifyNumberWithoutExp(3.14e5)).toBe('314000');\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/tests/stringify-number.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {\n    CHAR_EM_DASH,\n    CHAR_EN_DASH,\n    CHAR_HYPHEN,\n    CHAR_JP_HYPHEN,\n    CHAR_MINUS,\n} from '../../../../constants';\nimport type {MaskitoNumberParams} from '../../number-params';\nimport {maskitoStringifyNumber} from '../stringify-number';\n\ndescribe('maskitoStringifyNumber', () => {\n    describe('decimal separator is dot (default one)', () => {\n        it('thousand separator is space', () => {\n            expect(\n                maskitoStringifyNumber(1000000.42, {\n                    maximumFractionDigits: 2,\n                    decimalSeparator: '.',\n                    thousandSeparator: ' ',\n                }),\n            ).toBe('1 000 000.42');\n        });\n\n        it('thousand separator is hyphen', () => {\n            expect(\n                maskitoStringifyNumber(1000000.42, {\n                    maximumFractionDigits: 2,\n                    decimalSeparator: '.',\n                    thousandSeparator: '-',\n                }),\n            ).toBe('1-000-000.42');\n        });\n\n        it('thousand separator is empty string', () => {\n            expect(\n                maskitoStringifyNumber(1000000.42, {\n                    maximumFractionDigits: 2,\n                    thousandSeparator: '',\n                    decimalSeparator: '.',\n                }),\n            ).toBe('1000000.42');\n        });\n\n        it('empty decimal part & thousand separator is comma', () => {\n            expect(maskitoStringifyNumber(1000000, {thousandSeparator: ','})).toBe(\n                '1,000,000',\n            );\n        });\n\n        it('trailing decimal separator (minimumFractionDigits > maximumFractionDigits => maximumFractionDigits has more priority)', () => {\n            expect(\n                maskitoStringifyNumber(0, {\n                    maximumFractionDigits: 0,\n                    decimalSeparator: '.',\n                    minimumFractionDigits: 2,\n                }),\n            ).toBe('0');\n        });\n\n        it('trailing decimal separator maximumFractionDigits', () => {\n            expect(\n                maskitoStringifyNumber(0, {\n                    maximumFractionDigits: 2,\n                    decimalSeparator: '.',\n                    minimumFractionDigits: 2,\n                }),\n            ).toBe('0.00');\n        });\n\n        it('number with exponential notation', () => {\n            expect(maskitoStringifyNumber(0.000000012, {maximumFractionDigits: 10})).toBe(\n                '0.000000012',\n            );\n            expect(\n                maskitoStringifyNumber(1.2e-8, {maximumFractionDigits: 10, prefix: '$'}),\n            ).toBe('$0.000000012');\n\n            expect(maskitoStringifyNumber(2e24, {thousandSeparator: '_'})).toBe(\n                '2_000_000_000_000_000_000_000_000',\n            );\n        });\n    });\n\n    describe('decimal separator is comma', () => {\n        it('thousand separator is space', () => {\n            expect(\n                maskitoStringifyNumber(42111.42, {\n                    maximumFractionDigits: 2,\n                    decimalSeparator: ',',\n                    thousandSeparator: ' ',\n                }),\n            ).toBe('42 111,42');\n        });\n\n        it('thousand separator is hyphen', () => {\n            expect(\n                maskitoStringifyNumber(42111.42, {\n                    maximumFractionDigits: 2,\n                    decimalSeparator: ',',\n                    thousandSeparator: '-',\n                }),\n            ).toBe('42-111,42');\n        });\n\n        it('thousand separator is empty string', () => {\n            expect(\n                maskitoStringifyNumber(42111.42, {\n                    maximumFractionDigits: 2,\n                    decimalSeparator: ',',\n                    thousandSeparator: '',\n                }),\n            ).toBe('42111,42');\n        });\n\n        it('empty decimal part & thousand separator is dot', () => {\n            expect(\n                maskitoStringifyNumber(42111, {\n                    decimalSeparator: ',',\n                    thousandSeparator: '.',\n                }),\n            ).toBe('42.111');\n        });\n\n        it('trailing decimal separator (minimumFractionDigits > maximumFractionDigits => maximumFractionDigits has more priority)', () => {\n            expect(\n                maskitoStringifyNumber(42, {\n                    maximumFractionDigits: 0,\n                    decimalSeparator: ',',\n                    minimumFractionDigits: 2,\n                }),\n            ).toBe('42');\n        });\n\n        it('with decimal part', () => {\n            expect(\n                maskitoStringifyNumber(42.1, {\n                    decimalSeparator: ',',\n                    thousandSeparator: '.',\n                    maximumFractionDigits: 2,\n                }),\n            ).toBe('42,1');\n        });\n\n        it('zero-padded fraction part', () => {\n            expect(\n                maskitoStringifyNumber(42, {\n                    maximumFractionDigits: 2,\n                    decimalSeparator: ',',\n                    minimumFractionDigits: 2,\n                }),\n            ).toBe('42,00');\n        });\n    });\n\n    describe('negative numbers', () => {\n        describe('minus sign', () => {\n            it('can be minus', () => {\n                expect(maskitoStringifyNumber(-1, {minusSign: CHAR_MINUS})).toBe(\n                    `${CHAR_MINUS}1`,\n                );\n            });\n\n            it('can be hyphen', () => {\n                expect(\n                    maskitoStringifyNumber(-123456, {\n                        minusSign: CHAR_HYPHEN,\n                        thousandSeparator: ' ',\n                    }),\n                ).toBe(`${CHAR_HYPHEN}123 456`);\n            });\n\n            it('can be en-dash', () => {\n                expect(\n                    maskitoStringifyNumber(-123456789, {\n                        minusSign: CHAR_EN_DASH,\n                        thousandSeparator: ' ',\n                    }),\n                ).toBe(`${CHAR_EN_DASH}123 456 789`);\n            });\n\n            it('can be em-dash', () => {\n                expect(maskitoStringifyNumber(-42, {minusSign: CHAR_EM_DASH})).toBe(\n                    `${CHAR_EM_DASH}42`,\n                );\n            });\n\n            it('can be katakana-hiragana prolonged sound mark', () => {\n                expect(maskitoStringifyNumber(-42, {minusSign: CHAR_JP_HYPHEN})).toBe(\n                    `${CHAR_JP_HYPHEN}42`,\n                );\n            });\n        });\n\n        it('stringifies negative integer number when thousand separator is hyphen & minus sign is hyphen', () => {\n            expect(\n                maskitoStringifyNumber(-123456, {\n                    thousandSeparator: '-',\n                    minusSign: '-',\n                }),\n            ).toBe('-123-456');\n        });\n\n        it('stringifies negative number with decimal part', () => {\n            expect(maskitoStringifyNumber(-123.456, {maximumFractionDigits: 3})).toBe(\n                `${CHAR_MINUS}123.456`,\n            );\n        });\n    });\n\n    describe('Prefix & Postfix', () => {\n        it('stringifies number with only prefix', () => {\n            expect(maskitoStringifyNumber(42, {prefix: '$'})).toBe('$42');\n        });\n\n        it('stringifies number with only postfix', () => {\n            expect(maskitoStringifyNumber(42, {postfix: '%'})).toBe('42%');\n        });\n\n        it('stringifies number with both prefix and postfix', () => {\n            expect(\n                maskitoStringifyNumber(42, {\n                    prefix: '$',\n                    postfix: ' per day',\n                }),\n            ).toBe('$42 per day');\n        });\n\n        it('stringifies negative number with prefix', () => {\n            expect(maskitoStringifyNumber(-42, {prefix: '>'})).toBe(`>${CHAR_MINUS}42`);\n        });\n\n        describe('prefix/postfix includes point and space', () => {\n            it('stringifies INTEGER number with postfix \" lbs.\"', () => {\n                expect(maskitoStringifyNumber(42, {postfix: ' lbs.'})).toBe('42 lbs.');\n\n                expect(\n                    maskitoStringifyNumber(1000, {\n                        thousandSeparator: ' ',\n                        postfix: ' lbs.',\n                    }),\n                ).toBe('1 000 lbs.');\n            });\n\n            it('stringifies DECIMAL number with postfix \" lbs.\"', () => {\n                expect(\n                    maskitoStringifyNumber(0.42, {\n                        maximumFractionDigits: 2,\n                        postfix: ' lbs.',\n                    }),\n                ).toBe('0.42 lbs.');\n\n                expect(\n                    maskitoStringifyNumber(1000.42, {\n                        maximumFractionDigits: 2,\n                        thousandSeparator: ' ',\n                        postfix: ' lbs.',\n                    }),\n                ).toBe('1 000.42 lbs.');\n\n                expect(\n                    maskitoStringifyNumber(1000, {\n                        maximumFractionDigits: 0,\n                        minimumFractionDigits: 2,\n                        thousandSeparator: ' ',\n                        postfix: ' lbs.',\n                    }),\n                ).toBe('1 000 lbs.');\n            });\n\n            it('stringifies INTEGER number with prefix \"lbs. \"', () => {\n                expect(maskitoStringifyNumber(42, {prefix: 'lbs. '})).toBe('lbs. 42');\n\n                expect(\n                    maskitoStringifyNumber(1000, {\n                        thousandSeparator: ' ',\n                        prefix: 'lbs. ',\n                    }),\n                ).toBe('lbs. 1 000');\n            });\n\n            it('stringifies DECIMAL number with prefix \"lbs. \"', () => {\n                expect(\n                    maskitoStringifyNumber(0.42, {\n                        maximumFractionDigits: 2,\n                        prefix: 'lbs. ',\n                    }),\n                ).toBe('lbs. 0.42');\n\n                expect(\n                    maskitoStringifyNumber(1000.42, {\n                        maximumFractionDigits: 2,\n                        thousandSeparator: ' ',\n                        prefix: 'lbs. ',\n                    }),\n                ).toBe('lbs. 1 000.42');\n            });\n        });\n    });\n\n    describe('Minus is positioned before prefix', () => {\n        const params: MaskitoNumberParams = {\n            decimalSeparator: ',',\n            minusSign: CHAR_MINUS,\n            prefix: '$',\n            negativePattern: 'minusFirst',\n            maximumFractionDigits: 2,\n        };\n\n        it('-42 => -$42', () => {\n            expect(maskitoStringifyNumber(-42, params)).toBe(`${CHAR_MINUS}$42`);\n        });\n\n        it('-0.42 => -$0,42', () => {\n            expect(maskitoStringifyNumber(-0.42, params)).toBe(`${CHAR_MINUS}$0,42`);\n        });\n\n        it('0 => $0', () => {\n            expect(maskitoStringifyNumber(0, params)).toBe('$0');\n        });\n\n        it('NaN', () => {\n            expect(maskitoStringifyNumber(Number.NaN, params)).toBe('');\n        });\n    });\n\n    describe('Min and Max constraints', () => {\n        it('applies min constraint', () => {\n            expect(maskitoStringifyNumber(-10, {min: 0})).toBe('0');\n        });\n\n        it('applies max constraint', () => {\n            expect(maskitoStringifyNumber(1000, {max: 100})).toBe('100');\n        });\n\n        it('applies both min and max constraints', () => {\n            expect(\n                maskitoStringifyNumber(150, {\n                    min: 0,\n                    max: 100,\n                }),\n            ).toBe('100');\n\n            expect(\n                maskitoStringifyNumber(-50, {\n                    min: 0,\n                    max: 100,\n                }),\n            ).toBe('0');\n\n            expect(\n                maskitoStringifyNumber(50, {\n                    min: 0,\n                    max: 100,\n                }),\n            ).toBe('50');\n        });\n    });\n\n    describe('[maximumFractionDigits] handling', () => {\n        it('handles zero maximumFractionDigits correctly', () => {\n            expect(maskitoStringifyNumber(123.456, {maximumFractionDigits: 0})).toBe(\n                '123',\n            );\n        });\n\n        it('handles custom maximumFractionDigits correctly', () => {\n            expect(maskitoStringifyNumber(123.456, {maximumFractionDigits: 1})).toBe(\n                '123.4',\n            );\n\n            expect(maskitoStringifyNumber(123.456, {maximumFractionDigits: 4})).toBe(\n                '123.456',\n            );\n        });\n\n        it('handles zero padding correctly', () => {\n            expect(\n                maskitoStringifyNumber(123, {\n                    maximumFractionDigits: 2,\n                    minimumFractionDigits: 2,\n                }),\n            ).toBe('123.00');\n\n            expect(\n                maskitoStringifyNumber(123, {\n                    maximumFractionDigits: 0,\n                    minimumFractionDigits: 0,\n                }),\n            ).toBe('123');\n        });\n    });\n\n    describe('BigInt support', () => {\n        it('stringifies BigInt values larger than Number.MAX_SAFE_INTEGER', () => {\n            expect(\n                maskitoStringifyNumber(\n                    BigInt(String(Number.MAX_SAFE_INTEGER).repeat(2)),\n                    {thousandSeparator: ' '},\n                ),\n            ).toBe('90 071 992 547 409 919 007 199 254 740 991');\n        });\n\n        it('clamps values using bigint-powered min/max', () => {\n            expect(\n                maskitoStringifyNumber(100n, {\n                    min: -5n,\n                    max: 5n,\n                }),\n            ).toBe('5');\n\n            expect(\n                maskitoStringifyNumber(-100n, {\n                    min: -5n,\n                    max: 5n,\n                }),\n            ).toBe(`${CHAR_MINUS}5`);\n\n            expect(\n                maskitoStringifyNumber(10, {\n                    min: -5n,\n                    max: 5n,\n                }),\n            ).toBe('5');\n\n            expect(\n                maskitoStringifyNumber(10n, {\n                    min: -5,\n                    max: 5,\n                }),\n            ).toBe('5');\n        });\n\n        it('pads fractional part for bigint inputs', () => {\n            expect(\n                maskitoStringifyNumber(42n, {\n                    thousandSeparator: '',\n                    maximumFractionDigits: 2,\n                    minimumFractionDigits: 2,\n                }),\n            ).toBe('42.00');\n        });\n\n        it('works with prefix', () => {\n            expect(\n                maskitoStringifyNumber(1000000000000n, {\n                    prefix: '$',\n                    thousandSeparator: '_',\n                }),\n            ).toBe('$1_000_000_000_000');\n        });\n\n        it('works with postfix', () => {\n            expect(\n                maskitoStringifyNumber(-5000n, {\n                    postfix: ' pcs',\n                    thousandSeparator: '_',\n                }),\n            ).toBe(`${CHAR_MINUS}5_000 pcs`);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/tests/to-number-parts.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\nimport type {MaskitoNumberParams} from '@maskito/kit';\n\nimport {\n    CHAR_EM_DASH,\n    CHAR_EN_DASH,\n    CHAR_HYPHEN,\n    CHAR_JP_HYPHEN,\n    CHAR_MINUS,\n    DEFAULT_PSEUDO_MINUSES,\n} from '../../../../constants';\nimport {toNumberParts} from '../number-parts';\n\nconst DEFAULT_PARAMS = {\n    prefix: '',\n    postfix: '',\n    minusPseudoSigns: DEFAULT_PSEUDO_MINUSES,\n    decimalSeparator: '.',\n    minusSign: '-',\n    maximumFractionDigits: 0,\n    decimalPseudoSeparators: [','],\n} as const satisfies MaskitoNumberParams;\n\ndescribe('toNumberParts', () => {\n    [',', '.'].forEach((decimalSeparator) => {\n        describe(`decimalSeparator = ${decimalSeparator}`, () => {\n            const params = {\n                ...DEFAULT_PARAMS,\n                minusSign: '-',\n                maximumFractionDigits: 2,\n                decimalSeparator,\n            } as const satisfies MaskitoNumberParams;\n\n            it('empty string => empty parts', () => {\n                expect(toNumberParts('', params)).toEqual({\n                    minus: '',\n                    integerPart: '',\n                    decimalPart: '',\n                    decimalSeparator: '',\n                    prefix: '',\n                    postfix: '',\n                });\n            });\n\n            it(`123${decimalSeparator}45 => {minus: \"\", integerPart: \"123\", decimalPart: \"45\"}`, () => {\n                expect(toNumberParts(`123${decimalSeparator}45`, params)).toEqual({\n                    minus: '',\n                    integerPart: '123',\n                    decimalPart: '45',\n                    decimalSeparator,\n                    prefix: '',\n                    postfix: '',\n                });\n            });\n\n            it(`-123${decimalSeparator}45 => {minus: \"-\", integerPart: \"123\", decimalPart: \"45\"}`, () => {\n                expect(toNumberParts(`-123${decimalSeparator}45`, params)).toEqual({\n                    minus: '-',\n                    integerPart: '123',\n                    decimalPart: '45',\n                    decimalSeparator,\n                    prefix: '',\n                    postfix: '',\n                });\n            });\n\n            it('123 => {minus: \"\", integerPart: \"123\", decimalPart: \"\"}', () => {\n                expect(toNumberParts('123', params)).toEqual({\n                    minus: '',\n                    integerPart: '123',\n                    decimalPart: '',\n                    decimalSeparator: '',\n                    prefix: '',\n                    postfix: '',\n                });\n            });\n\n            it('-123 => {minus: \"-\", integerPart: \"123\", decimalPart: \"\"}', () => {\n                expect(toNumberParts('-123', params)).toEqual({\n                    minus: '-',\n                    integerPart: '123',\n                    decimalPart: '',\n                    decimalSeparator: '',\n                    prefix: '',\n                    postfix: '',\n                });\n            });\n\n            it(`${decimalSeparator}45 => {minus: \"\", integerPart: \"\", decimalPart: \"45\"}`, () => {\n                expect(toNumberParts(`${decimalSeparator}45`, params)).toEqual({\n                    minus: '',\n                    integerPart: '',\n                    decimalPart: '45',\n                    decimalSeparator,\n                    prefix: '',\n                    postfix: '',\n                });\n            });\n\n            it(`-${decimalSeparator}45 => {minus: \"-\", integerPart: \"\", decimalPart: \"45\"}`, () => {\n                expect(toNumberParts(`-${decimalSeparator}45`, params)).toEqual({\n                    minus: '-',\n                    integerPart: '',\n                    decimalPart: '45',\n                    decimalSeparator,\n                    prefix: '',\n                    postfix: '',\n                });\n            });\n\n            it('- => {minus: \"-\", integerPart: \"\", decimalPart: \"\"}', () => {\n                expect(toNumberParts('-', params)).toEqual({\n                    minus: '-',\n                    integerPart: '',\n                    decimalPart: '',\n                    decimalSeparator: '',\n                    prefix: '',\n                    postfix: '',\n                });\n            });\n        });\n    });\n\n    describe('different minus signs', () => {\n        [CHAR_MINUS, CHAR_HYPHEN, CHAR_EN_DASH, CHAR_EM_DASH, CHAR_JP_HYPHEN].forEach(\n            (minus) => {\n                const params = {\n                    ...DEFAULT_PARAMS,\n                    minusSign: minus,\n                    maximumFractionDigits: 2,\n                    decimalSeparator: '.',\n                    decimalPseudoSeparators: ['б'],\n                    thousandSeparator: ',',\n                } as const satisfies MaskitoNumberParams;\n\n                it(minus, () => {\n                    expect(toNumberParts(`${minus}1,234,567.89`, params)).toEqual({\n                        minus,\n                        integerPart: '1,234,567',\n                        decimalPart: '89',\n                        decimalSeparator: '.',\n                        prefix: '',\n                        postfix: '',\n                    });\n                });\n            },\n        );\n    });\n\n    describe('thousand separator (e.g. underscore) is a part of integer', () => {\n        const thousandSeparator = '_';\n\n        const params = {\n            ...DEFAULT_PARAMS,\n            minusSign: '-',\n            thousandSeparator,\n            maximumFractionDigits: 2,\n            decimalSeparator: '.',\n        } as const satisfies MaskitoNumberParams;\n\n        it('only thousand separator sign', () => {\n            expect(toNumberParts(thousandSeparator, params)).toEqual({\n                minus: '',\n                integerPart: '_',\n                decimalPart: '',\n                decimalSeparator: '',\n                prefix: '',\n                postfix: '',\n            });\n        });\n\n        it('only minus and thousand separator signs', () => {\n            expect(toNumberParts(`-${thousandSeparator}`, params)).toEqual({\n                minus: '-',\n                integerPart: thousandSeparator,\n                decimalPart: '',\n                decimalSeparator: '',\n                prefix: '',\n                postfix: '',\n            });\n        });\n\n        it(`-1${thousandSeparator}234.45 => {minus: \"-\", integerPart: \"1${thousandSeparator}234\", decimalPart: \"45\"}`, () => {\n            expect(toNumberParts(`-1${thousandSeparator}234.45`, params)).toEqual({\n                minus: '-',\n                integerPart: `1${thousandSeparator}234`,\n                decimalPart: '45',\n                decimalSeparator: '.',\n                prefix: '',\n                postfix: '',\n            });\n        });\n\n        it(`-${thousandSeparator}234.45 => {minus: \"-\", integerPart: \"${thousandSeparator}234\", decimalPart: \"45\"}`, () => {\n            expect(toNumberParts(`-${thousandSeparator}234.45`, params)).toEqual({\n                minus: '-',\n                integerPart: `${thousandSeparator}234`,\n                decimalPart: '45',\n                decimalSeparator: '.',\n                prefix: '',\n                postfix: '',\n            });\n        });\n    });\n\n    describe('multi-character affixes', () => {\n        describe('prefix = EUR', () => {\n            const params = {\n                ...DEFAULT_PARAMS,\n                prefix: 'EUR',\n            } as const satisfies MaskitoNumberParams;\n\n            ['E', 'U', 'R'].forEach((char) => {\n                it(`type single character ${char}`, () => {\n                    expect(toNumberParts(char, params)).toEqual({\n                        minus: '',\n                        integerPart: '',\n                        decimalPart: '',\n                        decimalSeparator: '',\n                        prefix: char,\n                        postfix: '',\n                    });\n                });\n            });\n        });\n\n        describe('postfix = руб.', () => {\n            const params = {\n                ...DEFAULT_PARAMS,\n                postfix: 'руб.', // ends with point!\n                decimalSeparator: '.', // point too!\n                maximumFractionDigits: 2,\n            } as const satisfies MaskitoNumberParams;\n\n            ['р', 'у', 'б'].forEach((char) => {\n                it(`type 1 + single character ${char}`, () => {\n                    expect(toNumberParts(`1${char}`, params)).toEqual({\n                        minus: '',\n                        integerPart: '1',\n                        decimalPart: '',\n                        decimalSeparator: '',\n                        prefix: '',\n                        postfix: char,\n                    });\n                });\n            });\n\n            it('type 1 + point', () => {\n                expect(toNumberParts('1.', params)).toEqual({\n                    minus: '',\n                    integerPart: '1',\n                    decimalPart: '',\n                    decimalSeparator: '.',\n                    prefix: '',\n                    postfix: '',\n                });\n            });\n\n            it('100руб.', () => {\n                expect(toNumberParts('100руб.', params)).toEqual({\n                    minus: '',\n                    integerPart: '100',\n                    decimalPart: '',\n                    decimalSeparator: '',\n                    prefix: '',\n                    postfix: 'руб.',\n                });\n            });\n        });\n    });\n\n    it('prefix ends with point & value starts with decimal point too', () => {\n        expect(\n            toNumberParts('.42', {\n                ...DEFAULT_PARAMS,\n                decimalSeparator: '.',\n                prefix: 'lbs.',\n                maximumFractionDigits: 2,\n            }),\n        ).toEqual({\n            minus: '',\n            integerPart: '',\n            decimalPart: '42',\n            decimalSeparator: '.',\n            prefix: '',\n            postfix: '',\n        });\n    });\n\n    it('postfix contains point & value ends with decimal point too', () => {\n        expect(\n            toNumberParts('123.lbs.', {\n                ...DEFAULT_PARAMS,\n                maximumFractionDigits: 2,\n                postfix: 'lbs.',\n            }),\n        ).toEqual({\n            minus: '',\n            integerPart: '123',\n            decimalPart: '',\n            decimalSeparator: '.',\n            prefix: '',\n            postfix: 'lbs.',\n        });\n    });\n\n    describe('postfix starts with point | [postfix]=\".000 km\" & [maximumFractionDigits]=\"0\"', () => {\n        const postfix = '.000 km';\n        const params = {\n            ...DEFAULT_PARAMS,\n            maximumFractionDigits: 0,\n            postfix,\n        } as const;\n\n        it('1.000 km', () => {\n            expect(toNumberParts('1.000 km', params)).toEqual({\n                minus: '',\n                integerPart: '1',\n                decimalPart: '',\n                decimalSeparator: '',\n                prefix: '',\n                postfix: '.000 km',\n            });\n        });\n\n        it('.000 km', () => {\n            expect(toNumberParts('.000 km', params)).toEqual({\n                minus: '',\n                integerPart: '',\n                decimalPart: '',\n                decimalSeparator: '',\n                prefix: '',\n                postfix: '.000 km',\n            });\n        });\n\n        it('1000.', () => {\n            expect(toNumberParts('1000.', params)).toEqual({\n                minus: '',\n                integerPart: '1000',\n                decimalPart: '',\n                decimalSeparator: '',\n                prefix: '',\n                postfix: '.',\n            });\n        });\n\n        it('100.000 km', () => {\n            expect(toNumberParts('100.000 km', params)).toEqual({\n                minus: '',\n                integerPart: '100',\n                decimalPart: '',\n                decimalSeparator: '',\n                prefix: '',\n                postfix: '.000 km',\n            });\n        });\n\n        it('100 (zeroes can be both incomplete postfix & integer part => for ambiguous case regard it as integer part)', () => {\n            expect(toNumberParts('100', params)).toEqual({\n                minus: '',\n                integerPart: '100',\n                decimalPart: '',\n                decimalSeparator: '',\n                prefix: '',\n                postfix: '',\n            });\n        });\n    });\n\n    describe('prefix & minus can be swapped', () => {\n        it('>-123 => {prefix: \">\", minus: \"-\"}', () => {\n            expect(\n                toNumberParts('>-123', {\n                    ...DEFAULT_PARAMS,\n                    prefix: '>',\n                }),\n            ).toEqual({\n                minus: '-',\n                integerPart: '123',\n                decimalPart: '',\n                decimalSeparator: '',\n                prefix: '>',\n                postfix: '',\n            });\n        });\n\n        it('-$123 => {prefix: \"$\", minus: \"-\", negativePattern: \"minusFirst\"}', () => {\n            expect(\n                toNumberParts('-$123', {\n                    ...DEFAULT_PARAMS,\n                    prefix: '$',\n                }),\n            ).toEqual({\n                minus: '-',\n                integerPart: '123',\n                decimalPart: '',\n                decimalSeparator: '',\n                prefix: '$',\n                postfix: '',\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/tests/validate-decimal-pseudo-separators.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {DEFAULT_DECIMAL_PSEUDO_SEPARATORS} from '../../../../constants';\nimport {validateDecimalPseudoSeparators} from '../validate-decimal-pseudo-separators';\n\ndescribe('validate decimal pseudo separators or return default', () => {\n    it('should return no empty array if decimalPseudoSeparators === `undefined`', () => {\n        expect(\n            validateDecimalPseudoSeparators({\n                decimalSeparator: ',',\n                thousandSeparator: ' ',\n            }),\n        ).toEqual(DEFAULT_DECIMAL_PSEUDO_SEPARATORS.filter((char) => char !== ','));\n    });\n\n    it('should exclude decimalSeparator and thousandSeparator from decimalPseudoSeparators', () => {\n        const decimalPseudoSeparators = [',', '.', 'a', 'b'];\n        const decimalSeparator = 'a';\n        const thousandSeparator = 'b';\n\n        expect(\n            validateDecimalPseudoSeparators({\n                decimalSeparator,\n                thousandSeparator,\n                decimalPseudoSeparators,\n            }),\n        ).toEqual([',', '.']);\n    });\n\n    it('should return original decimalPseudoSeparators', () => {\n        const decimalPseudoSeparators = [',', 'б', 'ю'];\n        const decimalSeparator = '.';\n        const thousandSeparator = ' ';\n\n        expect(\n            validateDecimalPseudoSeparators({\n                decimalSeparator,\n                thousandSeparator,\n                decimalPseudoSeparators,\n            }),\n        ).toEqual(decimalPseudoSeparators);\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/validate-decimal-pseudo-separators.ts",
    "content": "import {DEFAULT_DECIMAL_PSEUDO_SEPARATORS} from '../../../constants';\n\nexport function validateDecimalPseudoSeparators({\n    decimalSeparator,\n    thousandSeparator,\n    decimalPseudoSeparators = DEFAULT_DECIMAL_PSEUDO_SEPARATORS,\n}: {\n    decimalSeparator: string;\n    thousandSeparator: string;\n    decimalPseudoSeparators?: readonly string[];\n}): string[] {\n    return decimalPseudoSeparators.filter(\n        (char) => char !== thousandSeparator && char !== decimalSeparator,\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/number/utils/with-number-defaults.ts",
    "content": "import {\n    CHAR_MINUS,\n    CHAR_NO_BREAK_SPACE,\n    CHAR_ZERO_WIDTH_SPACE,\n    DEFAULT_PSEUDO_MINUSES,\n} from '../../../constants';\nimport {type MaskitoNumberParams} from '../number-params';\nimport {validateDecimalPseudoSeparators} from './validate-decimal-pseudo-separators';\n\nexport function withNumberDefaults({\n    max = Infinity,\n    min = -Infinity,\n    thousandSeparator = CHAR_NO_BREAK_SPACE,\n    thousandSeparatorPattern = (x) => x.match(/\\d{1,3}(?=(?:\\d{3})*$)/g) ?? [],\n    decimalSeparator = '.',\n    decimalPseudoSeparators: unsafeDecimalPseudoSeparators,\n    prefix = '',\n    postfix = '',\n    minusSign = CHAR_MINUS,\n    minusPseudoSigns = DEFAULT_PSEUDO_MINUSES.filter(\n        (char) =>\n            char !== thousandSeparator && char !== decimalSeparator && char !== minusSign,\n    ),\n    maximumFractionDigits = 0,\n    minimumFractionDigits = 0,\n    negativePattern = 'prefixFirst',\n}: MaskitoNumberParams = {}): Required<MaskitoNumberParams> {\n    const decimalPseudoSeparators = validateDecimalPseudoSeparators({\n        decimalSeparator,\n        thousandSeparator,\n        decimalPseudoSeparators: unsafeDecimalPseudoSeparators,\n    });\n\n    return {\n        max,\n        min,\n        thousandSeparator,\n        thousandSeparatorPattern,\n        postfix,\n        minusSign,\n        minusPseudoSigns,\n        maximumFractionDigits,\n        decimalPseudoSeparators,\n        negativePattern,\n        decimalSeparator:\n            maximumFractionDigits <= 0 && decimalSeparator === thousandSeparator\n                ? ''\n                : decimalSeparator,\n        prefix:\n            prefix.endsWith(decimalSeparator) && maximumFractionDigits > 0\n                ? `${prefix}${CHAR_ZERO_WIDTH_SPACE}`\n                : prefix,\n        minimumFractionDigits: Math.min(minimumFractionDigits, maximumFractionDigits),\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/time/index.ts",
    "content": "export * from './time-mask';\nexport * from './time-params';\nexport * from './utils';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/time/time-mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\n\nimport {\n    DEFAULT_TIME_SEGMENT_MAX_VALUES,\n    DEFAULT_TIME_SEGMENT_MIN_VALUES,\n} from '../../constants';\nimport {\n    createMeridiemSteppingPlugin,\n    createTimeSegmentsSteppingPlugin,\n} from '../../plugins';\nimport {\n    createColonConvertPreprocessor,\n    createFullWidthToHalfWidthPreprocessor,\n    createInvalidTimeSegmentInsertionPreprocessor,\n    createMeridiemPostprocessor,\n    createMeridiemPreprocessor,\n    createZeroPlaceholdersPreprocessor,\n    maskitoPostfixPostprocessorGenerator,\n    maskitoPrefixPostprocessorGenerator,\n} from '../../processors';\nimport type {MaskitoTimeSegments} from '../../types';\nimport {createTimeMaskExpression, enrichTimeSegmentsWithZeroes} from '../../utils/time';\nimport type {MaskitoTimeParams} from './time-params';\n\nexport function maskitoTimeOptionsGenerator({\n    mode,\n    timeSegmentMaxValues = {},\n    timeSegmentMinValues = {},\n    step = 0,\n    prefix = '',\n    postfix = '',\n}: MaskitoTimeParams): Required<MaskitoOptions> {\n    const hasMeridiem = mode.includes('AA');\n    const enrichedTimeSegmentMaxValues: MaskitoTimeSegments<number> = {\n        ...DEFAULT_TIME_SEGMENT_MAX_VALUES,\n        ...(hasMeridiem ? {hours: 12} : {}),\n        ...timeSegmentMaxValues,\n    };\n    const enrichedTimeSegmentMinValues: MaskitoTimeSegments<number> = {\n        ...DEFAULT_TIME_SEGMENT_MIN_VALUES,\n        ...(hasMeridiem ? {hours: 1} : {}),\n        ...timeSegmentMinValues,\n    };\n    const maskExpression = [...prefix, ...createTimeMaskExpression(mode)];\n\n    return {\n        mask: postfix\n            ? ({value}) => cutExpression(maskExpression, value).concat(...postfix)\n            : maskExpression,\n        preprocessors: [\n            createFullWidthToHalfWidthPreprocessor(),\n            createColonConvertPreprocessor(),\n            createZeroPlaceholdersPreprocessor(postfix),\n            createMeridiemPreprocessor(mode),\n            createInvalidTimeSegmentInsertionPreprocessor({\n                timeMode: mode,\n                timeSegmentMinValues: enrichedTimeSegmentMinValues,\n                timeSegmentMaxValues: enrichedTimeSegmentMaxValues,\n            }),\n        ],\n        postprocessors: [\n            createMeridiemPostprocessor(mode),\n            (elementState) =>\n                enrichTimeSegmentsWithZeroes(elementState, {\n                    mode,\n                    timeSegmentMaxValues: enrichedTimeSegmentMaxValues,\n                }),\n            maskitoPrefixPostprocessorGenerator(prefix),\n            maskitoPostfixPostprocessorGenerator(postfix),\n        ],\n        plugins: [\n            createTimeSegmentsSteppingPlugin({\n                fullMode: mode,\n                step,\n                timeSegmentMinValues: enrichedTimeSegmentMinValues,\n                timeSegmentMaxValues: enrichedTimeSegmentMaxValues,\n            }),\n            createMeridiemSteppingPlugin(mode.indexOf('AA')),\n        ],\n        overwriteMode: 'replace',\n    };\n}\n\n/**\n * Without cutting, the mask expression removes postfix on the last digit deletion\n * ___\n * Case 1 (static pattern mask expression)\n * Mask expression is [/\\d/, /\\d/, ':', /\\d/, /\\d/, ' left']\n * 12:34| left => Press Backspace => 12:3|\n * Mask correctly removes postfix because it's fixed characters after not yet inserted 4th digit.\n * ___\n * Case 2 (dynamic pattern mask expression)\n * Mask expression is [/\\d/, /\\d/, ':', /\\d/, /\\d/, ' left'] & textfield contains `12:34 left`\n * 12:34| left => Press Backspace => Mask expression becomes [/\\d/, /\\d/, ':', /\\d/, ' left']  => 12:3| left\n * Mask correctly does not remove postfix because it's trailing fixed characters\n * and all non-fixed characters were already inserted.\n */\nfunction cutExpression(\n    expression: Array<RegExp | string>,\n    value: string,\n): Array<RegExp | string> {\n    let digitsCount =\n        Math.min(\n            value.replaceAll(/\\D/g, '').length,\n            expression.filter((x) => typeof x !== 'string').length,\n        ) || 1;\n    const afterLastDigit =\n        expression.findIndex((x) => typeof x !== 'string' && !--digitsCount) + 1;\n\n    return expression.slice(0, afterLastDigit);\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/time/time-params.ts",
    "content": "import type {MaskitoTimeMode, MaskitoTimeSegments} from '../../types';\n\nexport interface MaskitoTimeParams {\n    readonly mode: MaskitoTimeMode;\n    readonly timeSegmentMaxValues?: Partial<MaskitoTimeSegments<number>>;\n    readonly timeSegmentMinValues?: Partial<MaskitoTimeSegments<number>>;\n    readonly step?: number;\n    readonly prefix?: string;\n    readonly postfix?: string;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/time/utils/index.ts",
    "content": "export * from './parse-time';\nexport * from './stringify-time';\n"
  },
  {
    "path": "projects/kit/src/lib/masks/time/utils/parse-time.ts",
    "content": "import {DEFAULT_TIME_SEGMENT_MAX_VALUES} from '../../../constants';\nimport type {MaskitoTimeSegments} from '../../../types';\nimport {padEndTimeSegments, parseTimeString} from '../../../utils/time';\nimport type {MaskitoTimeParams} from '../time-params';\n\n/**\n * Converts a formatted time string to milliseconds based on the given `options.mode`.\n *\n * @param maskedTime a formatted time string by {@link maskitoTimeOptionsGenerator} or {@link maskitoStringifyTime}\n * @param params\n */\nexport function maskitoParseTime(\n    maskedTime: string,\n    {mode, timeSegmentMaxValues = {}}: MaskitoTimeParams,\n): number {\n    const maxValues: MaskitoTimeSegments<number> = {\n        ...DEFAULT_TIME_SEGMENT_MAX_VALUES,\n        ...timeSegmentMaxValues,\n    };\n\n    const msInSecond = maxValues.milliseconds + 1;\n    const msInMinute = (maxValues.seconds + 1) * msInSecond;\n    const msInHour = (maxValues.minutes + 1) * msInMinute;\n\n    const parsedTime = padEndTimeSegments(parseTimeString(maskedTime, mode));\n    let hours = Number(parsedTime.hours ?? '');\n\n    if (mode.includes('AA') && Number.isFinite(hours)) {\n        if (maskedTime.includes('PM')) {\n            hours = hours < 12 ? hours + 12 : hours;\n        } else {\n            hours = hours === 12 ? 0 : hours;\n        }\n    }\n\n    return (\n        hours * msInHour +\n        Number(parsedTime.minutes ?? '') * msInMinute +\n        Number(parsedTime.seconds ?? '') * msInSecond +\n        Number(parsedTime.milliseconds ?? '')\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/time/utils/stringify-time.ts",
    "content": "import {DEFAULT_TIME_SEGMENT_MAX_VALUES} from '../../../constants';\nimport type {MaskitoTimeSegments} from '../../../types';\nimport {padStartTimeSegments} from '../../../utils/time';\nimport type {MaskitoTimeParams} from '../time-params';\n\n/**\n * Converts milliseconds to a formatted time string based on the given `options.mode`.\n *\n * @param milliseconds unsigned integer milliseconds\n * @param params\n */\nexport function maskitoStringifyTime(\n    milliseconds: number,\n    {mode, timeSegmentMaxValues = {}}: MaskitoTimeParams,\n): string {\n    const maxValues: MaskitoTimeSegments<number> = {\n        ...DEFAULT_TIME_SEGMENT_MAX_VALUES,\n        ...timeSegmentMaxValues,\n    };\n\n    const hasMeridiem = mode.includes('AA');\n    const msInSecond = maxValues.milliseconds + 1;\n    const msInMinute = (maxValues.seconds + 1) * msInSecond;\n    const msInHour = (maxValues.minutes + 1) * msInMinute;\n\n    const hours = Math.trunc(milliseconds / msInHour);\n\n    milliseconds -= hours * msInHour;\n\n    const minutes = Math.trunc(milliseconds / msInMinute);\n\n    milliseconds -= minutes * msInMinute;\n\n    const seconds = Math.trunc(milliseconds / msInSecond);\n\n    milliseconds -= seconds * msInSecond;\n\n    const result = padStartTimeSegments({\n        hours: hasMeridiem ? hours % 12 || 12 : hours,\n        minutes,\n        seconds,\n        milliseconds,\n    });\n\n    return mode\n        .replaceAll(/H+/g, result.hours)\n        .replaceAll('MSS', result.milliseconds)\n        .replaceAll(/M+/g, result.minutes)\n        .replaceAll(/S+/g, result.seconds)\n        .replace('AA', hours >= 12 ? 'PM' : 'AM');\n}\n"
  },
  {
    "path": "projects/kit/src/lib/masks/time/utils/tests/parse-time.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\nimport {maskitoParseTime, type MaskitoTimeMode} from '@maskito/kit';\n\ndescribe('maskitoParseTime', () => {\n    const testCases = new Map<MaskitoTimeMode, Array<{text: string; ms: number}>>([\n        [\n            'HH:MM:SS.MSS',\n            [\n                {text: '', ms: 0},\n                {text: '00:00:00.000', ms: 0},\n\n                {text: '1', ms: 36000000},\n                {text: '10', ms: 36000000},\n                {text: '12', ms: 43200000},\n                {text: '12:', ms: 43200000},\n                {text: '12:3', ms: 45000000},\n                {text: '12:30', ms: 45000000},\n                {text: '12:34', ms: 45240000},\n                {text: '12:34:', ms: 45240000},\n                {text: '12:34:5', ms: 45290000},\n                {text: '12:34:50', ms: 45290000},\n                {text: '12:34:56', ms: 45296000},\n                {text: '12:34:56.', ms: 45296000},\n                {text: '12:34:56.7', ms: 45296700},\n                {text: '12:34:56.70', ms: 45296700},\n                {text: '12:34:56.700', ms: 45296700},\n                {text: '12:34:56.78', ms: 45296780},\n                {text: '12:34:56.780', ms: 45296780},\n                {text: '12:34:56.789', ms: 45296789},\n\n                {text: '23:59:59.999', ms: 86399999},\n            ],\n        ],\n        [\n            'HH:MM:SS.MSS AA',\n            [\n                {text: '', ms: 0},\n                {text: '12:00:00.000 AM', ms: 0},\n                {text: '01:00:00.000 AM', ms: 3600000},\n                {text: '11:59:59.999 AM', ms: 43199999},\n                {text: '12:00:00.000 PM', ms: 43200000},\n                {text: '01:00:00.000 PM', ms: 46800000},\n                {text: '11:59:59.999 PM', ms: 86399999},\n            ],\n        ],\n        [\n            'HH:MM:SS',\n            [\n                {text: '', ms: 0},\n                {text: '00:00:00', ms: 0},\n\n                {text: '1', ms: 36000000},\n                {text: '10', ms: 36000000},\n                {text: '12', ms: 43200000},\n                {text: '12:', ms: 43200000},\n                {text: '12:3', ms: 45000000},\n                {text: '12:30', ms: 45000000},\n                {text: '12:34', ms: 45240000},\n                {text: '12:34:', ms: 45240000},\n                {text: '12:34:5', ms: 45290000},\n                {text: '12:34:50', ms: 45290000},\n                {text: '12:34:56', ms: 45296000},\n\n                {text: '23:59:59', ms: 86399000},\n            ],\n        ],\n        [\n            'HH:MM:SS AA',\n            [\n                {text: '', ms: 0},\n                {text: '12:00:00 AM', ms: 0},\n                {text: '01:00:00 AM', ms: 3600000},\n                {text: '11:59:59 AM', ms: 43199000},\n                {text: '12:00:00 PM', ms: 43200000},\n                {text: '01:00:00 PM', ms: 46800000},\n                {text: '11:59:59 PM', ms: 86399000},\n            ],\n        ],\n        [\n            'HH:MM',\n            [\n                {text: '', ms: 0},\n                {text: '00:00', ms: 0},\n\n                {text: '1', ms: 36000000},\n                {text: '10', ms: 36000000},\n                {text: '12', ms: 43200000},\n                {text: '12:', ms: 43200000},\n                {text: '12:3', ms: 45000000},\n                {text: '12:30', ms: 45000000},\n                {text: '12:34', ms: 45240000},\n\n                {text: '23:59', ms: 86340000},\n            ],\n        ],\n        [\n            'HH:MM AA',\n            [\n                {text: '', ms: 0},\n                {text: '12:00 AM', ms: 0},\n                {text: '01:00 AM', ms: 3600000},\n                {text: '11:59 AM', ms: 43140000},\n                {text: '12:00 PM', ms: 43200000},\n                {text: '01:00 PM', ms: 46800000},\n                {text: '11:59 PM', ms: 86340000},\n            ],\n        ],\n        [\n            'HH',\n            [\n                {text: '', ms: 0},\n                {text: '00', ms: 0},\n\n                {text: '1', ms: 36000000},\n                {text: '10', ms: 36000000},\n                {text: '12', ms: 43200000},\n\n                {text: '23', ms: 82800000},\n            ],\n        ],\n        [\n            'HH AA',\n            [\n                {text: '', ms: 0},\n                {text: '12 AM', ms: 0},\n                {text: '01 AM', ms: 3600000},\n                {text: '11 AM', ms: 39600000},\n                {text: '12 PM', ms: 43200000},\n                {text: '01 PM', ms: 46800000},\n                {text: '11 PM', ms: 82800000},\n            ],\n        ],\n        [\n            'MM:SS.MSS',\n            [\n                {text: '', ms: 0},\n                {text: '00:00.000', ms: 0},\n\n                {text: '1', ms: 600000},\n                {text: '10', ms: 600000},\n                {text: '12', ms: 720000},\n                {text: '12.', ms: 720000},\n                {text: '12:3', ms: 750000},\n                {text: '12:30', ms: 750000},\n                {text: '12:34', ms: 754000},\n                {text: '12:34.', ms: 754000},\n                {text: '12:34.5', ms: 754500},\n                {text: '12:34.50', ms: 754500},\n                {text: '12:34.500', ms: 754500},\n                {text: '12:34.56', ms: 754560},\n                {text: '12:34.560', ms: 754560},\n                {text: '12:34.567', ms: 754567},\n\n                {text: '59:59.999', ms: 3599999},\n            ],\n        ],\n        [\n            'MM:SS',\n            [\n                {text: '', ms: 0},\n                {text: '1', ms: 600000},\n                {text: '10', ms: 600000},\n                {text: '12', ms: 720000},\n                {text: '12:', ms: 720000},\n                {text: '12:3', ms: 750000},\n                {text: '12:30', ms: 750000},\n            ],\n        ],\n        [\n            'SS.MSS',\n            [\n                {text: '', ms: 0},\n                {text: '00.000', ms: 0},\n\n                {text: '1', ms: 10000},\n                {text: '10', ms: 10000},\n                {text: '12', ms: 12000},\n                {text: '12.', ms: 12000},\n                {text: '12.3', ms: 12300},\n                {text: '12.30', ms: 12300},\n                {text: '12.300', ms: 12300},\n                {text: '12.34', ms: 12340},\n                {text: '12.340', ms: 12340},\n                {text: '12.345', ms: 12345},\n\n                {text: '59.999', ms: 59999},\n            ],\n        ],\n    ]);\n\n    testCases.forEach((cases, mode) => {\n        describe(`mode ${mode}`, () => {\n            cases.forEach(({text, ms}) => {\n                it(`'${text}' => ${ms}ms`, () => {\n                    expect(maskitoParseTime(text, {mode})).toBe(ms);\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/masks/time/utils/tests/stringify-time.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\nimport {maskitoStringifyTime, type MaskitoTimeMode} from '@maskito/kit';\n\ndescribe('maskitoStringifyTime', () => {\n    const testCases = new Map<MaskitoTimeMode, Array<{ms: number; text: string}>>([\n        [\n            'HH:MM:SS.MSS',\n            [\n                {ms: 0, text: '00:00:00.000'},\n                {ms: 3661001, text: '01:01:01.001'},\n                {ms: 45296789, text: '12:34:56.789'},\n                {ms: 86399999, text: '23:59:59.999'},\n            ],\n        ],\n        [\n            'HH:MM:SS.MSS AA',\n            [\n                {ms: 0, text: '12:00:00.000 AM'},\n                {ms: 3600000, text: '01:00:00.000 AM'},\n                {ms: 43199999, text: '11:59:59.999 AM'},\n                {ms: 43200000, text: '12:00:00.000 PM'},\n                {ms: 46800000, text: '01:00:00.000 PM'},\n                {ms: 86399999, text: '11:59:59.999 PM'},\n            ],\n        ],\n        [\n            'HH:MM:SS',\n            [\n                {ms: 0, text: '00:00:00'},\n                {ms: 3661000, text: '01:01:01'},\n                {ms: 10920000, text: '03:02:00'},\n                {ms: 45296000, text: '12:34:56'},\n                {ms: 86399000, text: '23:59:59'},\n            ],\n        ],\n        [\n            'HH:MM:SS AA',\n            [\n                {ms: 0, text: '12:00:00 AM'},\n                {ms: 3600000, text: '01:00:00 AM'},\n                {ms: 43199000, text: '11:59:59 AM'},\n                {ms: 43200000, text: '12:00:00 PM'},\n                {ms: 46800000, text: '01:00:00 PM'},\n                {ms: 86399000, text: '11:59:59 PM'},\n            ],\n        ],\n        [\n            'HH:MM',\n            [\n                {ms: 0, text: '00:00'},\n                {ms: 3660000, text: '01:01'},\n                {ms: 45240000, text: '12:34'},\n                {ms: 86340000, text: '23:59'},\n            ],\n        ],\n        [\n            'HH:MM AA',\n            [\n                {ms: 0, text: '12:00 AM'},\n                {ms: 3600000, text: '01:00 AM'},\n                {ms: 43140000, text: '11:59 AM'},\n                {ms: 43200000, text: '12:00 PM'},\n                {ms: 46800000, text: '01:00 PM'},\n                {ms: 86340000, text: '11:59 PM'},\n            ],\n        ],\n        [\n            'HH',\n            [\n                {ms: 0, text: '00'},\n                {ms: 3600000, text: '01'},\n                {ms: 43200000, text: '12'},\n                {ms: 82800000, text: '23'},\n            ],\n        ],\n        [\n            'HH AA',\n            [\n                {ms: 0, text: '12 AM'},\n                {ms: 3600000, text: '01 AM'},\n                {ms: 39600000, text: '11 AM'},\n                {ms: 43200000, text: '12 PM'},\n                {ms: 46800000, text: '01 PM'},\n                {ms: 82800000, text: '11 PM'},\n            ],\n        ],\n        [\n            'MM:SS.MSS',\n            [\n                {ms: 0, text: '00:00.000'},\n                {ms: 61001, text: '01:01.001'},\n                {ms: 754567, text: '12:34.567'},\n                {ms: 3599999, text: '59:59.999'},\n            ],\n        ],\n        [\n            'MM:SS',\n            [\n                {ms: 0, text: '00:00'},\n                {ms: 60000, text: '01:00'},\n                {ms: 600000, text: '10:00'},\n                {ms: 750000, text: '12:30'},\n            ],\n        ],\n        [\n            'SS.MSS',\n            [\n                {ms: 0, text: '00.000'},\n                {ms: 1001, text: '01.001'},\n                {ms: 12345, text: '12.345'},\n                {ms: 59999, text: '59.999'},\n            ],\n        ],\n    ]);\n\n    testCases.forEach((cases, mode) => {\n        describe(`mode ${mode}`, () => {\n            cases.forEach(({ms, text}) => {\n                it(`${ms}ms => '${text}'`, () => {\n                    expect(maskitoStringifyTime(ms, {mode})).toBe(text);\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/plugins/add-on-focus.ts",
    "content": "import {type MaskitoPlugin, maskitoUpdateElement} from '@maskito/core';\n\nimport {maskitoEventHandler} from './event-handler';\n\nexport function maskitoAddOnFocusPlugin(value: string): MaskitoPlugin {\n    return maskitoEventHandler('focus', (element) => {\n        if (!element.value) {\n            maskitoUpdateElement(element, value);\n        }\n    });\n}\n"
  },
  {
    "path": "projects/kit/src/lib/plugins/caret-guard.ts",
    "content": "import type {MaskitoPlugin} from '@maskito/core';\n\nimport {clamp} from '../utils';\nimport {maskitoSelectionChangeHandler} from './selection-change';\n\nexport function maskitoCaretGuard(\n    guard: (\n        value: string,\n        selection: readonly [from: number, to: number],\n    ) => [from: number, to: number],\n): MaskitoPlugin {\n    return maskitoSelectionChangeHandler((element) => {\n        const start = element.selectionStart ?? 0;\n        const end = element.selectionEnd ?? 0;\n        const [fromLimit, toLimit] = guard(element.value, [start, end]);\n\n        if (fromLimit > start || toLimit < end) {\n            element.setSelectionRange(\n                clamp(start, fromLimit, toLimit),\n                clamp(end, fromLimit, toLimit),\n            );\n        }\n    });\n}\n"
  },
  {
    "path": "projects/kit/src/lib/plugins/event-handler.ts",
    "content": "import type {MaskitoElement, MaskitoOptions, MaskitoPlugin} from '@maskito/core';\n\nexport function maskitoEventHandler(\n    name: string,\n    handler: (element: MaskitoElement, options: Required<MaskitoOptions>) => void,\n    eventListenerOptions?: AddEventListenerOptions,\n): MaskitoPlugin {\n    return (element, maskitoOptions) => {\n        const listener = (): void => handler(element, maskitoOptions);\n\n        element.addEventListener(name, listener, eventListenerOptions);\n\n        return () => element.removeEventListener(name, listener, eventListenerOptions);\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/plugins/index.ts",
    "content": "export {maskitoAddOnFocusPlugin} from './add-on-focus';\nexport {maskitoCaretGuard} from './caret-guard';\nexport {maskitoEventHandler} from './event-handler';\nexport {maskitoRejectEvent} from './reject-event';\nexport {maskitoRemoveOnBlurPlugin} from './remove-on-blur';\nexport {maskitoSelectionChangeHandler} from './selection-change';\nexport {createMeridiemSteppingPlugin} from './time/meridiem-stepping';\nexport {createTimeSegmentsSteppingPlugin} from './time/time-segments-stepping';\n"
  },
  {
    "path": "projects/kit/src/lib/plugins/reject-event.ts",
    "content": "import type {MaskitoPlugin} from '@maskito/core';\n\nexport const maskitoRejectEvent: MaskitoPlugin = (element) => {\n    const listener = (): void => {\n        const value = element.value;\n\n        element.addEventListener(\n            'beforeinput',\n            (event) => {\n                if (event.defaultPrevented && value === element.value) {\n                    element.dispatchEvent(\n                        new CustomEvent('maskitoReject', {bubbles: true}),\n                    );\n                }\n            },\n            {once: true},\n        );\n    };\n\n    element.addEventListener('beforeinput', listener, true);\n\n    return () => element.removeEventListener('beforeinput', listener, true);\n};\n"
  },
  {
    "path": "projects/kit/src/lib/plugins/remove-on-blur.ts",
    "content": "import {type MaskitoPlugin, maskitoUpdateElement} from '@maskito/core';\n\nimport {maskitoEventHandler} from './event-handler';\n\nexport function maskitoRemoveOnBlurPlugin(value: string): MaskitoPlugin {\n    return maskitoEventHandler('blur', (element) => {\n        if (element.value === value) {\n            maskitoUpdateElement(element, '');\n        }\n    });\n}\n"
  },
  {
    "path": "projects/kit/src/lib/plugins/selection-change.ts",
    "content": "import type {MaskitoElement, MaskitoOptions, MaskitoPlugin} from '@maskito/core';\n\nexport function maskitoSelectionChangeHandler(\n    handler: (element: MaskitoElement, options: Required<MaskitoOptions>) => void,\n): MaskitoPlugin {\n    return (element, options) => {\n        const document = element.ownerDocument;\n        let isPointerDown = 0;\n        const onPointerDown = (): number => isPointerDown++;\n        const onPointerUp = (): void => {\n            isPointerDown = Math.max(--isPointerDown, 0);\n        };\n\n        const listener = (): void => {\n            if (!element.matches(':focus')) {\n                return;\n            }\n\n            if (isPointerDown) {\n                return document.addEventListener('mouseup', listener, {\n                    once: true,\n                    passive: true,\n                });\n            }\n\n            handler(element, options);\n        };\n\n        document.addEventListener('selectionchange', listener, {passive: true});\n        // Safari does not fire `selectionchange` on focus after programmatic update of textfield value\n        element.addEventListener('focus', listener, {passive: true});\n        element.addEventListener('mousedown', onPointerDown, {passive: true});\n        document.addEventListener('mouseup', onPointerUp, {passive: true});\n\n        return () => {\n            document.removeEventListener('selectionchange', listener);\n            element.removeEventListener('focus', listener);\n            element.removeEventListener('mousedown', onPointerDown);\n            document.removeEventListener('mouseup', onPointerUp);\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/plugins/time/meridiem-stepping.ts",
    "content": "import {type MaskitoPlugin, maskitoUpdateElement} from '@maskito/core';\n\nimport {ANY_MERIDIEM_CHARACTER_RE, CHAR_NO_BREAK_SPACE} from '../../constants';\nimport {noop} from '../../utils';\n\nexport function createMeridiemSteppingPlugin(meridiemStartIndex: number): MaskitoPlugin {\n    if (meridiemStartIndex < 0) {\n        return noop;\n    }\n\n    return (element) => {\n        const listener = (event: KeyboardEvent): void => {\n            const caretIndex = Number(element.selectionStart);\n            const value = element.value.toUpperCase();\n\n            if (\n                (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') ||\n                caretIndex < meridiemStartIndex\n            ) {\n                return;\n            }\n\n            event.preventDefault();\n\n            // eslint-disable-next-line no-nested-ternary\n            const meridiemMainCharacter = value.includes('A')\n                ? 'P'\n                : value.includes('P') || event.key === 'ArrowUp'\n                  ? 'A'\n                  : 'P';\n\n            const newMeridiem = `${CHAR_NO_BREAK_SPACE}${meridiemMainCharacter}M`;\n\n            maskitoUpdateElement(element, {\n                value:\n                    value.length === meridiemStartIndex\n                        ? `${value}${newMeridiem}`\n                        : value.replace(ANY_MERIDIEM_CHARACTER_RE, newMeridiem),\n                selection: [caretIndex, caretIndex],\n            });\n        };\n\n        element.addEventListener('keydown', listener);\n\n        return () => element.removeEventListener('keydown', listener);\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/plugins/time/time-segments-stepping.ts",
    "content": "import {type MaskitoPlugin, maskitoUpdateElement} from '@maskito/core';\n\nimport type {MaskitoTimeSegments} from '../../types';\nimport {noop} from '../../utils';\n\nexport function createTimeSegmentsSteppingPlugin({\n    step,\n    fullMode,\n    timeSegmentMinValues,\n    timeSegmentMaxValues,\n}: {\n    step: number;\n    fullMode: string;\n    timeSegmentMinValues: MaskitoTimeSegments<number>;\n    timeSegmentMaxValues: MaskitoTimeSegments<number>;\n}): MaskitoPlugin {\n    const segmentsIndexes = createTimeSegmentsIndexes(fullMode);\n\n    return step <= 0\n        ? noop\n        : (element) => {\n              const listener = (event: KeyboardEvent): void => {\n                  if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') {\n                      return;\n                  }\n\n                  event.preventDefault();\n                  const selectionStart = element.selectionStart ?? 0;\n                  const activeSegment = getActiveSegment({\n                      segmentsIndexes,\n                      selectionStart,\n                  });\n\n                  if (!activeSegment) {\n                      return;\n                  }\n\n                  const updatedValue = updateSegmentValue({\n                      selection: segmentsIndexes.get(activeSegment)!,\n                      value: element.value,\n                      toAdd: event.key === 'ArrowUp' ? step : -step,\n                      min: timeSegmentMinValues[activeSegment],\n                      max: timeSegmentMaxValues[activeSegment],\n                  });\n\n                  maskitoUpdateElement(element, {\n                      value: updatedValue,\n                      selection: [selectionStart, selectionStart],\n                  });\n              };\n\n              element.addEventListener('keydown', listener);\n\n              return () => element.removeEventListener('keydown', listener);\n          };\n}\n\nfunction createTimeSegmentsIndexes(\n    fullMode: string,\n): Map<keyof MaskitoTimeSegments, readonly [number, number]> {\n    return new Map([\n        ['hours', getSegmentRange(fullMode, 'HH')],\n        ['milliseconds', getSegmentRange(fullMode, 'MSS')],\n        ['minutes', getSegmentRange(fullMode, 'MM')],\n        ['seconds', getSegmentRange(fullMode, 'SS')],\n    ]);\n}\n\nfunction getSegmentRange(mode: string, segment: string): [number, number] {\n    const index = mode.indexOf(segment);\n\n    return index === -1 ? [-1, -1] : [index, index + segment.length];\n}\n\nfunction getActiveSegment({\n    segmentsIndexes,\n    selectionStart,\n}: {\n    segmentsIndexes: Map<keyof MaskitoTimeSegments, readonly [number, number]>;\n    selectionStart: number;\n}): keyof MaskitoTimeSegments | null {\n    for (const [segmentName, segmentRange] of segmentsIndexes.entries()) {\n        const [from, to] = segmentRange;\n\n        if (from <= selectionStart && selectionStart <= to) {\n            return segmentName;\n        }\n    }\n\n    return null;\n}\n\nfunction updateSegmentValue({\n    selection,\n    value,\n    toAdd,\n    min,\n    max,\n}: {\n    selection: readonly [number, number];\n    value: string;\n    toAdd: number;\n    min: number;\n    max: number;\n}): string {\n    const [from, to] = selection;\n    const segmentValue = Number(value.slice(from, to).padEnd(to - from, '0'));\n    const newSegmentValue = mod(segmentValue + toAdd, min, max + 1);\n\n    return `${value.slice(0, from)}${String(newSegmentValue).padStart(to - from, '0')}${value.slice(to, value.length)}`;\n}\n\nfunction mod(value: number, min: number, max: number): number {\n    const range = max - min;\n\n    return ((((value - min) % range) + range) % range) + min;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/colon-convert-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport {toHalfWidthColon} from '../utils';\n\n/**\n * Convert full width colon (：) to half width one (:)\n */\nexport function createColonConvertPreprocessor(): MaskitoPreprocessor {\n    return ({elementState, data}) => {\n        const {value, selection} = elementState;\n\n        return {\n            elementState: {\n                selection,\n                value: toHalfWidthColon(value),\n            },\n            data: toHalfWidthColon(data),\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/date-segments-zero-padding-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\n\nimport {DATE_SEGMENTS_MAX_VALUES} from '../constants';\nimport type {MaskitoDateSegments} from '../types';\nimport {padWithZeroesUntilValid, parseDateString, toDateString} from '../utils';\n\nexport function createDateSegmentsZeroPaddingPostprocessor({\n    dateModeTemplate,\n    dateSegmentSeparator,\n    splitFn,\n    uniteFn,\n}: {\n    dateModeTemplate: string;\n    dateSegmentSeparator: string;\n    splitFn: (value: string) => {dateStrings: string[]; restPart?: string};\n    uniteFn: (validatedDateStrings: string[], initialValue: string) => string;\n}): MaskitoPostprocessor {\n    return ({value, selection}) => {\n        const [from, to] = selection;\n        const {dateStrings, restPart = ''} = splitFn(value);\n        const validatedDateStrings: string[] = [];\n        let caretShift = 0;\n\n        dateStrings.forEach((dateString) => {\n            const parsedDate = parseDateString(dateString, dateModeTemplate);\n            const dateSegments = Object.entries(parsedDate) as Array<\n                [keyof MaskitoDateSegments, string]\n            >;\n\n            const validatedDateSegments = dateSegments.reduce(\n                (acc, [segmentName, segmentValue]) => {\n                    const {validatedSegmentValue, prefixedZeroesCount} =\n                        padWithZeroesUntilValid(\n                            segmentValue,\n                            `${DATE_SEGMENTS_MAX_VALUES[segmentName]}`,\n                        );\n\n                    caretShift += prefixedZeroesCount;\n\n                    return {...acc, [segmentName]: validatedSegmentValue};\n                },\n                {},\n            );\n\n            validatedDateStrings.push(\n                toDateString(validatedDateSegments, {dateMode: dateModeTemplate}),\n            );\n        });\n\n        const validatedValue = `${uniteFn(validatedDateStrings, value)}${\n            dateStrings[dateStrings.length - 1]?.endsWith(dateSegmentSeparator)\n                ? dateSegmentSeparator\n                : ''\n        }${restPart}`;\n\n        if (\n            caretShift &&\n            validatedValue.slice(\n                to + caretShift,\n                to + caretShift + dateSegmentSeparator.length,\n            ) === dateSegmentSeparator\n        ) {\n            /**\n             * If `caretShift` > 0, it means that time segment was padded with zero.\n             * It is only possible if any character insertion happens.\n             * If caret is before `dateSegmentSeparator` => it should be moved after `dateSegmentSeparator`.\n             */\n            caretShift += dateSegmentSeparator.length;\n        }\n\n        return {\n            selection: [from + caretShift, to + caretShift],\n            value: validatedValue,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/first-date-end-separator-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport {getFirstCompleteDate} from '../utils';\n\n/**\n * It replaces pseudo range separators with valid one.\n * @example '01.01.2000_11.11.2000' -> '01.01.2000 - 01.01.2000'\n * @example '01.01.2000_23:59' -> '01.01.2000, 23:59'\n */\nexport function createFirstDateEndSeparatorPreprocessor({\n    dateModeTemplate,\n    firstDateEndSeparator,\n    dateSegmentSeparator,\n    pseudoFirstDateEndSeparators,\n}: {\n    dateModeTemplate: string;\n    firstDateEndSeparator: string;\n    dateSegmentSeparator: string;\n    pseudoFirstDateEndSeparators: string[];\n}): MaskitoPreprocessor {\n    return ({elementState, data}) => {\n        const {value, selection} = elementState;\n        const [from, to] = selection;\n        const firstCompleteDate = getFirstCompleteDate(value, dateModeTemplate);\n        const pseudoSeparators = pseudoFirstDateEndSeparators.filter(\n            (x) => !firstDateEndSeparator.includes(x) && x !== dateSegmentSeparator,\n        );\n        const pseudoSeparatorsRE = new RegExp(`[${pseudoSeparators.join('')}]`, 'gi');\n        const newValue =\n            firstCompleteDate && value.length > firstCompleteDate.length\n                ? `${firstCompleteDate}${value\n                      .slice(firstCompleteDate.length)\n                      .replace(/^\\D*/, firstDateEndSeparator)}`\n                : value;\n        const caretShift = newValue.length - value.length;\n\n        return {\n            elementState: {\n                selection: [from + caretShift, to + caretShift],\n                value: newValue,\n            },\n            data: data.replace(pseudoSeparatorsRE, firstDateEndSeparator),\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/fullwidth-to-halfwidth-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport {toHalfWidthNumber} from '../utils';\n\n/**\n * Convert full width numbers like １, ２ to half width numbers 1, 2\n */\nexport function createFullWidthToHalfWidthPreprocessor(): MaskitoPreprocessor {\n    return ({elementState, data}) => {\n        const {value, selection} = elementState;\n\n        return {\n            elementState: {\n                selection,\n                value: toHalfWidthNumber(value),\n            },\n            data: toHalfWidthNumber(data),\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/index.ts",
    "content": "export {createColonConvertPreprocessor} from './colon-convert-preprocessor';\nexport {createDateSegmentsZeroPaddingPostprocessor} from './date-segments-zero-padding-postprocessor';\nexport {createFirstDateEndSeparatorPreprocessor} from './first-date-end-separator-preprocessor';\nexport {createFullWidthToHalfWidthPreprocessor} from './fullwidth-to-halfwidth-preprocessor';\nexport {createInvalidTimeSegmentInsertionPreprocessor} from './invalid-time-segment-insertion-preprocessor';\nexport {\n    createMeridiemPostprocessor,\n    createMeridiemPreprocessor,\n} from './meridiem-processors';\nexport {createMinMaxDatePostprocessor} from './min-max-date-postprocessor';\nexport {normalizeDatePreprocessor} from './normalize-date-preprocessor';\nexport {maskitoPostfixPostprocessorGenerator} from './postfix-postprocessor';\nexport {maskitoPrefixPostprocessorGenerator} from './prefix-postprocessor';\nexport {createValidDatePreprocessor} from './valid-date-preprocessor';\nexport {maskitoWithPlaceholder} from './with-placeholder';\nexport {createZeroPlaceholdersPreprocessor} from './zero-placeholders-preprocessor';\n"
  },
  {
    "path": "projects/kit/src/lib/processors/invalid-time-segment-insertion-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport {\n    DEFAULT_TIME_SEGMENT_MAX_VALUES,\n    DEFAULT_TIME_SEGMENT_MIN_VALUES,\n    TIME_FIXED_CHARACTERS,\n    TIME_SEGMENT_VALUE_LENGTHS,\n} from '../constants';\nimport type {MaskitoTimeMode, MaskitoTimeSegments} from '../types';\nimport {clamp, escapeRegExp} from '../utils';\nimport {parseTimeString} from '../utils/time';\n\n/**\n * Prevent insertion if any time segment will become invalid\n * (and even zero padding won't help with it).\n * @example 2|0:00 => Type 9 => 2|0:00\n */\nexport function createInvalidTimeSegmentInsertionPreprocessor({\n    timeMode,\n    timeSegmentMinValues = DEFAULT_TIME_SEGMENT_MIN_VALUES,\n    timeSegmentMaxValues = DEFAULT_TIME_SEGMENT_MAX_VALUES,\n    parseValue = (x) => ({timeString: x}),\n}: {\n    timeMode: MaskitoTimeMode;\n    timeSegmentMinValues?: MaskitoTimeSegments<number>;\n    timeSegmentMaxValues?: MaskitoTimeSegments<number>;\n    parseValue?: (value: string) => {timeString: string; restValue?: string};\n}): MaskitoPreprocessor {\n    const invalidCharsRegExp = new RegExp(\n        String.raw`[^\\d${TIME_FIXED_CHARACTERS.map(escapeRegExp).join('')}]+`,\n    );\n\n    return ({elementState, data}, actionType) => {\n        if (actionType !== 'insert') {\n            return {elementState, data};\n        }\n\n        const {value, selection} = elementState;\n        const [from, rawTo] = selection;\n        const newCharacters = data.replace(invalidCharsRegExp, '');\n        const to = rawTo + newCharacters.length; // to be conformed with `overwriteMode: replace`\n        const newPossibleValue = `${value.slice(0, from)}${newCharacters}${value.slice(to)}`;\n        const {timeString, restValue = ''} = parseValue(newPossibleValue);\n        const timeSegments = Object.entries(\n            parseTimeString(timeString, timeMode),\n        ) as Array<[keyof MaskitoTimeSegments, string]>;\n\n        let offset = restValue.length;\n\n        for (const [segmentName, stringifiedSegmentValue] of timeSegments) {\n            const minSegmentValue = timeSegmentMinValues[segmentName];\n            const maxSegmentValue = timeSegmentMaxValues[segmentName];\n            const segmentValue = Number(stringifiedSegmentValue);\n\n            const lastSegmentDigitIndex =\n                offset + TIME_SEGMENT_VALUE_LENGTHS[segmentName];\n\n            if (\n                lastSegmentDigitIndex >= from &&\n                lastSegmentDigitIndex <= to &&\n                segmentValue !== clamp(segmentValue, minSegmentValue, maxSegmentValue)\n            ) {\n                return {elementState, data: ''}; // prevent insertion\n            }\n\n            offset +=\n                stringifiedSegmentValue.length +\n                // any time segment separator\n                1;\n        }\n\n        return {elementState, data};\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/meridiem-processors.ts",
    "content": "import type {MaskitoPostprocessor, MaskitoPreprocessor} from '@maskito/core';\n\nimport {\n    ALL_MERIDIEM_CHARACTERS_RE,\n    ANY_MERIDIEM_CHARACTER_RE,\n    CHAR_NO_BREAK_SPACE,\n} from '../constants';\nimport type {MaskitoTimeMode} from '../types';\nimport {identity} from '../utils';\n\nexport function createMeridiemPreprocessor(\n    timeMode: MaskitoTimeMode,\n): MaskitoPreprocessor {\n    if (!timeMode.includes('AA')) {\n        return identity;\n    }\n\n    const mainMeridiemCharRE = /^[AP]$/gi;\n\n    return ({elementState, data}) => {\n        const {value, selection} = elementState;\n        const newValue = value.toUpperCase();\n        const newData = data.toUpperCase();\n\n        if (\n            newValue.match(ALL_MERIDIEM_CHARACTERS_RE) &&\n            newData.match(mainMeridiemCharRE)\n        ) {\n            return {\n                elementState: {\n                    value: newValue.replaceAll(ALL_MERIDIEM_CHARACTERS_RE, ''),\n                    selection,\n                },\n                data: `${newData}M`,\n            };\n        }\n\n        return {elementState: {selection, value: newValue}, data: newData};\n    };\n}\n\nexport function createMeridiemPostprocessor(\n    timeMode: MaskitoTimeMode,\n): MaskitoPostprocessor {\n    if (!timeMode.includes('AA')) {\n        return identity;\n    }\n\n    return ({value, selection}, initialElementState) => {\n        if (\n            !value.match(ANY_MERIDIEM_CHARACTER_RE) ||\n            value.match(ALL_MERIDIEM_CHARACTERS_RE)\n        ) {\n            return {value, selection};\n        }\n\n        const [from, to] = selection;\n\n        // any meridiem character was deleted\n        if (initialElementState.value.match(ALL_MERIDIEM_CHARACTERS_RE)) {\n            const newValue = value.replace(ANY_MERIDIEM_CHARACTER_RE, '');\n\n            return {\n                value: newValue,\n                selection: [\n                    Math.min(from, newValue.length),\n                    Math.min(to, newValue.length),\n                ],\n            };\n        }\n\n        const fullMeridiem = `${CHAR_NO_BREAK_SPACE}${value.includes('P') ? 'P' : 'A'}M`;\n        const newValue = value.replace(ANY_MERIDIEM_CHARACTER_RE, (x) =>\n            x === CHAR_NO_BREAK_SPACE ? x : fullMeridiem,\n        );\n\n        return {\n            value: newValue,\n            selection:\n                to >= newValue.indexOf(fullMeridiem)\n                    ? [newValue.length, newValue.length]\n                    : selection,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/min-max-date-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\n\nimport {DEFAULT_MAX_DATE, DEFAULT_MIN_DATE} from '../constants';\nimport {\n    clamp,\n    dateToSegments,\n    isDateStringComplete,\n    parseDateRangeString,\n    parseDateString,\n    segmentsToDate,\n    toDateString,\n} from '../utils';\nimport {raiseSegmentValueToMin} from '../utils/date/raise-segment-value-to-min';\n\nconst LEAP_YEAR = '1972';\n\nexport function createMinMaxDatePostprocessor({\n    dateModeTemplate,\n    min = DEFAULT_MIN_DATE,\n    max = DEFAULT_MAX_DATE,\n    rangeSeparator = '',\n    dateSegmentSeparator = '.',\n}: {\n    dateModeTemplate: string;\n    min?: Date;\n    max?: Date;\n    rangeSeparator?: string;\n    dateSegmentSeparator?: string;\n}): MaskitoPostprocessor {\n    return ({value, selection}) => {\n        const endsWithRangeSeparator = rangeSeparator && value.endsWith(rangeSeparator);\n        const dateStrings = parseDateRangeString(value, dateModeTemplate, rangeSeparator);\n\n        let validatedValue = '';\n\n        for (const dateString of dateStrings) {\n            validatedValue += validatedValue ? rangeSeparator : '';\n\n            const parsedDate = parseDateString(dateString, dateModeTemplate);\n\n            if (!isDateStringComplete(dateString, dateModeTemplate)) {\n                const fixedDate = raiseSegmentValueToMin(parsedDate, dateModeTemplate);\n\n                const fixedValue = toDateString(fixedDate, {dateMode: dateModeTemplate});\n                const tail = dateString.endsWith(dateSegmentSeparator)\n                    ? dateSegmentSeparator\n                    : '';\n\n                validatedValue += `${fixedValue}${tail}`;\n                continue;\n            }\n\n            const date = segmentsToDate({year: LEAP_YEAR, ...parsedDate});\n\n            const clampedDate = clamp(date, min, max);\n\n            validatedValue += toDateString(dateToSegments(clampedDate), {\n                dateMode: dateModeTemplate,\n            });\n        }\n\n        return {\n            selection,\n            value: `${validatedValue}${endsWithRangeSeparator ? rangeSeparator : ''}`,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/normalize-date-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport {DATE_TIME_SEPARATOR} from '../masks/date-time/constants';\n\nexport function normalizeDatePreprocessor({\n    dateModeTemplate,\n    dateSegmentsSeparator,\n    rangeSeparator = '',\n    dateTimeSeparator = DATE_TIME_SEPARATOR,\n}: {\n    dateModeTemplate: string;\n    dateSegmentsSeparator: string;\n    rangeSeparator?: string;\n    dateTimeSeparator?: string;\n}): MaskitoPreprocessor {\n    return ({elementState, data}) => {\n        const templateSegments = dateModeTemplate.split(dateSegmentsSeparator);\n        const includesTime = data.includes(dateTimeSeparator);\n        const dateSegments = data\n            .slice(0, includesTime ? data.indexOf(dateTimeSeparator) : Infinity)\n            .split(/\\D/)\n            .filter(Boolean);\n\n        if (!dateSegments.length || dateSegments.length % templateSegments.length !== 0) {\n            return {elementState, data};\n        }\n\n        const dates = dateSegments.reduce<string[]>((dates, segment, index) => {\n            const template = templateSegments[index % templateSegments.length] ?? '';\n            const dateIndex = Math.trunc(index / templateSegments.length);\n            const isLastDateSegment =\n                index % templateSegments.length === templateSegments.length - 1;\n\n            if (!dates[dateIndex]) {\n                dates[dateIndex] = '';\n            }\n\n            dates[dateIndex] += isLastDateSegment\n                ? segment\n                : `${segment.padStart(template.length, '0')}${dateSegmentsSeparator}`;\n\n            return dates;\n        }, []);\n\n        return {\n            elementState,\n            data: includesTime\n                ? `${dates[0]}${data.slice(data.indexOf(dateTimeSeparator))}`\n                : dates.join(rangeSeparator),\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/postfix-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\n\nimport {escapeRegExp, findCommonBeginningSubstr, identity} from '../utils';\n\nexport function maskitoPostfixPostprocessorGenerator(\n    postfix: string,\n): MaskitoPostprocessor {\n    const completedPostfixRE = new RegExp(`${escapeRegExp(postfix)}$`);\n    const incompletePostfixRE = new RegExp(\n        postfix &&\n            `(${postfix\n                .split('')\n                .map(escapeRegExp)\n                // eslint-disable-next-line\n                .reduce((acc, _, i, arr) => `${acc}|${arr.slice(0, i + 1).join('')}`)})$`,\n    );\n\n    return postfix\n        ? ({value, selection}, initialElementState) => {\n              if (!value && !initialElementState.value.endsWith(postfix)) {\n                  // cases when developer wants input to be empty (programmatically)\n                  return {value, selection};\n              }\n\n              if (\n                  !value.match(incompletePostfixRE) &&\n                  !initialElementState.value.endsWith(postfix)\n              ) {\n                  return {selection, value: `${value}${postfix}`};\n              }\n\n              const initialValueBeforePostfix = initialElementState.value.replace(\n                  completedPostfixRE,\n                  '',\n              );\n              const postfixWasModified =\n                  initialElementState.selection[1] > initialValueBeforePostfix.length;\n              const alreadyExistedValueBeforePostfix = findCommonBeginningSubstr(\n                  initialValueBeforePostfix,\n                  value,\n              );\n\n              return {\n                  selection,\n                  value: Array.from(postfix)\n                      .reverse()\n                      .reduce((newValue, char, index) => {\n                          const i = newValue.length - 1 - index;\n                          const isInitiallyMirroredChar =\n                              alreadyExistedValueBeforePostfix[i] === char &&\n                              postfixWasModified;\n\n                          return newValue[i] !== char || isInitiallyMirroredChar\n                              ? `${newValue.slice(0, i + 1)}${char}${newValue.slice(i + 1)}`\n                              : newValue;\n                      }, value),\n              };\n          }\n        : identity;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/prefix-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\n\nimport {identity} from '../utils';\n\nexport function maskitoPrefixPostprocessorGenerator(\n    prefix: string,\n): MaskitoPostprocessor {\n    return prefix\n        ? ({value, selection}, initialElementState) => {\n              if (\n                  value.startsWith(prefix) || // already valid\n                  (!value && !initialElementState.value.startsWith(prefix)) // cases when developer wants input to be empty\n              ) {\n                  return {value, selection};\n              }\n\n              const [from, to] = selection;\n              const prefixedValue = Array.from(prefix).reduce(\n                  (modifiedValue, char, i) =>\n                      modifiedValue[i] === char\n                          ? modifiedValue\n                          : `${modifiedValue.slice(0, i)}${char}${modifiedValue.slice(i)}`,\n                  value,\n              );\n              const addedCharsCount = prefixedValue.length - value.length;\n\n              return {\n                  selection: [from + addedCharsCount, to + addedCharsCount],\n                  value: prefixedValue,\n              };\n          }\n        : identity;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/tests/first-date-end-separator-preprocessor.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {POSSIBLE_DATE_RANGE_SEPARATOR} from '../../masks/date-range/constants';\nimport {POSSIBLE_DATE_TIME_SEPARATOR} from '../../masks/date-time/constants';\nimport {createFirstDateEndSeparatorPreprocessor} from '../first-date-end-separator-preprocessor';\n\ndescribe('FirstDateEndSeparatorPreprocessor', () => {\n    const EMPTY_SELECTION = [0, 0] as const;\n\n    describe('DateRange', () => {\n        const preprocessorFn = createFirstDateEndSeparatorPreprocessor({\n            dateModeTemplate: 'dd.mm.yyyy',\n            firstDateEndSeparator: ' ~ ',\n            dateSegmentSeparator: '.',\n            pseudoFirstDateEndSeparators: POSSIBLE_DATE_RANGE_SEPARATOR,\n        });\n        const preprocess = (value: string): string =>\n            preprocessorFn(\n                {elementState: {value, selection: EMPTY_SELECTION}, data: ''},\n                'validation',\n            ).elementState.value;\n\n        it('only complete date (without date end separator)', () => {\n            expect(preprocess('01.01.2000')).toBe('01.01.2000');\n        });\n\n        it('only complete date + date end separator', () => {\n            expect(preprocess('01.01.2000~')).toBe('01.01.2000 ~ ');\n        });\n\n        it('01.01.2000_11.11.2011', () => {\n            expect(preprocess('01.01.2000_11.11.2011')).toBe('01.01.2000 ~ 11.11.2011');\n        });\n\n        it('01.01.2000-11.11.2011', () => {\n            expect(preprocess('01.01.2000-11.11.2011')).toBe('01.01.2000 ~ 11.11.2011');\n        });\n\n        it('01-01-2000 - 11-11-2011', () => {\n            expect(preprocess('01-01-2000 - 11-11-2011')).toBe('01-01-2000 ~ 11-11-2011');\n        });\n\n        it('01.01.2000~11.11.2011', () => {\n            expect(preprocess('01.01.2000~11.11.2011')).toBe('01.01.2000 ~ 11.11.2011');\n        });\n\n        it('01.01.2000 ~ 11.11.2011', () => {\n            expect(preprocess('01.01.2000 ~ 11.11.2011')).toBe('01.01.2000 ~ 11.11.2011');\n        });\n\n        it('`value` contains only complete date and `data` contains pseudo range separator', () => {\n            const {elementState, data} = preprocessorFn(\n                {\n                    elementState: {value: '01.01.2000', selection: EMPTY_SELECTION},\n                    data: '-',\n                },\n                'insert',\n            );\n\n            expect(elementState.value).toBe('01.01.2000');\n            expect(data).toBe(' ~ ');\n        });\n    });\n\n    describe('DateTime', () => {\n        const preprocessorFn = createFirstDateEndSeparatorPreprocessor({\n            dateModeTemplate: 'dd.mm.yyyy',\n            firstDateEndSeparator: '_',\n            dateSegmentSeparator: '.',\n            pseudoFirstDateEndSeparators: POSSIBLE_DATE_TIME_SEPARATOR,\n        });\n        // eslint-disable-next-line sonarjs/no-identical-functions\n        const preprocess = (value: string): string =>\n            preprocessorFn(\n                {elementState: {value, selection: EMPTY_SELECTION}, data: ''},\n                'validation',\n            ).elementState.value;\n\n        it('01.01.2000,23:59', () => {\n            expect(preprocess('01.01.2000,23:59')).toBe('01.01.2000_23:59');\n        });\n\n        it('01.01.2000, 23:59', () => {\n            expect(preprocess('01.01.2000, 23:59')).toBe('01.01.2000_23:59');\n        });\n\n        it('01.01.2000_23:59', () => {\n            expect(preprocess('01.01.2000_23:59')).toBe('01.01.2000_23:59');\n        });\n\n        it('01-01-2000-23:59', () => {\n            expect(preprocess('01-01-2000-23:59')).toBe('01-01-2000_23:59');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/processors/tests/normalize-date-preprocessor.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\nimport type {MaskitoPreprocessor} from '@maskito/core';\n\nimport {normalizeDatePreprocessor} from '../normalize-date-preprocessor';\n\ndescribe('normalizeDatePreprocessor', () => {\n    describe('Input-date-range', () => {\n        const preprocessor = normalizeDatePreprocessor({\n            dateModeTemplate: 'dd.mm.yyyy',\n            dateSegmentsSeparator: '.',\n            rangeSeparator: ' – ',\n        });\n\n        const check = getCheckFunction(preprocessor);\n\n        it('empty input => 6.2.2023 – 7.2.2023', () => {\n            check('6.2.2023 – 7.2.2023', '06.02.2023 – 07.02.2023');\n        });\n\n        it('empty input => 6.2.2023 – 7.2.2023 (basic spaces)', () => {\n            check('6.2.2023 – 7.2.2023', '06.02.2023 – 07.02.2023');\n        });\n\n        it('empty input => 06.2.2023-07.2.2023', () => {\n            check('06.2.2023-07.2.2023', '06.02.2023 – 07.02.2023');\n        });\n\n        it('empty input => 06-2-2023 - 07-2-2023', () => {\n            check('06-2-2023-07-2-2023', '06.02.2023 – 07.02.2023');\n        });\n\n        it('empty input => 06-2-2023-07-2-2023', () => {\n            check('06-2-2023-07-2-2023', '06.02.2023 – 07.02.2023');\n        });\n    });\n\n    describe('Input-date long mode', () => {\n        const preprocessor = normalizeDatePreprocessor({\n            dateModeTemplate: 'dd.mm.yyyy',\n            dateSegmentsSeparator: '.',\n        });\n\n        const check = getCheckFunction(preprocessor);\n\n        it('empty input => 6.2.2023', () => {\n            check('6.2.2023', '06.02.2023');\n        });\n\n        it('empty input => 06.2.2023', () => {\n            check('06.2.2023', '06.02.2023');\n        });\n\n        it('empty input => 06.2.20', () => {\n            check('06.2.20', '06.02.20');\n        });\n    });\n\n    describe('input-date short mode', () => {\n        const preprocessor = normalizeDatePreprocessor({\n            dateModeTemplate: 'mm/yy',\n            dateSegmentsSeparator: '/',\n        });\n\n        const check = getCheckFunction(preprocessor);\n\n        it('empty input => 2/2/22', () => {\n            check('2/2', '02/2');\n        });\n\n        it('empty input => 1.1', () => {\n            check('1.1', '01/1');\n        });\n\n        it('empty input => 3.12', () => {\n            check('3.12', '03/12');\n        });\n    });\n\n    describe('input-date-time', () => {\n        const preprocessor = normalizeDatePreprocessor({\n            dateModeTemplate: 'dd.mm.yyyy',\n            dateSegmentsSeparator: '.',\n        });\n        const check = getCheckFunction(preprocessor);\n\n        it('empty input => 6.2.2023, 12:00', () => {\n            check('6.2.2023, 12:00', '06.02.2023, 12:00');\n        });\n\n        it('empty input => 6.2.2023, 15', () => {\n            check('6.2.2023, 15', '06.02.2023, 15');\n        });\n\n        it('empty input => 06.2.2023', () => {\n            check('06.2.2023', '06.02.2023');\n        });\n\n        it('empty input => 6.2.2023', () => {\n            check('6.2.2022, 15', '06.02.2022, 15');\n        });\n\n        it('empty input => 6.2.2023, 12:01.001', () => {\n            check('6.2.2023, 12:01.001', '06.02.2023, 12:01.001');\n        });\n\n        it('empty input => 6.2.2023, 01.001', () => {\n            check('6.2.2023, 01.001', '06.02.2023, 01.001');\n        });\n    });\n});\n\nfunction getCheckFunction(\n    preprocessor: MaskitoPreprocessor,\n): (actual: string, expected: string) => void {\n    return (insertedCharacters: string, expectedValue: string): void => {\n        const EMPTY_INPUT = {value: '', selection: [0, 0] as [number, number]};\n\n        const {data} = preprocessor(\n            {elementState: EMPTY_INPUT, data: insertedCharacters},\n            'insert',\n        );\n\n        expect(data).toEqual(expectedValue);\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/tests/postfix-postprocessor.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {maskitoPostfixPostprocessorGenerator} from '../postfix-postprocessor';\n\ndescribe('maskitoPostfixPostprocessorGenerator', () => {\n    const EMPTY_INPUT = {value: '', selection: [0, 0] as const};\n\n    describe('postfix is a single character', () => {\n        const postprocessor = maskitoPostfixPostprocessorGenerator('%');\n\n        it('does not add postfix if input was initially empty', () => {\n            expect(postprocessor(EMPTY_INPUT, EMPTY_INPUT)).toEqual(EMPTY_INPUT);\n        });\n\n        it('type 99 => 99%', () => {\n            expect(\n                postprocessor(\n                    {value: '99', selection: [2, 2]}, // after changes\n                    // percent sign was deleted by backspace\n                    {value: '99%', selection: [3, 3]}, // before changes (initialElementState)\n                ),\n            ).toEqual({value: '99%', selection: [2, 2]});\n        });\n\n        it('paste 99% => 99% (no extra percent sign)', () => {\n            expect(\n                postprocessor(\n                    {value: '99%', selection: [3, 3]}, // after\n                    // paste from clipboard\n                    EMPTY_INPUT, // before\n                ),\n            ).toEqual({value: '99%', selection: [3, 3]});\n        });\n    });\n\n    describe('postfix consists of many characters', () => {\n        describe('postfix=.00', () => {\n            const postprocessor = maskitoPostfixPostprocessorGenerator('.00');\n\n            it('does not add postfix if input was initially empty', () => {\n                expect(postprocessor(EMPTY_INPUT, EMPTY_INPUT)).toEqual(EMPTY_INPUT);\n            });\n\n            it('type 100 => 100.00', () => {\n                expect(\n                    postprocessor(\n                        {value: '100', selection: [3, 3]}, // after\n                        EMPTY_INPUT, // before\n                    ),\n                ).toEqual({value: '100.00', selection: [3, 3]});\n            });\n\n            it('100.0 => 100.00', () => {\n                expect(\n                    postprocessor(\n                        {value: '100.0', selection: [5, 5]}, // after\n                        // attempt to delete character from postfix\n                        {value: '100.00', selection: [6, 6]}, // before\n                    ),\n                ).toEqual({value: '100.00', selection: [5, 5]});\n            });\n\n            it('100. => 100.00', () => {\n                expect(\n                    postprocessor(\n                        {value: '100.', selection: [4, 4]}, // after\n                        // attempt to delete many characters from postfix\n                        {value: '100.00', selection: [6, 6]}, // before\n                    ),\n                ).toEqual({value: '100.00', selection: [4, 4]});\n            });\n        });\n\n        describe('postfix=_lbs_per_day', () => {\n            const postprocessor = maskitoPostfixPostprocessorGenerator('_lbs_per_day');\n\n            it('paste 100 + partially filled postfix => 100_lbs_per_day', () => {\n                expect(\n                    postprocessor(\n                        {\n                            value: '100_lbs',\n                            selection: ['100_lbs'.length, '100_lbs'.length],\n                        },\n                        EMPTY_INPUT,\n                    ),\n                ).toEqual({\n                    value: '100_lbs_per_day',\n                    selection: ['100_lbs'.length, '100_lbs'.length],\n                });\n            });\n        });\n    });\n\n    describe('postfix starts with the same character as other part of the value ends', () => {\n        it('$_100_per_kg => $_|100_|per_kg (select all digits and underscore) => Delete => $_|_per_kg', () => {\n            const postprocessor = maskitoPostfixPostprocessorGenerator('_per_kg');\n\n            expect(\n                postprocessor(\n                    {value: '$_per_kg', selection: [2, 2]}, // after\n                    {value: '$_100_per_kg', selection: ['$_'.length, '$_100_'.length]}, // initial\n                ),\n            ).toEqual({value: '$__per_kg', selection: [2, 2]});\n        });\n\n        it('$__100__per_kg => $__|100__|per_kg (select all digits and 2 underscore) => Delete => $__|__per_kg', () => {\n            const postprocessor = maskitoPostfixPostprocessorGenerator('__per_kg');\n\n            expect(\n                postprocessor(\n                    {value: '$__per_kg', selection: [3, 3]}, // after\n                    {\n                        value: '$__100__per_kg',\n                        selection: ['$__'.length, '$__100__'.length],\n                    }, // initial\n                ),\n            ).toEqual({value: '$____per_kg', selection: [3, 3]});\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/processors/tests/prefix-postprocessor.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\nimport type {MaskitoPostprocessor} from '@maskito/core';\n\nimport {maskitoPrefixPostprocessorGenerator} from '../prefix-postprocessor';\n\ntype ElementState = ReturnType<MaskitoPostprocessor>;\n\ndescribe('maskitoPrefixPostprocessorGenerator', () => {\n    const EMPTY_INPUT = {value: '', selection: [0, 0] as const};\n\n    describe('prefix is a single character', () => {\n        const postprocessor = maskitoPrefixPostprocessorGenerator('$');\n\n        it('does not add prefix if input was initially empty', () => {\n            expect(postprocessor(EMPTY_INPUT, EMPTY_INPUT)).toEqual(EMPTY_INPUT);\n        });\n\n        it('123 => $|123', () => {\n            expect(\n                postprocessor(\n                    {value: '123', selection: [0, 0]}, // after changes\n                    // percent sign was deleted by backspace\n                    {value: '$123', selection: [1, 1]}, // before changes\n                ),\n            ).toEqual({value: '$123', selection: [1, 1]});\n        });\n\n        it('paste $123 => $123 (no extra dollar sign)', () => {\n            expect(\n                postprocessor(\n                    {value: '$123', selection: [4, 4]}, // after\n                    // // paste from clipboard\n                    EMPTY_INPUT, // before\n                ),\n            ).toEqual({value: '$123', selection: [4, 4]});\n        });\n    });\n\n    describe('prefix consists of many characters', () => {\n        const postprocessor = maskitoPrefixPostprocessorGenerator('kg ');\n\n        it('does not add prefix if input was initially empty', () => {\n            expect(postprocessor(EMPTY_INPUT, EMPTY_INPUT)).toEqual(EMPTY_INPUT);\n        });\n\n        it('123 => kg |123', () => {\n            expect(\n                postprocessor(\n                    {value: '123', selection: [0, 0]}, // after\n                    // all characters from prefix was deleted\n                    {value: 'kg 123', selection: [3, 3]}, // before\n                ),\n            ).toEqual({value: 'kg 123', selection: [3, 3]});\n        });\n\n        it('g 123 => kg |123', () => {\n            expect(\n                postprocessor(\n                    {value: 'g 123', selection: [0, 0]}, // after\n                    // leading character from prefix was deleted\n                    {value: 'kg 123', selection: [1, 1]}, // before\n                ),\n            ).toEqual({value: 'kg 123', selection: [1, 1]});\n        });\n\n        it(' 123 => kg |123', () => {\n            expect(\n                postprocessor(\n                    {value: ' 123', selection: [0, 0]}, // after\n                    // some characters from prefix was deleted\n                    {value: 'kg 123', selection: [2, 2]}, // before\n                ),\n            ).toEqual({value: 'kg 123', selection: [2, 2]});\n        });\n\n        describe('Textfield is empty => Type any character from prefix', () => {\n            const process = (elementState: ElementState): ElementState =>\n                postprocessor(elementState, EMPTY_INPUT);\n\n            it('empty input => type k (part of prefix) => kg |', () => {\n                expect(process({value: 'k', selection: [1, 1]})).toEqual({\n                    value: 'kg ',\n                    selection: [3, 3],\n                });\n            });\n\n            it('empty input => type g (part of prefix) => kg |', () => {\n                expect(process({value: 'g', selection: [1, 1]})).toEqual({\n                    value: 'kg ',\n                    selection: [3, 3],\n                });\n            });\n\n            it('empty input => type space (part of prefix) => kg |', () => {\n                expect(process({value: ' ', selection: [1, 1]})).toEqual({\n                    value: 'kg ',\n                    selection: [3, 3],\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/processors/tests/valid-date-preprocessor.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {createValidDatePreprocessor} from '../valid-date-preprocessor';\n\ndescribe('createValidDatePreprocessor', () => {\n    describe('Paste/Drop of many characters', () => {\n        const preprocessor = createValidDatePreprocessor({\n            dateModeTemplate: 'dd.mm.yyyy',\n            dateSegmentsSeparator: '.',\n            rangeSeparator: ' – ',\n        });\n        const EMPTY_INPUT = {value: '', selection: [0, 0] as [number, number]};\n\n        const check = (insertedCharacters: string, expectedValue: string): void => {\n            const {data} = preprocessor(\n                {elementState: EMPTY_INPUT, data: insertedCharacters},\n                'insert',\n            );\n\n            expect(data).toEqual(expectedValue);\n        };\n\n        it('empty input => 06.02.2023 – 07.02.2023 (non-breaking spaces)', () => {\n            check('06.02.2023 – 07.02.2023', '06.02.2023 – 07.02.2023');\n        });\n\n        it('empty input => 06.02.2023 – 07.02.2023 (basic spaces)', () => {\n            check('06.02.2023 – 07.02.2023', '06.02.202307.02.2023');\n        });\n\n        it('empty input => 06.02.2023–07.02.2023', () => {\n            check('06.02.2023–07.02.2023', '06.02.202307.02.2023');\n        });\n\n        it('empty input => 06.02.202307.02.2023', () => {\n            check('06.02.202307.02.2023', '06.02.202307.02.2023');\n        });\n\n        it('empty input => 0602202307022023', () => {\n            check('0602202307022023', '06.02.202307.02.2023');\n        });\n    });\n\n    it('ignores range separator', () => {\n        const rangeSeparator = ' – ';\n        const processor = createValidDatePreprocessor({\n            rangeSeparator,\n            dateModeTemplate: 'dd.mm.yyyy',\n            dateSegmentsSeparator: '.',\n        });\n\n        const initialState = {\n            value: '06.02.2023',\n            selection: ['06.02.2023'.length, '06.02.2023'.length] as const,\n        };\n        const {elementState, data} = processor(\n            {elementState: initialState, data: rangeSeparator},\n            'insert',\n        );\n\n        expect(elementState).toEqual(initialState);\n        expect(data).toBe(rangeSeparator);\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/processors/tests/with-placeholder.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\nimport {maskitoPipe} from '@maskito/core';\nimport {maskitoWithPlaceholder} from '@maskito/kit';\n\ndescribe('maskitoWithPlaceholder(\"dd/mm/yyyy\")', () => {\n    const {preprocessors, postprocessors} = maskitoWithPlaceholder('dd/mm/yyyy');\n    const preprocessor = maskitoPipe(preprocessors);\n    const postprocessor = maskitoPipe(postprocessors);\n    const EMPTY_ELEMENT_STATE = {\n        value: '',\n        selection: [0, 0] as const,\n    };\n\n    describe('preprocessors', () => {\n        const check = (valueBefore: string, valueAfter: string): void => {\n            const {elementState} = preprocessor(\n                {\n                    elementState: {\n                        value: valueBefore,\n                        selection: [0, 0] as const,\n                    },\n                    data: '',\n                },\n                'insert',\n            );\n\n            expect(elementState.value).toBe(valueAfter);\n        };\n\n        it('empty', () => check('', ''));\n\n        it('2/mm/yyyy => 2', () => check('2d/mm/yyyy', '2'));\n\n        it('26/mm/yyyy => 26', () => check('26/mm/yyyy', '26'));\n\n        it('26/0m/yyyy => 26/0', () => check('26/0m/yyyy', '26/0'));\n\n        it('26/01/yyyy => 26/01', () => check('26/01/yyyy', '26/01'));\n\n        it('26/01/1yyy => 26/01/1', () => check('26/01/1yyy', '26/01/1'));\n\n        it('26/01/19yy => 26/01/19', () => check('26/01/19yy', '26/01/19'));\n\n        it('26/01/199y => 26/01/199', () => check('26/01/199y', '26/01/199'));\n\n        it('26/01/1997 => 26/01/1997', () => check('26/01/1997', '26/01/1997'));\n    });\n\n    describe('postprocessors', () => {\n        beforeEach(() => {\n            // Reset side effects from other tests\n            preprocessor({elementState: EMPTY_ELEMENT_STATE, data: ''}, 'validation');\n        });\n\n        describe('different initial element state (2nd argument of postprocessor)', () => {\n            const ONLY_PLACEHOLDER_STATE = {\n                value: 'dd/mm/yyyy',\n                selection: [0, 0] as const,\n            };\n\n            [EMPTY_ELEMENT_STATE, ONLY_PLACEHOLDER_STATE].forEach((initialState) => {\n                const check = (valueBefore: string, valueAfter: string): void => {\n                    const {value} = postprocessor(\n                        {\n                            value: valueBefore,\n                            selection: [0, 0] as const,\n                        },\n                        initialState,\n                    );\n\n                    expect(value).toBe(valueAfter);\n                };\n\n                describe(`Initial element value is \"${initialState.value}\"`, () => {\n                    it('1 => 1d/mm/yyyy', () => check('1', '1d/mm/yyyy'));\n\n                    it('16 => 16/mm/yyyy', () => check('16', '16/mm/yyyy'));\n\n                    it('16/0 => 16/0m/yyyy', () => check('16/0', '16/0m/yyyy'));\n\n                    it('16/05 => 16/05/yyyy', () => check('16/05', '16/05/yyyy'));\n\n                    it('16/05/2 => 16/05/2yyy', () => check('16/05/2', '16/05/2yyy'));\n\n                    it('16/05/20 => 16/05/20yy', () => check('16/05/20', '16/05/20yy'));\n\n                    it('16/05/202 => 16/05/202y', () => check('16/05/202', '16/05/202y'));\n\n                    it('16/05/2023 => 16/05/2023', () =>\n                        check('16/05/2023', '16/05/2023'));\n                });\n            });\n\n            describe('postprocessor gets empty value', () => {\n                /**\n                 * We can get this case only if textfield is updated programmatically.\n                 * User can't erase symbols from already empty textfield.\n                 */\n                it('if initial state has empty value too => Empty', () => {\n                    const {value} = postprocessor(\n                        {\n                            value: '',\n                            selection: [0, 0] as const,\n                        },\n                        EMPTY_ELEMENT_STATE,\n                    );\n\n                    expect(value).toBe('');\n                });\n\n                it('initial value is not empty => placeholder', () => {\n                    const {value} = postprocessor(\n                        {\n                            value: '',\n                            selection: [0, 0] as const,\n                        },\n                        ONLY_PLACEHOLDER_STATE,\n                    );\n\n                    expect(value).toBe('dd/mm/yyyy');\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/processors/valid-date-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nimport {escapeRegExp, parseDateRangeString, validateDateString} from '../utils';\n\nexport function createValidDatePreprocessor({\n    dateModeTemplate,\n    dateSegmentsSeparator,\n    rangeSeparator = '',\n}: {\n    dateModeTemplate: string;\n    dateSegmentsSeparator: string;\n    rangeSeparator?: string;\n}): MaskitoPreprocessor {\n    return ({elementState, data}) => {\n        const {value, selection} = elementState;\n\n        if (data === dateSegmentsSeparator) {\n            return {\n                elementState,\n                data: selection[0] === value.length ? data : '',\n            };\n        }\n\n        if (!data.replaceAll(/\\D/g, '')) {\n            return {elementState, data};\n        }\n\n        const newCharacters = data.replaceAll(\n            new RegExp(\n                String.raw`[^\\d${escapeRegExp(dateSegmentsSeparator)}${rangeSeparator}]`,\n                'g',\n            ),\n            '',\n        );\n\n        const [from, rawTo] = selection;\n        let to = rawTo + data.length;\n        const newPossibleValue = `${value.slice(0, from)}${newCharacters}${value.slice(to)}`;\n        const dateStrings = parseDateRangeString(\n            newPossibleValue,\n            dateModeTemplate,\n            rangeSeparator,\n        );\n\n        let validatedValue = '';\n        const hasRangeSeparator =\n            Boolean(rangeSeparator) && newPossibleValue.includes(rangeSeparator);\n\n        for (const dateString of dateStrings) {\n            const {validatedDateString, updatedSelection} = validateDateString({\n                dateString,\n                dateModeTemplate,\n                dateSegmentsSeparator,\n                offset: validatedValue.length,\n                selection: [from, to],\n            });\n\n            if (dateString && !validatedDateString) {\n                return {elementState, data: ''}; // prevent changes\n            }\n\n            to = updatedSelection[1];\n\n            validatedValue +=\n                hasRangeSeparator && !validatedValue\n                    ? `${validatedDateString}${rangeSeparator}`\n                    : validatedDateString;\n        }\n\n        const newData = validatedValue.slice(from, to);\n\n        return {\n            elementState: {\n                selection,\n                value: `${validatedValue.slice(0, from)}${newData\n                    .split(dateSegmentsSeparator)\n                    .map((segment) => '0'.repeat(segment.length))\n                    .join(dateSegmentsSeparator)}${validatedValue.slice(to)}`,\n            },\n            data: newData,\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/with-placeholder.ts",
    "content": "import {\n    type MaskitoOptions,\n    type MaskitoPreprocessor,\n    maskitoUpdateElement,\n} from '@maskito/core';\n\nimport {maskitoCaretGuard, maskitoEventHandler} from '../plugins';\n\nexport function maskitoWithPlaceholder(\n    placeholder: string,\n    focusedOnly = false,\n): Pick<Required<MaskitoOptions>, 'plugins' | 'postprocessors' | 'preprocessors'> & {\n    removePlaceholder: (value: string) => string;\n} {\n    let lastClearValue = '';\n    let action: Parameters<MaskitoPreprocessor>[1] = 'validation';\n    const removePlaceholder = (value: string): string => {\n        for (let i = value.length - 1; i >= lastClearValue.length; i--) {\n            if (value[i] !== placeholder[i]) {\n                return value.slice(0, i + 1);\n            }\n        }\n\n        return value.slice(0, lastClearValue.length);\n    };\n    const plugins = [maskitoCaretGuard((value) => [0, removePlaceholder(value).length])];\n\n    let focused = false;\n\n    if (focusedOnly) {\n        const focus = maskitoEventHandler(\n            'focus',\n            (element) => {\n                focused = true;\n                maskitoUpdateElement(\n                    element,\n                    `${element.value}${placeholder.slice(element.value.length)}`,\n                );\n            },\n            {capture: true},\n        );\n\n        const blur = maskitoEventHandler(\n            'blur',\n            (element) => {\n                focused = false;\n                maskitoUpdateElement(element, removePlaceholder(element.value));\n            },\n            {capture: true},\n        );\n\n        plugins.push(focus, blur);\n    }\n\n    return {\n        plugins,\n        removePlaceholder,\n        preprocessors: [\n            ({elementState, data}, actionType) => {\n                action = actionType;\n                const {value, selection} = elementState;\n\n                return {\n                    elementState: {\n                        selection,\n                        value: removePlaceholder(value),\n                    },\n                    data,\n                };\n            },\n        ],\n        postprocessors: [\n            ({value, selection}, initialElementState) => {\n                lastClearValue = value;\n\n                const justPlaceholderRemoval =\n                    `${value}${placeholder.slice(\n                        value.length,\n                        initialElementState.value.length,\n                    )}` === initialElementState.value;\n\n                if (action === 'validation' && justPlaceholderRemoval) {\n                    /**\n                     * If `value` still equals to `initialElementState.value`,\n                     * then it means that value is patched programmatically (from Maskito's plugin or externally).\n                     * In this case, we don't want to mutate value and automatically add/remove placeholder.\n                     * ___\n                     * For example, developer wants to remove manually placeholder (+ do something else with value) on blur.\n                     * Without this condition, placeholder will be unexpectedly added again.\n                     */\n                    return {selection, value: initialElementState.value};\n                }\n\n                const newValue =\n                    focused || !focusedOnly\n                        ? `${value}${placeholder.slice(value.length)}`\n                        : value;\n\n                if (\n                    newValue === initialElementState.value &&\n                    action === 'deleteBackward'\n                ) {\n                    const [caretIndex] = initialElementState.selection;\n\n                    return {\n                        value: newValue,\n                        selection: [caretIndex, caretIndex],\n                    };\n                }\n\n                return {value: newValue, selection};\n            },\n        ],\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/processors/zero-placeholders-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nexport function createZeroPlaceholdersPreprocessor(postfix = ''): MaskitoPreprocessor {\n    const isLastChar = (value: string, [_, to]: readonly [number, number]): boolean =>\n        to >= value.length - postfix.length;\n\n    return ({elementState}, actionType) => {\n        const {value, selection} = elementState;\n\n        if (!value || isLastChar(value, selection)) {\n            return {elementState};\n        }\n\n        const [from, to] = selection;\n\n        const zeroes = value.slice(from, to).replaceAll(/\\d/g, '0');\n        const newValue = `${value.slice(0, from)}${zeroes}${value.slice(to)}`;\n\n        if (!zeroes.replaceAll(/\\D/g, '')) {\n            return {elementState};\n        }\n\n        if (actionType === 'validation' || (actionType === 'insert' && from === to)) {\n            return {elementState: {selection, value: newValue}};\n        }\n\n        return {\n            elementState: {\n                selection:\n                    actionType === 'deleteBackward' || actionType === 'insert'\n                        ? [from, from]\n                        : [to, to],\n                value: newValue,\n            },\n        };\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/types/date-mode.ts",
    "content": "export type MaskitoDateMode =\n    | 'dd/mm'\n    | 'dd/mm/yyyy'\n    | 'mm/dd'\n    | 'mm/dd/yyyy'\n    | 'mm/yy'\n    | 'mm/yyyy'\n    | 'yyyy'\n    | 'yyyy/mm'\n    | 'yyyy/mm/dd';\n"
  },
  {
    "path": "projects/kit/src/lib/types/date-segments.ts",
    "content": "export interface MaskitoDateSegments<T = string> {\n    day: T;\n    month: T;\n    year: T;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/types/index.ts",
    "content": "export * from './date-mode';\nexport * from './date-segments';\nexport * from './time-mode';\nexport * from './time-segments';\n"
  },
  {
    "path": "projects/kit/src/lib/types/time-mode.ts",
    "content": "export type MaskitoTimeMode =\n    | 'HH AA'\n    | 'HH:MM AA'\n    | 'HH:MM:SS AA'\n    | 'HH:MM:SS.MSS AA'\n    | 'HH:MM:SS.MSS'\n    | 'HH:MM:SS'\n    | 'HH:MM'\n    | 'HH'\n    | 'MM:SS.MSS'\n    | 'MM:SS'\n    | 'SS.MSS';\n"
  },
  {
    "path": "projects/kit/src/lib/types/time-segments.ts",
    "content": "export interface MaskitoTimeSegments<T = string> {\n    hours: T;\n    minutes: T;\n    seconds: T;\n    milliseconds: T;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/clamp.ts",
    "content": "/**\n * Clamps a value between two inclusive limits\n */\nexport function clamp<T extends Date | bigint | number>(\n    value: T,\n    minimum: T | null,\n    maximum?: T | null,\n): T {\n    const minClamped = max(minimum ?? value, value);\n\n    return min(maximum ?? minClamped, minClamped);\n}\n\nfunction min<T extends Date | bigint | number>(x: T, ...values: T[]): T {\n    return values.reduce((a, b) => (a < b ? a : b), x);\n}\n\nfunction max<T extends Date | bigint | number>(x: T, ...values: T[]): T {\n    return values.reduce((a, b) => (a > b ? a : b), x);\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/count-digits.ts",
    "content": "export function countDigits(str: string): number {\n    return str.replaceAll(/\\W/g, '').length;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/append-date.ts",
    "content": "import {MIN_DAY, MonthNumber, MONTHS_IN_YEAR} from '../../masks/date-range/constants';\nimport type {MaskitoDateSegments} from '../../types';\n\nexport function appendDate(\n    date: Date,\n    {day = 0, month = 0, year = 0}: Partial<MaskitoDateSegments<number>> = {},\n): Date {\n    if (day === 0 && month === 0 && year === 0) {\n        return date;\n    }\n\n    const initialYear = date.getFullYear();\n    const initialMonth = date.getMonth();\n    const initialDate = date.getDate();\n\n    const totalMonths = (initialYear + year) * MONTHS_IN_YEAR + initialMonth + month;\n    let years = Math.floor(totalMonths / MONTHS_IN_YEAR);\n    let months = totalMonths % MONTHS_IN_YEAR;\n\n    const monthDaysCount = getMonthDaysCount(months, isLeapYear(years));\n    const currentMonthDaysCount = getMonthDaysCount(initialMonth, isLeapYear(years));\n    let days = day;\n\n    if (initialDate >= monthDaysCount) {\n        days += initialDate - (currentMonthDaysCount - monthDaysCount);\n    } else if (\n        currentMonthDaysCount < monthDaysCount &&\n        initialDate === currentMonthDaysCount\n    ) {\n        days += initialDate + (monthDaysCount - currentMonthDaysCount);\n    } else {\n        days += initialDate;\n    }\n\n    while (days > getMonthDaysCount(months, isLeapYear(years))) {\n        days -= getMonthDaysCount(months, isLeapYear(years));\n\n        if (months === MonthNumber.December) {\n            years++;\n            months = MonthNumber.January;\n        } else {\n            months++;\n        }\n    }\n\n    while (days < MIN_DAY) {\n        if (months === MonthNumber.January) {\n            years--;\n            months = MonthNumber.December;\n        } else {\n            months--;\n        }\n\n        days += getMonthDaysCount(months, isLeapYear(years));\n    }\n\n    days =\n        day < 0 || month < 0 || year < 0\n            ? days + 1 // add one day when moving days, or months, or years backward\n            : days - 1; // \"from\"-day is included in the range\n\n    return new Date(years, months, days);\n}\n\nfunction getMonthDaysCount(month: number, isLeapYear: boolean): number {\n    switch (month) {\n        case MonthNumber.April:\n        case MonthNumber.June:\n        case MonthNumber.November:\n        case MonthNumber.September:\n            return 30;\n        case MonthNumber.February:\n            return isLeapYear ? 29 : 28;\n        default:\n            return 31;\n    }\n}\n\nfunction isLeapYear(year: number): boolean {\n    return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0);\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/date-segment-value-length.ts",
    "content": "import type {MaskitoDateSegments} from '../../types';\n\nexport const getDateSegmentValueLength: (\n    dateString: string,\n) => MaskitoDateSegments<number> = (dateString: string) => ({\n    day: dateString.match(/d/g)?.length ?? 0,\n    month: dateString.match(/m/g)?.length ?? 0,\n    year: dateString.match(/y/g)?.length ?? 0,\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/date-to-segments.ts",
    "content": "import type {MaskitoDateSegments, MaskitoTimeSegments} from '../../types';\n\nexport function dateToSegments(date: Date): MaskitoDateSegments & MaskitoTimeSegments {\n    return {\n        day: String(date.getDate()).padStart(2, '0'),\n        month: String(date.getMonth() + 1).padStart(2, '0'),\n        year: String(date.getFullYear()).padStart(4, '0'),\n        hours: String(date.getHours()).padStart(2, '0'),\n        minutes: String(date.getMinutes()).padStart(2, '0'),\n        seconds: String(date.getSeconds()).padStart(2, '0'),\n        milliseconds: String(date.getMilliseconds()).padStart(3, '0'),\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/get-date-segments-order.ts",
    "content": "import type {MaskitoDateSegments} from '../../types';\n\nconst ALL_POSSIBLE_SEGMENTS: ReadonlyArray<keyof MaskitoDateSegments> = [\n    'day',\n    'month',\n    'year',\n];\n\nexport function getDateSegmentsOrder(\n    template: string,\n): ReadonlyArray<keyof MaskitoDateSegments> {\n    return [...ALL_POSSIBLE_SEGMENTS].sort((a, b) =>\n        template.indexOf(a[0]!) > template.indexOf(b[0]!) ? 1 : -1,\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/get-first-complete-date.ts",
    "content": "import {countDigits} from '../count-digits';\n\nexport function getFirstCompleteDate(\n    dateString: string,\n    dateModeTemplate: string,\n): string {\n    const digitsInDate = countDigits(dateModeTemplate);\n    const [completeDate = ''] =\n        new RegExp(String.raw`(\\D*\\d){${digitsInDate}}`).exec(dateString) || [];\n\n    return completeDate;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/is-date-string-complete.ts",
    "content": "export function isDateStringComplete(\n    dateString: string,\n    dateModeTemplate: string,\n): boolean {\n    if (dateString.length < dateModeTemplate.length) {\n        return false;\n    }\n\n    return dateString.split(/\\D/).every((segment) => !/^0+$/.exec(segment));\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/parse-date-range-string.ts",
    "content": "import {countDigits} from '../count-digits';\n\nexport function parseDateRangeString(\n    dateRange: string,\n    dateModeTemplate: string,\n    rangeSeparator: string,\n): string[] {\n    const digitsInDate = countDigits(dateModeTemplate);\n\n    return (\n        dateRange\n            .replace(rangeSeparator, '')\n            .match(new RegExp(String.raw`(\\D*\\d[^\\d\\s]*){1,${digitsInDate}}`, 'g')) || []\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/parse-date-string.ts",
    "content": "import type {MaskitoDateSegments} from '../../types';\n\nexport function parseDateString(\n    dateString: string,\n    fullMode: string,\n): Partial<MaskitoDateSegments> {\n    const cleanMode = fullMode.replaceAll(/[^dmy]/g, '');\n    const onlyDigitsDate = dateString.replaceAll(/\\D+/g, '');\n\n    const dateSegments: MaskitoDateSegments = {\n        day: onlyDigitsDate.slice(cleanMode.indexOf('d'), cleanMode.lastIndexOf('d') + 1),\n        month: onlyDigitsDate.slice(\n            cleanMode.indexOf('m'),\n            cleanMode.lastIndexOf('m') + 1,\n        ),\n        year: onlyDigitsDate.slice(\n            cleanMode.indexOf('y'),\n            cleanMode.lastIndexOf('y') + 1,\n        ),\n    };\n\n    return Object.fromEntries(\n        Object.entries(dateSegments)\n            .filter(([_, value]) => Boolean(value))\n            .sort(([a], [b]) =>\n                fullMode.toLowerCase().indexOf(a.slice(0, 1)) >\n                fullMode.toLowerCase().indexOf(b.slice(0, 1))\n                    ? 1\n                    : -1,\n            ),\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/raise-segment-value-to-min.ts",
    "content": "import type {MaskitoDateSegments} from '../../types';\nimport {getDateSegmentValueLength} from './date-segment-value-length';\n\nexport function raiseSegmentValueToMin(\n    segments: Partial<MaskitoDateSegments>,\n    fullMode: string,\n): Partial<MaskitoDateSegments> {\n    const segmentsLength = getDateSegmentValueLength(fullMode);\n\n    return Object.fromEntries(\n        Object.entries<string>(segments).map(([key, value]: [string, string]) => {\n            const segmentLength =\n                segmentsLength[key as keyof Partial<MaskitoDateSegments>];\n\n            return [\n                key,\n                value.length === segmentLength && /^0+$/.exec(value)\n                    ? '1'.padStart(segmentLength, '0')\n                    : value,\n            ];\n        }),\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/segments-to-date.ts",
    "content": "import type {MaskitoDateSegments, MaskitoTimeSegments} from '../../types';\n\nexport function segmentsToDate(\n    parsedDate: Partial<MaskitoDateSegments>,\n    parsedTime?: Partial<MaskitoTimeSegments>,\n): Date {\n    const year = parsedDate.year?.length === 2 ? `20${parsedDate.year}` : parsedDate.year;\n\n    const date = new Date(\n        Number(year ?? '0'),\n        Number(parsedDate.month ?? '1') - 1,\n        Number(parsedDate.day ?? '1'),\n        Number(parsedTime?.hours ?? '0'),\n        Number(parsedTime?.minutes ?? '0'),\n        Number(parsedTime?.seconds ?? '0'),\n        Number(parsedTime?.milliseconds ?? '0'),\n    );\n\n    // needed for years less than 1900\n    date.setFullYear(Number(year ?? '0'));\n\n    return date;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/tests/append-date.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {appendDate} from '../append-date';\n\nconst y2000m6d15 = new Date(2000, 6, 15);\n\ndescribe('appendDate', () => {\n    it('year: 2000, month: 6, day: 15, if {} was passed', () => {\n        const result = appendDate(y2000m6d15, {});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(6);\n        expect(result.getDate()).toBe(15);\n    });\n\n    it('year: 2000, month: 6, day: 15, if {year: 0} was passed', () => {\n        const result = appendDate(y2000m6d15, {year: 0});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(6);\n        expect(result.getDate()).toBe(15);\n    });\n\n    it('year: 2000, month: 6, day: 15, if {year: 0, month: 0} was passed', () => {\n        const result = appendDate(y2000m6d15, {year: 0, month: 0});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(6);\n        expect(result.getDate()).toBe(15);\n    });\n\n    it('year: 2000, month: 6, day: 15, if {year: 0, month: 0, day: 0} was passed', () => {\n        const result = appendDate(y2000m6d15, {\n            year: 0,\n            month: 0,\n            day: 0,\n        });\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(6);\n        expect(result.getDate()).toBe(15);\n    });\n\n    it('year: 2005, month: 6, day: 14, if {year: 5} was passed', () => {\n        const result = appendDate(y2000m6d15, {year: 5});\n\n        expect(result.getFullYear()).toBe(2005);\n        expect(result.getMonth()).toBe(6);\n        expect(result.getDate()).toBe(14);\n    });\n\n    it('year: 1995, month: 6, day: 16, if {year: -5} was passed', () => {\n        const result = appendDate(y2000m6d15, {year: -5});\n\n        expect(result.getFullYear()).toBe(1995);\n        expect(result.getMonth()).toBe(6);\n        expect(result.getDate()).toBe(16);\n    });\n\n    it('year: 2000, month: 11, day: 14, if {month: 5} was passed', () => {\n        const result = appendDate(y2000m6d15, {month: 5});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(11);\n        expect(result.getDate()).toBe(14);\n    });\n\n    it('year: 2000, month: 1, day: 16, if {month: -5} was passed', () => {\n        const result = appendDate(y2000m6d15, {month: -5});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(1);\n        expect(result.getDate()).toBe(16);\n    });\n\n    it('year: 2000, month: 6, day: 19, if {day: 5} was passed', () => {\n        const result = appendDate(y2000m6d15, {day: 5});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(6);\n        expect(result.getDate()).toBe(19);\n    });\n\n    it('year: 2000, month: 6, day: 11, if {day: -5} was passed', () => {\n        const result = appendDate(y2000m6d15, {day: -5});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(6);\n        expect(result.getDate()).toBe(11);\n    });\n\n    it('year: 2000, month: 6, day: 31, if {day: 17} was passed', () => {\n        const result = appendDate(y2000m6d15, {day: 17});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(6);\n        expect(result.getDate()).toBe(31);\n    });\n\n    it('year: 2000, month: 11, day: 30, if {day: 169} was passed', () => {\n        const result = appendDate(y2000m6d15, {day: 169});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(11);\n        expect(result.getDate()).toBe(30);\n    });\n\n    it('year: 2000, month: 11, day: 31, if {day: 170} was passed', () => {\n        const result = appendDate(y2000m6d15, {day: 170});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(11);\n        expect(result.getDate()).toBe(31);\n    });\n\n    it('year: 2000, month: 0, day: 1, if {day: -197} was passed', () => {\n        const result = appendDate(y2000m6d15, {day: -197});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(0);\n        expect(result.getDate()).toBe(1);\n    });\n\n    it('year: 2000, month: 6, day: 1, if {day: -15} was passed', () => {\n        const result = appendDate(y2000m6d15, {day: -15});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(6);\n        expect(result.getDate()).toBe(1);\n    });\n\n    it('year: 2000, month: 2, day: 30, if {month: -4, day: 14} was passed', () => {\n        const result = appendDate(y2000m6d15, {month: -4, day: 14});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(2);\n        expect(result.getDate()).toBe(30);\n    });\n\n    it('year: 2000, month: 0, day: 1, if {month: -6, day: -15} was passed', () => {\n        const result = appendDate(y2000m6d15, {month: -6, day: -15});\n\n        expect(result.getFullYear()).toBe(2000);\n        expect(result.getMonth()).toBe(0);\n        expect(result.getDate()).toBe(1);\n    });\n\n    it('year: 1999, month: 0, day: 1, if {month: -6, day: -15, year: -1} was passed', () => {\n        const result = appendDate(y2000m6d15, {month: -6, day: -15, year: -1});\n\n        expect(result.getFullYear()).toBe(1999);\n        expect(result.getMonth()).toBe(0);\n        expect(result.getDate()).toBe(1);\n    });\n\n    it('year: 2018, month: 1, day: 27, if {month: 1} was passed', () => {\n        const result = appendDate(new Date(2018, 0, 31), {month: 1});\n\n        expect(result.getFullYear()).toBe(2018);\n        expect(result.getMonth()).toBe(1);\n        expect(result.getDate()).toBe(27);\n    });\n\n    it('year: 2025, month: 0, day: 31, if {month: 1} was passed', () => {\n        const result = appendDate(new Date(2025, 0, 1), {month: 1});\n\n        expect(result.getFullYear()).toBe(2025);\n        expect(result.getMonth()).toBe(0);\n        expect(result.getDate()).toBe(31);\n    });\n\n    it('year: 2025, month: 8, day: 1, if {month: -1} was passed (for the last day of month)', () => {\n        const result = appendDate(new Date(2025, 8, 30), {month: -1});\n\n        expect(result.getFullYear()).toBe(2025);\n        expect(result.getMonth()).toBe(8);\n        expect(result.getDate()).toBe(1);\n    });\n\n    it('year: 2025, month: 8, day: 30, if {month: 1} was passed', () => {\n        const result = appendDate(new Date(2025, 8, 1), {month: 1});\n\n        expect(result.getFullYear()).toBe(2025);\n        expect(result.getMonth()).toBe(8);\n        expect(result.getDate()).toBe(30);\n    });\n\n    it('year: 2018, month: 2, day: 1, if {month: -1} was passed (for the last day of month', () => {\n        const result = appendDate(new Date(2018, 2, 31), {month: -1});\n\n        expect(result.getFullYear()).toBe(2018);\n        expect(result.getMonth()).toBe(2);\n        expect(result.getDate()).toBe(1);\n    });\n\n    it('year: 2018, month: 2, day: 31, if {month: 1} was passed', () => {\n        const result = appendDate(new Date(2018, 2, 1), {month: 1});\n\n        expect(result.getFullYear()).toBe(2018);\n        expect(result.getMonth()).toBe(2);\n        expect(result.getDate()).toBe(31);\n    });\n\n    it('year: 2018, month: 1, day: 27, if {month: -1} was passed (when the current month has more days than the final month, and the final month don`t has the day)', () => {\n        const result = appendDate(new Date(2018, 2, 29), {month: -1});\n\n        expect(result.getFullYear()).toBe(2018);\n        expect(result.getMonth()).toBe(1);\n        expect(result.getDate()).toBe(27);\n    });\n\n    it('year: 2018, month: 1, day: 27, if {month: -1} was passed', () => {\n        const result = appendDate(new Date(2018, 2, 26), {month: -1});\n\n        expect(result.getFullYear()).toBe(2018);\n        expect(result.getMonth()).toBe(1);\n        expect(result.getDate()).toBe(27);\n    });\n\n    it('year: 2018, month: 2, day: 26, if {month: 1} was passed', () => {\n        const result = appendDate(new Date(2018, 1, 27), {month: 1});\n\n        expect(result.getFullYear()).toBe(2018);\n        expect(result.getMonth()).toBe(2);\n        expect(result.getDate()).toBe(26);\n    });\n\n    it('year: 2018, month: 1, day: 26, if {month: -1} was passed (when the current month has more days than the final month, but both have the day, and it`s the last day of the final month)', () => {\n        const result = appendDate(new Date(2018, 2, 28), {month: -1});\n\n        expect(result.getFullYear()).toBe(2018);\n        expect(result.getMonth()).toBe(1);\n        expect(result.getDate()).toBe(26);\n    });\n\n    it('year: 2018, month: 1, day: 26, if {month: -1} was passed', () => {\n        const result = appendDate(new Date(2018, 2, 25), {month: -1});\n\n        expect(result.getFullYear()).toBe(2018);\n        expect(result.getMonth()).toBe(1);\n        expect(result.getDate()).toBe(26);\n    });\n\n    it('year: 2018, month: 2, day: 25, if {month: 1} was passed', () => {\n        const result = appendDate(new Date(2018, 1, 26), {month: 1});\n\n        expect(result.getFullYear()).toBe(2018);\n        expect(result.getMonth()).toBe(2);\n        expect(result.getDate()).toBe(25);\n    });\n\n    it('year: 2018, month: 1, day: 28, if {month: -1} was passed (when the current month has more days than the final month, but both has the day)', () => {\n        const result = appendDate(new Date(2018, 2, 27), {month: -1});\n\n        expect(result.getFullYear()).toBe(2018);\n        expect(result.getMonth()).toBe(1);\n        expect(result.getDate()).toBe(28);\n    });\n\n    it('year: 2018, month: 1, day: 28, if {month: -1} was passed (when the current month has more days than the final month, and the final month don`t has the day)', () => {\n        const result = appendDate(new Date(2018, 2, 30), {month: -1});\n\n        expect(result.getFullYear()).toBe(2018);\n        expect(result.getMonth()).toBe(1);\n        expect(result.getDate()).toBe(28);\n    });\n\n    it('year: 2018, month: 2, day: 30, if {month: 1} was passed (when the current month has more days than the final month, but both has the day)', () => {\n        const result = appendDate(new Date(2018, 1, 28), {month: 1});\n\n        expect(result.getFullYear()).toBe(2018);\n        expect(result.getMonth()).toBe(2);\n        expect(result.getDate()).toBe(30);\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/tests/get-date-segment-value-length.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {getDateSegmentValueLength} from '../date-segment-value-length';\n\ndescribe('getDateSegmentValueLength', () => {\n    it('short date', () => {\n        expect(getDateSegmentValueLength('mm.yy')).toEqual({day: 0, month: 2, year: 2});\n    });\n\n    it('full date', () => {\n        expect(getDateSegmentValueLength('dd.mm.yyyy')).toEqual({\n            day: 2,\n            month: 2,\n            year: 4,\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/tests/parse-date-range-string.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {parseDateRangeString} from '../parse-date-range-string';\n\ndescribe('parseDateRangeString', () => {\n    const tests = [\n        ['', []],\n        ['1', ['1']],\n        ['13', ['13']],\n        ['13.', ['13.']],\n        ['13.0', ['13.0']],\n        ['13.02', ['13.02']],\n        ['13.02.', ['13.02.']],\n        ['13.02.2023', ['13.02.2023']],\n        ['13.02.2023 ', ['13.02.2023']],\n        ['13.02.2023 –', ['13.02.2023']],\n        ['13.02.2023 – ', ['13.02.2023']],\n        ['13.02.2023 – 14', ['13.02.2023', '14']],\n        ['13.02.2023 – 14.', ['13.02.2023', '14.']],\n        ['13.02.2023 – 14.03.2025', ['13.02.2023', '14.03.2025']],\n        ['13.02.202314.03.2025', ['13.02.2023', '14.03.2025']],\n        ['13.02.202314032025', ['13.02.2023', '14032025']],\n        ['1302202314032025', ['13022023', '14032025']],\n    ] as const;\n\n    tests.forEach(([dateRangeString, expectedParsedDates]) => {\n        it(`${dateRangeString} => ${JSON.stringify(expectedParsedDates)}`, () => {\n            expect(parseDateRangeString(dateRangeString, 'dd.mm.yyyy', ' – ')).toEqual(\n                expectedParsedDates,\n            );\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/to-date-string.ts",
    "content": "import {DATE_TIME_SEPARATOR} from '../../masks/date-time/constants';\nimport type {MaskitoDateSegments, MaskitoTimeSegments} from '../../types';\n\nexport function toDateString(\n    segments: Partial<MaskitoDateSegments>,\n    options: {\n        dateMode: string;\n    },\n): string;\nexport function toDateString(\n    segments: Partial<MaskitoDateSegments & MaskitoTimeSegments>,\n    options: {\n        dateMode: string;\n        dateTimeSeparator: string;\n        timeMode: string;\n    },\n): string;\nexport function toDateString(\n    {\n        day,\n        month,\n        year,\n        hours,\n        minutes,\n        seconds,\n        milliseconds,\n    }: Partial<MaskitoDateSegments & MaskitoTimeSegments>,\n    {\n        dateMode,\n        dateTimeSeparator = DATE_TIME_SEPARATOR,\n        timeMode,\n    }: {\n        dateMode: string;\n        dateTimeSeparator?: string;\n        timeMode?: string;\n    },\n): string {\n    const yearLength = dateMode.match(/y/g)?.length ?? 0;\n    const fullMode = `${dateMode}${timeMode ? `${dateTimeSeparator}${timeMode}` : ''}`;\n\n    return fullMode\n        .replaceAll(/d+/g, day ?? '')\n        .replaceAll(/m+/g, month ?? '')\n        .replaceAll(/y+/g, year?.slice(-yearLength) ?? '')\n        .replaceAll(/H+/g, hours ?? '')\n        .replaceAll('MSS', milliseconds ?? '')\n        .replaceAll(/M+/g, minutes ?? '')\n        .replaceAll(/S+/g, seconds ?? '')\n        .replaceAll(/^\\D+/g, '')\n        .replaceAll(/\\D+$/g, '');\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/date/validate-date-string.ts",
    "content": "import {DATE_SEGMENTS_MAX_VALUES} from '../../constants';\nimport type {MaskitoDateSegments} from '../../types';\nimport {getDateSegmentValueLength} from './date-segment-value-length';\nimport {getDateSegmentsOrder} from './get-date-segments-order';\nimport {parseDateString} from './parse-date-string';\nimport {toDateString} from './to-date-string';\n\nexport function validateDateString({\n    dateString,\n    dateModeTemplate,\n    dateSegmentsSeparator,\n    offset,\n    selection: [from, to],\n}: {\n    dateString: string;\n    dateSegmentsSeparator: string;\n    dateModeTemplate: string;\n    offset: number;\n    selection: [number, number];\n}): {validatedDateString: string; updatedSelection: [number, number]} {\n    const parsedDate = parseDateString(dateString, dateModeTemplate);\n    const dateSegments = Object.entries(parsedDate) as Array<\n        [keyof MaskitoDateSegments, string]\n    >;\n    const segmentsOrder = getDateSegmentsOrder(dateModeTemplate);\n    const validatedDateSegments: Partial<MaskitoDateSegments> = {};\n\n    for (let i = 0; i < dateSegments.length; i++) {\n        const [segmentName, segmentValue] = dateSegments[i]!;\n        const validatedDate = toDateString(validatedDateSegments, {\n            dateMode: dateModeTemplate,\n        });\n        const maxSegmentValue = DATE_SEGMENTS_MAX_VALUES[segmentName];\n\n        const fantomSeparator = validatedDate.length && dateSegmentsSeparator.length;\n\n        const lastSegmentDigitIndex =\n            offset +\n            validatedDate.length +\n            fantomSeparator +\n            getDateSegmentValueLength(dateModeTemplate)[segmentName];\n        const isLastSegmentDigitAdded =\n            lastSegmentDigitIndex >= from && lastSegmentDigitIndex === to;\n\n        if (isLastSegmentDigitAdded && Number(segmentValue) > Number(maxSegmentValue)) {\n            const nextSegment = segmentsOrder[segmentsOrder.indexOf(segmentName) + 1];\n\n            if (!nextSegment || nextSegment === 'year') {\n                // 31.1|0.2010 => Type 9 => 31.1|0.2010\n                return {validatedDateString: '', updatedSelection: [from, to]}; // prevent changes\n            }\n\n            validatedDateSegments[segmentName] = `0${segmentValue.slice(0, -1)}`;\n            dateSegments[i + 1] = [\n                nextSegment,\n                `${segmentValue.slice(-1)}${(dateSegments[i + 1]?.[1] ?? '').slice(1)}`,\n            ];\n            continue;\n        }\n\n        if (isLastSegmentDigitAdded && Number(segmentValue) < 1) {\n            // 31.0|1.2010 => Type 0 => 31.0|1.2010\n            return {validatedDateString: '', updatedSelection: [from, to]}; // prevent changes\n        }\n\n        validatedDateSegments[segmentName] = segmentValue;\n    }\n\n    const validatedDateString = toDateString(validatedDateSegments, {\n        dateMode: dateModeTemplate,\n    });\n    const addedDateSegmentSeparators = validatedDateString.length - dateString.length;\n\n    return {\n        validatedDateString,\n        updatedSelection: [\n            from + addedDateSegmentSeparators,\n            to + addedDateSegmentSeparators,\n        ],\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/dummy.ts",
    "content": "export function identity<T>(x: T): T {\n    return x;\n}\n\n// eslint-disable-next-line  @typescript-eslint/no-empty-function\nexport function noop(): void {}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/escape-reg-exp.ts",
    "content": "/**\n * Copy-pasted solution from lodash\n * @see https://lodash.com/docs/4.17.15#escapeRegExp\n */\n\nconst reRegExpChar = /[\\\\^$.*+?()[\\]{}|]/g;\nconst reHasRegExpChar = new RegExp(reRegExpChar.source);\n\nexport function escapeRegExp(str: string): string {\n    return str && reHasRegExpChar.test(str)\n        ? str.replaceAll(reRegExpChar, String.raw`\\$&`)\n        : str;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/find-common-beginning-substr.ts",
    "content": "export function findCommonBeginningSubstr(a: string, b: string): string {\n    let res = '';\n\n    for (let i = 0; i < a.length; i++) {\n        if (a[i] !== b[i]) {\n            return res;\n        }\n\n        res += a[i];\n    }\n\n    return res;\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/index.ts",
    "content": "export * from './clamp';\nexport * from './count-digits';\nexport * from './date/append-date';\nexport * from './date/date-segment-value-length';\nexport * from './date/date-to-segments';\nexport * from './date/get-date-segments-order';\nexport * from './date/get-first-complete-date';\nexport * from './date/is-date-string-complete';\nexport * from './date/parse-date-range-string';\nexport * from './date/parse-date-string';\nexport * from './date/segments-to-date';\nexport * from './date/to-date-string';\nexport * from './date/validate-date-string';\nexport * from './dummy';\nexport * from './escape-reg-exp';\nexport * from './find-common-beginning-substr';\nexport * from './is-empty';\nexport * from './pad-with-zeroes-until-valid';\nexport * from './to-half-width-colon';\nexport * from './to-half-width-number';\n"
  },
  {
    "path": "projects/kit/src/lib/utils/is-empty.ts",
    "content": "export function isEmpty(entity?: object | null): boolean {\n    return !entity || (typeof entity === 'object' && Object.keys(entity).length === 0);\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/pad-with-zeroes-until-valid.ts",
    "content": "const ALL_ZEROES_RE = /^0+$/;\n\nexport function padWithZeroesUntilValid(\n    segmentValue: string,\n    paddedMaxValue: string,\n    prefixedZeroesCount = 0,\n): {prefixedZeroesCount: number; validatedSegmentValue: string} {\n    const paddedSegmentValue = segmentValue.padEnd(paddedMaxValue.length, '0');\n\n    if (Number(paddedSegmentValue) <= Number(paddedMaxValue)) {\n        return {validatedSegmentValue: segmentValue, prefixedZeroesCount};\n    }\n\n    if (paddedSegmentValue.endsWith('0')) {\n        // 00:|00 => Type 9 => 00:09|\n        return padWithZeroesUntilValid(\n            `0${segmentValue.slice(0, paddedMaxValue.length - 1)}`,\n            paddedMaxValue,\n            prefixedZeroesCount + 1,\n        );\n    }\n\n    const valueWithoutLastChar = segmentValue.slice(0, paddedMaxValue.length - 1);\n\n    if (ALL_ZEROES_RE.exec(valueWithoutLastChar)) {\n        return {validatedSegmentValue: '', prefixedZeroesCount};\n    }\n\n    // |19:00 => Type 2 => 2|0:00\n    return padWithZeroesUntilValid(\n        `${valueWithoutLastChar}0`,\n        paddedMaxValue,\n        prefixedZeroesCount,\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/tests/clamp.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {clamp} from '../clamp';\n\ndescribe('clamp', () => {\n    describe('number', () => {\n        it('returns the original value when it satisfies limits', () => {\n            expect(clamp(5, 0, 10)).toBe(5);\n        });\n\n        it('returns the minimum when the value is below the lower bound', () => {\n            expect(clamp(-5, 0, 10)).toBe(0);\n        });\n\n        it('returns the maximum when the value exceeds the upper bound', () => {\n            expect(clamp(15, 0, 10)).toBe(10);\n        });\n\n        it('ignores a null minimum and keeps enforcing the maximum', () => {\n            expect(clamp(15, null, 10)).toBe(10);\n        });\n\n        it('ignores a null maximum and keeps enforcing the minimum', () => {\n            expect(clamp(-5, 0, null)).toBe(0);\n        });\n\n        it('returns the incoming value when both bounds are null', () => {\n            expect(clamp(7, null, null)).toBe(7);\n        });\n    });\n\n    describe('bigint', () => {\n        it('returns the original value when it satisfies limits', () => {\n            expect(clamp(7n, 0n, 10n)).toBe(7n);\n        });\n\n        it('returns the minimum when the value is below the lower bound', () => {\n            expect(clamp(-5n, 0n, 10n)).toBe(0n);\n        });\n\n        it('returns the maximum when the value exceeds the upper bound', () => {\n            expect(clamp(15n, 0n, 10n)).toBe(10n);\n        });\n\n        it('ignores a null minimum and keeps enforcing the maximum', () => {\n            expect(clamp(15n, null, 10n)).toBe(10n);\n        });\n\n        it('ignores a null maximum and keeps enforcing the minimum', () => {\n            expect(clamp(-5n, 0n, null)).toBe(0n);\n        });\n\n        it('returns the incoming value when both bounds are null', () => {\n            expect(clamp(42n, null, null)).toBe(42n);\n        });\n\n        it('handles negative bigint values', () => {\n            expect(clamp(-10n, -5n, 5n)).toBe(-5n);\n            expect(clamp(-3n, -5n, 5n)).toBe(-3n);\n        });\n    });\n\n    describe('number with decimal part + min/max as bigint', () => {\n        it('clamps decimal values below the bigint lower bound', () => {\n            expect(clamp<bigint | number>(1.5, 2n, 5n)).toBe(2n);\n        });\n\n        it('keeps decimal values intact when they fall within bigint bounds', () => {\n            expect(clamp<bigint | number>(3.123456789, 2n, 5n)).toBe(3.123456789);\n        });\n\n        it('clamps decimal values above the bigint upper bound', () => {\n            expect(clamp<bigint | number>(6.98765432, 2n, 5n)).toBe(5n);\n        });\n    });\n\n    describe('value is bigint + min/max as numbers with decimal point', () => {\n        it('clamps bigint below the numeric lower bound', () => {\n            expect(clamp<bigint | number>(1n, 1.5, 5.5)).toBe(1.5);\n        });\n\n        it('returns bigint when it lies within numeric bounds', () => {\n            expect(clamp<bigint | number>(3n, 1.5, 5.5)).toBe(3n);\n        });\n\n        it('clamps bigint above the numeric upper bound', () => {\n            expect(clamp<bigint | number>(10n, 1.5, 5.5)).toBe(5.5);\n        });\n\n        it('handles negative bigint with decimal bounds', () => {\n            expect(clamp<bigint | number>(-10n, -5.5, 5.5)).toBe(-5.5);\n            expect(clamp<bigint | number>(-3n, -5.5, 5.5)).toBe(-3n);\n        });\n    });\n\n    describe('Date', () => {\n        it('returns the original value (with same reference!) when it falls inside the inclusive range', () => {\n            const lowerBound = new Date('2020-01-01T00:00:00Z');\n            const value = new Date('2020-01-02T00:00:00Z');\n            const upperBound = new Date('2020-01-03T00:00:00Z');\n\n            expect(clamp(value, lowerBound, upperBound)).toBe(value);\n        });\n\n        it('returns the boundary when value falls outside the range', () => {\n            const lowerBound = new Date('2020-01-01T00:00:00Z');\n            const upperBound = new Date('2020-01-03T00:00:00Z');\n\n            expect(clamp(new Date('2019-12-31T23:59:59Z'), lowerBound, upperBound)).toBe(\n                lowerBound,\n            );\n            expect(clamp(new Date('2020-01-03T12:00:00Z'), lowerBound, upperBound)).toBe(\n                upperBound,\n            );\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/tests/escape-reg-exp.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {CHAR_HYPHEN, CHAR_MINUS} from '../../constants';\nimport {escapeRegExp} from '../escape-reg-exp';\n\ndescribe('escapeRegExp', () => {\n    it('escapes dot', () => {\n        const rawRegExpStr = 'a.b';\n        const escaped = escapeRegExp(rawRegExpStr);\n        const testString = '-abb-a.b-';\n\n        expect(escaped).toBe(String.raw`a\\.b`);\n        expect(testString.replace(new RegExp(escaped), '')).toBe('-abb--');\n        expect(testString.replace(new RegExp(rawRegExpStr), '')).toBe('--a.b-');\n    });\n\n    it('escapes dollar sign', () => {\n        const rawRegExpStr = '10$';\n        const escaped = escapeRegExp(rawRegExpStr);\n        const testString = '-10$-10';\n\n        expect(escaped).toBe(String.raw`10\\$`);\n        expect(testString.replace(new RegExp(escaped), '')).toBe('--10');\n        expect(testString.replace(new RegExp(rawRegExpStr), '')).toBe('-10$-');\n    });\n\n    it('escapes plus', () => {\n        const rawRegExpStr = '+';\n        const escaped = escapeRegExp(rawRegExpStr);\n        const testString = '+42';\n\n        expect(escaped).toBe(String.raw`\\+`);\n        expect(testString.replace(new RegExp(escaped), '')).toBe('42');\n\n        expect(() =>\n            // eslint-disable-next-line regexp/no-invalid-regexp\n            testString.replace(new RegExp(rawRegExpStr), ''),\n        ).toThrow(new SyntaxError('Invalid regular expression: /+/: Nothing to repeat'));\n    });\n\n    describe('Symbols which do not require escaping', () => {\n        it('minus', () => {\n            expect(escapeRegExp(CHAR_MINUS)).toBe(CHAR_MINUS);\n            expect(`${CHAR_MINUS}42`.replace(new RegExp(CHAR_MINUS), '')).toBe('42');\n        });\n\n        it('hyphen', () => {\n            expect(escapeRegExp(CHAR_HYPHEN)).toBe(CHAR_HYPHEN);\n            expect(`${CHAR_HYPHEN}42`.replace(new RegExp(CHAR_HYPHEN), '')).toBe('42');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/tests/find-common-beginning-substr.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {findCommonBeginningSubstr} from '../find-common-beginning-substr';\n\ndescribe('findCommonBeginningSubstr', () => {\n    it('returns common substring until all characters are equal', () => {\n        expect(findCommonBeginningSubstr('123_456', '123456')).toBe('123');\n    });\n\n    it('returns empty string if any string is empty', () => {\n        expect(findCommonBeginningSubstr('123_456', '')).toBe('');\n        expect(findCommonBeginningSubstr('', '123_456')).toBe('');\n    });\n\n    it('returns empty string if the first characters are different', () => {\n        expect(findCommonBeginningSubstr('012345', '123')).toBe('');\n    });\n\n    it('returns the whole string if all characters are equal', () => {\n        expect(findCommonBeginningSubstr('777999', '777999')).toBe('777999');\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/tests/get-first-complete-date.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {getFirstCompleteDate} from '../date/get-first-complete-date';\n\ndescribe('getFirstCompleteDate', () => {\n    it('should return the first complete date', () => {\n        expect(getFirstCompleteDate('01.01.2000-11.11.2000', 'DD.MM.YYYY')).toBe(\n            '01.01.2000',\n        );\n        expect(getFirstCompleteDate('01.2000-11.2000', 'MM.YYYY')).toBe('01.2000');\n        expect(getFirstCompleteDate('01.01.2000,23:59', 'DD.MM.YYYY')).toBe('01.01.2000');\n    });\n\n    it('should return empty string if no complete date', () => {\n        expect(getFirstCompleteDate('01.01.20', 'DD.MM.YYYY')).toBe('');\n        expect(getFirstCompleteDate('01.01.200', 'DD.MM.YYYY HH:mm')).toBe('');\n    });\n\n    it('should return empty string if no date', () => {\n        expect(getFirstCompleteDate('', 'DD.MM.YYYY')).toBe('');\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/tests/is-empty.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {isEmpty} from '../is-empty';\n\ndescribe('isEmpty', () => {\n    describe('returns `true` for', () => {\n        it('empty object', () => {\n            expect(isEmpty({})).toBe(true);\n        });\n\n        it('zero-length array', () => {\n            expect(isEmpty([])).toBe(true);\n        });\n\n        it('null', () => {\n            expect(isEmpty(null)).toBe(true);\n        });\n\n        it('undefined', () => {\n            expect(isEmpty()).toBe(true);\n        });\n    });\n\n    describe('returns `false` for', () => {\n        it('non-empty object', () => {\n            expect(isEmpty({name: ''})).toBe(false);\n        });\n\n        it('not zero-length array', () => {\n            expect(isEmpty([0])).toBe(false);\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/tests/to-half-width-colon.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {toHalfWidthColon} from '../to-half-width-colon';\n\ndescribe('`toHalfWidthColon` utility converts full width colon to half width colon', () => {\n    it('： => :', () => {\n        expect(toHalfWidthColon('：')).toBe(':');\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/tests/to-half-width-number.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {toHalfWidthNumber} from '../to-half-width-number';\n\ndescribe('`toHalfWidthNumber` utility converts full width numbers to half width numbers', () => {\n    const tests = [\n        // [full width value, half width value]\n        ['１', '1'],\n        ['２', '2'],\n        ['３', '3'],\n        ['４', '4'],\n        ['５', '5'],\n        ['６', '6'],\n        ['７', '7'],\n        ['８', '8'],\n        ['９', '9'],\n    ] as const;\n\n    tests.forEach(([fullWidthValue, halfWidthValue]) => {\n        it(`${fullWidthValue} => ${halfWidthValue}`, () => {\n            expect(toHalfWidthNumber(fullWidthValue)).toBe(halfWidthValue);\n        });\n    });\n\n    it('１２３４５６ => 123456', () => {\n        expect(toHalfWidthNumber('１２３４５６')).toBe('123456');\n    });\n\n    it('１2３4５6 (full width + half width mix) => 123456', () => {\n        expect(toHalfWidthNumber('１2３4５6')).toBe('123456');\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/time/create-time-mask-expression.ts",
    "content": "import {CHAR_NO_BREAK_SPACE, TIME_FIXED_CHARACTERS} from '../../constants';\nimport type {MaskitoTimeMode} from '../../types';\n\nexport function createTimeMaskExpression(mode: MaskitoTimeMode): Array<RegExp | string> {\n    return Array.from(mode.replace(' AA', ''))\n        .map((char) => (TIME_FIXED_CHARACTERS.includes(char) ? char : /\\d/))\n        .concat(mode.includes('AA') ? [CHAR_NO_BREAK_SPACE, /[AP]/i, /M/i] : []);\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/time/enrich-time-segments-with-zeroes.ts",
    "content": "import {DEFAULT_TIME_SEGMENT_MAX_VALUES, TIME_FIXED_CHARACTERS} from '../../constants';\nimport type {MaskitoTimeMode, MaskitoTimeSegments} from '../../types';\nimport {escapeRegExp} from '../escape-reg-exp';\nimport {padWithZeroesUntilValid} from '../pad-with-zeroes-until-valid';\nimport {padStartTimeSegments} from './pad-start-time-segments';\nimport {parseTimeString} from './parse-time-string';\nimport {toTimeString} from './to-time-string';\n\nconst TRAILING_TIME_SEGMENT_SEPARATOR_REG = new RegExp(\n    `[${TIME_FIXED_CHARACTERS.map(escapeRegExp).join('')}]$`,\n);\n\n/**\n * Pads invalid time segment with zero to make it valid.\n * @example 00:|00 => Type 9 (too much for the first digit of minutes) => 00:09|\n * @example |19:00 => Type 2 (29 - invalid value for hours) => 2|0:00\n */\nexport function enrichTimeSegmentsWithZeroes(\n    {value, selection}: {value: string; selection: readonly [number, number]},\n    {\n        mode,\n        timeSegmentMaxValues = DEFAULT_TIME_SEGMENT_MAX_VALUES,\n    }: {\n        mode: MaskitoTimeMode;\n        timeSegmentMaxValues?: MaskitoTimeSegments<number>;\n    },\n): {value: string; selection: readonly [number, number]} {\n    const [from, to] = selection;\n    const parsedTime = parseTimeString(value, mode);\n    const possibleTimeSegments = Object.entries(parsedTime) as Array<\n        [keyof MaskitoTimeSegments, string]\n    >;\n\n    const paddedMaxValues = padStartTimeSegments(timeSegmentMaxValues);\n    const validatedTimeSegments: Partial<MaskitoTimeSegments> = {};\n    let paddedZeroes = 0;\n\n    for (const [segmentName, segmentValue] of possibleTimeSegments) {\n        const maxSegmentValue = paddedMaxValues[segmentName];\n\n        const {validatedSegmentValue, prefixedZeroesCount} = padWithZeroesUntilValid(\n            segmentValue,\n            String(maxSegmentValue),\n        );\n\n        paddedZeroes += prefixedZeroesCount;\n\n        validatedTimeSegments[segmentName] = validatedSegmentValue;\n    }\n\n    const [leadingNonDigitCharacters = ''] = value.match(/^\\D+(?=\\d)/g) || []; // prefix\n    const [trailingNonDigitCharacters = ''] = value.match(/\\D+$/g) || []; // trailing segment separators / meridiem characters / postfix\n    const validatedTimeString = `${leadingNonDigitCharacters}${toTimeString(validatedTimeSegments)}${trailingNonDigitCharacters}`;\n    const addedTimeSegmentSeparators = Math.max(\n        validatedTimeString.length - value.length - paddedZeroes,\n        0,\n    );\n    let newFrom = from + paddedZeroes + addedTimeSegmentSeparators;\n    let newTo = to + paddedZeroes + addedTimeSegmentSeparators;\n\n    if (\n        newFrom === newTo &&\n        paddedZeroes &&\n        // if next character after cursor is time segment separator\n        validatedTimeString.slice(0, newTo + 1).match(TRAILING_TIME_SEGMENT_SEPARATOR_REG)\n    ) {\n        newFrom++;\n        newTo++;\n    }\n\n    return {\n        value: validatedTimeString,\n        selection: [newFrom, newTo],\n    };\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/time/index.ts",
    "content": "export * from './create-time-mask-expression';\nexport * from './enrich-time-segments-with-zeroes';\nexport * from './pad-end-time-segments';\nexport * from './pad-start-time-segments';\nexport * from './parse-time-string';\nexport * from './to-time-string';\n"
  },
  {
    "path": "projects/kit/src/lib/utils/time/pad-end-time-segments.ts",
    "content": "import type {MaskitoTimeSegments} from '../../types';\nimport {padTimeSegments} from './pad-time-segments';\n\nexport function padEndTimeSegments(\n    timeSegments: MaskitoTimeSegments<number | string>,\n): MaskitoTimeSegments;\n\nexport function padEndTimeSegments(\n    timeSegments: Partial<MaskitoTimeSegments<number | string>>,\n): Partial<MaskitoTimeSegments>;\n\nexport function padEndTimeSegments(\n    timeSegments: Partial<MaskitoTimeSegments<number | string>>,\n): Partial<MaskitoTimeSegments> {\n    return padTimeSegments(timeSegments, (value, length) => value.padEnd(length, '0'));\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/time/pad-start-time-segments.ts",
    "content": "import type {MaskitoTimeSegments} from '../../types';\nimport {padTimeSegments} from './pad-time-segments';\n\nexport function padStartTimeSegments(\n    timeSegments: MaskitoTimeSegments<number | string>,\n): MaskitoTimeSegments;\n\nexport function padStartTimeSegments(\n    timeSegments: Partial<MaskitoTimeSegments<number | string>>,\n): Partial<MaskitoTimeSegments>;\n\nexport function padStartTimeSegments(\n    timeSegments: Partial<MaskitoTimeSegments<number | string>>,\n): Partial<MaskitoTimeSegments> {\n    return padTimeSegments(timeSegments, (value, length) => value.padStart(length, '0'));\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/time/pad-time-segments.ts",
    "content": "import {TIME_SEGMENT_VALUE_LENGTHS} from '../../constants';\nimport type {MaskitoTimeSegments} from '../../types';\n\nexport function padTimeSegments(\n    timeSegments: MaskitoTimeSegments<number | string>,\n    pad: (segmentValue: string, segmentLength: number) => string,\n): MaskitoTimeSegments;\n\nexport function padTimeSegments(\n    timeSegments: Partial<MaskitoTimeSegments<number | string>>,\n    pad: (segmentValue: string, segmentLength: number) => string,\n): Partial<MaskitoTimeSegments>;\n\nexport function padTimeSegments(\n    timeSegments: Partial<MaskitoTimeSegments<number | string>>,\n    pad: (segmentValue: string, segmentLength: number) => string,\n): Partial<MaskitoTimeSegments> {\n    return Object.fromEntries<string>(\n        Object.entries(timeSegments).map(([segmentName, segmentValue]) => [\n            segmentName,\n            pad(\n                String(segmentValue),\n                TIME_SEGMENT_VALUE_LENGTHS[segmentName as keyof MaskitoTimeSegments],\n            ),\n        ]),\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/time/parse-time-string.ts",
    "content": "import type {MaskitoTimeMode, MaskitoTimeSegments} from '../../types';\n\nconst SEGMENT_FULL_NAME: Record<string, keyof MaskitoTimeSegments> = {\n    HH: 'hours',\n    MM: 'minutes',\n    SS: 'seconds',\n    MSS: 'milliseconds',\n};\n\n/**\n * @param timeString can be with/without fixed characters\n */\nexport function parseTimeString(\n    timeString: string,\n    timeMode: MaskitoTimeMode,\n): Partial<MaskitoTimeSegments> {\n    const onlyDigits = timeString.replaceAll(/\\D+/g, '');\n\n    let offset = 0;\n\n    return Object.fromEntries(\n        timeMode\n            .split(/\\W/)\n            .filter((segmentAbbr) => SEGMENT_FULL_NAME[segmentAbbr])\n            .map((segmentAbbr) => {\n                const segmentValue = onlyDigits.slice(\n                    offset,\n                    offset + segmentAbbr.length,\n                );\n\n                offset += segmentAbbr.length;\n\n                return [SEGMENT_FULL_NAME[segmentAbbr], segmentValue];\n            }),\n    );\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/time/tests/enrich-time-segments-with-zeroes.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {enrichTimeSegmentsWithZeroes} from '../enrich-time-segments-with-zeroes';\n\ndescribe('enrichTimeSegmentsWithZeroes', () => {\n    const fn = (value: string): string =>\n        enrichTimeSegmentsWithZeroes({value, selection: [0, 0]}, {mode: 'HH:MM:SS'})\n            .value;\n\n    it('all time segments valid', () => {\n        expect(fn('17:43:00')).toBe('17:43:00');\n    });\n\n    it('contains invalid time segment for hours', () => {\n        expect(fn('30:30:30')).toBe('03:30:30');\n    });\n\n    it('invalid time segment for minutes', () => {\n        expect(fn('23:70:30')).toBe('23:07:30');\n    });\n\n    it('invalid time segment for seconds', () => {\n        expect(fn('23:07:90')).toBe('23:07:09');\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/time/tests/parse-time-string.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {parseTimeString} from '../parse-time-string';\n\ndescribe('parseTimeString', () => {\n    it('hh', () => {\n        expect(parseTimeString('19', 'HH')).toEqual({hours: '19'});\n    });\n\n    it('hh:mm', () => {\n        expect(parseTimeString('23:59', 'HH:MM')).toEqual({\n            hours: '23',\n            minutes: '59',\n        });\n    });\n\n    it('hh:mm:ss', () => {\n        expect(parseTimeString('12:24:55', 'HH:MM:SS')).toEqual({\n            hours: '12',\n            minutes: '24',\n            seconds: '55',\n        });\n    });\n\n    it('hh:mm:ss.mss', () => {\n        expect(parseTimeString('10:05:42.783', 'HH:MM:SS.MSS')).toEqual({\n            hours: '10',\n            minutes: '05',\n            seconds: '42',\n            milliseconds: '783',\n        });\n    });\n\n    it('mm:ss.mss', () => {\n        expect(parseTimeString('12:30.001', 'MM:SS.MSS')).toEqual({\n            minutes: '12',\n            seconds: '30',\n            milliseconds: '001',\n        });\n    });\n\n    it('ss.mss', () => {\n        expect(parseTimeString('59.999', 'SS.MSS')).toEqual({\n            seconds: '59',\n            milliseconds: '999',\n        });\n    });\n\n    it('mm:ss', () => {\n        expect(parseTimeString('25:55', 'MM:SS')).toEqual({\n            minutes: '25',\n            seconds: '55',\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/time/tests/to-time-string.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\n\nimport {toTimeString} from '../to-time-string';\n\ndescribe('toTimeString', () => {\n    describe('HH', () => {\n        it('21', () => {\n            expect(toTimeString({hours: '21'})).toBe('21');\n        });\n\n        it('1', () => {\n            expect(toTimeString({hours: '1'})).toBe('1');\n        });\n    });\n\n    describe('HH:MM', () => {\n        it('21:59', () => {\n            expect(toTimeString({hours: '21', minutes: '59'})).toBe('21:59');\n        });\n\n        it('12:4', () => {\n            expect(toTimeString({hours: '12', minutes: '4'})).toBe('12:4');\n        });\n    });\n\n    describe('HH:MM:SS', () => {\n        it('21:59:23', () => {\n            expect(toTimeString({hours: '21', minutes: '59', seconds: '23'})).toBe(\n                '21:59:23',\n            );\n        });\n\n        it('01:23:5', () => {\n            expect(toTimeString({hours: '01', minutes: '23', seconds: '5'})).toBe(\n                '01:23:5',\n            );\n        });\n    });\n\n    describe('HH:MM:SS.MSS', () => {\n        it('21:59:23.111', () => {\n            expect(\n                toTimeString({\n                    hours: '21',\n                    minutes: '59',\n                    seconds: '23',\n                    milliseconds: '111',\n                }),\n            ).toBe('21:59:23.111');\n        });\n\n        it('01:23:52.1', () => {\n            expect(\n                toTimeString({\n                    hours: '01',\n                    minutes: '23',\n                    seconds: '52',\n                    milliseconds: '1',\n                }),\n            ).toBe('01:23:52.1');\n        });\n\n        it('13:13:13.15', () => {\n            expect(\n                toTimeString({\n                    hours: '13',\n                    minutes: '13',\n                    seconds: '13',\n                    milliseconds: '15',\n                }),\n            ).toBe('13:13:13.15');\n        });\n    });\n\n    describe('MM:SS.MSS', () => {\n        it('12:12.111', () => {\n            expect(\n                toTimeString({\n                    minutes: '12',\n                    seconds: '12',\n                    milliseconds: '111',\n                }),\n            ).toBe('12:12.111');\n        });\n\n        it('23:01.9', () => {\n            expect(\n                toTimeString({\n                    minutes: '23',\n                    seconds: '01',\n                    milliseconds: '9',\n                }),\n            ).toBe('23:01.9');\n        });\n\n        it('00:02.91', () => {\n            expect(\n                toTimeString({\n                    minutes: '00',\n                    seconds: '02',\n                    milliseconds: '91',\n                }),\n            ).toBe('00:02.91');\n        });\n    });\n\n    describe('SS.MSS', () => {\n        it('12.111', () => {\n            expect(\n                toTimeString({\n                    seconds: '12',\n                    milliseconds: '111',\n                }),\n            ).toBe('12.111');\n        });\n\n        it('01.9', () => {\n            expect(\n                toTimeString({\n                    seconds: '01',\n                    milliseconds: '9',\n                }),\n            ).toBe('01.9');\n        });\n\n        it('02.91', () => {\n            expect(\n                toTimeString({\n                    seconds: '02',\n                    milliseconds: '91',\n                }),\n            ).toBe('02.91');\n        });\n    });\n\n    describe('MM:SS', () => {\n        it('12:11', () => {\n            expect(\n                toTimeString({\n                    minutes: '12',\n                    seconds: '11',\n                }),\n            ).toBe('12:11');\n        });\n\n        it('01:09', () => {\n            expect(\n                toTimeString({\n                    minutes: '01',\n                    seconds: '09',\n                }),\n            ).toBe('01:09');\n        });\n\n        it('02:55', () => {\n            expect(\n                toTimeString({\n                    minutes: '02',\n                    seconds: '55',\n                }),\n            ).toBe('02:55');\n        });\n    });\n});\n"
  },
  {
    "path": "projects/kit/src/lib/utils/time/to-time-string.ts",
    "content": "import type {MaskitoTimeSegments} from '../../types';\n\nconst LEADING_NON_DIGITS = /^\\D*/;\nconst TRAILING_NON_DIGITS = /\\D*$/;\n\nexport function toTimeString({\n    hours = '',\n    minutes = '',\n    seconds = '',\n    milliseconds = '',\n}: Partial<MaskitoTimeSegments>): string {\n    return `${hours}:${minutes}:${seconds}.${milliseconds}`\n        .replace(LEADING_NON_DIGITS, '')\n        .replace(TRAILING_NON_DIGITS, '');\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/to-half-width-colon.ts",
    "content": "import {CHAR_COLON, CHAR_JP_COLON} from '../constants';\n\n/**\n * Replace fullwidth colon with half width colon\n * @param fullWidthColon full width colon\n * @returns processed half width colon\n */\nexport function toHalfWidthColon(fullWidthColon: string): string {\n    return fullWidthColon.replaceAll(new RegExp(CHAR_JP_COLON, 'g'), CHAR_COLON);\n}\n"
  },
  {
    "path": "projects/kit/src/lib/utils/to-half-width-number.ts",
    "content": "/**\n * Replace fullwidth numbers with half width number\n * @param fullWidthNumber full width number\n * @returns processed half width number\n */\nexport function toHalfWidthNumber(fullWidthNumber: string): string {\n    return fullWidthNumber.replaceAll(/[０-９]/g, (s) =>\n        String.fromCharCode(s.charCodeAt(0) - 0xfee0),\n    );\n}\n"
  },
  {
    "path": "projects/phone/README.md",
    "content": "# @maskito/phone\n\n[![npm version](https://img.shields.io/npm/v/@maskito/phone.svg)](https://npmjs.com/package/@maskito/phone)\n[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@maskito/phone)](https://bundlephobia.com/result?p=@maskito/phone)\n\n<p align=\"center\">\n    <img src=\"https://raw.githubusercontent.com/taiga-family/maskito/main/projects/demo/src/assets/icons/maskito.svg\" alt=\"Maskito logo\" width=\"120px\">\n</p>\n\n<p align=\"center\">\n    <a href=\"https://maskito.dev\">Documentation</a> •\n    <a href=\"https://github.com/taiga-family/maskito/issues/new/choose\">Submit an Issue</a> •\n    <a href=\"https://t.me/taiga_ui\">Contact Us</a>\n</p>\n\n> The optional framework-agnostic Maskito's package.<br />It contains ready-to-use phone mask\n\n## How to install\n\n```bash\nnpm i @maskito/{core,phone}\n```\n"
  },
  {
    "path": "projects/phone/jest.config.ts",
    "content": "export default {\n    displayName: 'phone',\n    preset: '../../jest.preset.js',\n    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],\n    coverageDirectory: '../../coverage/phone',\n};\n"
  },
  {
    "path": "projects/phone/package.json",
    "content": "{\n    \"name\": \"@maskito/phone\",\n    \"version\": \"5.2.2\",\n    \"description\": \"The optional framework-agnostic Maskito's package with phone masks\",\n    \"keywords\": [\n        \"input\",\n        \"mask\",\n        \"inputmask\",\n        \"input-mask\",\n        \"phone\",\n        \"phonemask\",\n        \"phone-mask\",\n        \"format\",\n        \"input-format\",\n        \"input-formatting\",\n        \"javascript\",\n        \"typescript\"\n    ],\n    \"homepage\": \"https://maskito.dev\",\n    \"bugs\": \"https://github.com/taiga-family/maskito/issues\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/taiga-family/maskito.git\"\n    },\n    \"license\": \"Apache-2.0\",\n    \"author\": {\n        \"email\": \"vladimir.potekh@gmail.com\",\n        \"name\": \"Vladimir Potekhin\",\n        \"url\": \"https://github.com/vladimirpotekhin\"\n    },\n    \"contributors\": [\n        {\n            \"email\": \"alexander@inkin.ru\",\n            \"name\": \"Alex Inkin\"\n        },\n        {\n            \"email\": \"vladimir.potekh@gmail.com\",\n            \"name\": \"Vladimir Potekhin\"\n        },\n        {\n            \"email\": \"nikita.s.barsukov@gmail.com\",\n            \"name\": \"Nikita Barsukov\"\n        },\n        {\n            \"email\": \"nextzeddicus@gmail.com\",\n            \"name\": \"Georgiy Lunin\"\n        }\n    ],\n    \"devDependencies\": {\n        \"libphonenumber-js\": \"1.12.41\"\n    },\n    \"peerDependencies\": {\n        \"@maskito/core\": \"^5.2.2\",\n        \"@maskito/kit\": \"^5.2.2\",\n        \"libphonenumber-js\": \">=1.0.0\"\n    }\n}\n"
  },
  {
    "path": "projects/phone/project.json",
    "content": "{\n    \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n    \"name\": \"phone\",\n    \"projectType\": \"library\",\n    \"sourceRoot\": \"projects/phone/src\",\n    \"tags\": [],\n    \"targets\": {\n        \"build\": {\n            \"dependsOn\": [\n                {\n                    \"dependencies\": true,\n                    \"params\": \"forward\",\n                    \"target\": \"build\"\n                }\n            ],\n            \"executor\": \"@nx/rollup:rollup\",\n            \"options\": {\n                \"assets\": [\n                    {\n                        \"glob\": \"README.md\",\n                        \"input\": \"{projectRoot}\",\n                        \"output\": \".\"\n                    }\n                ],\n                \"compiler\": \"tsc\",\n                \"external\": \"all\",\n                \"format\": [\"esm\", \"cjs\"],\n                \"main\": \"{projectRoot}/src/index.ts\",\n                \"outputPath\": \"dist/{projectName}\",\n                \"project\": \"{projectRoot}/package.json\",\n                \"tsConfig\": \"tsconfig.build.json\",\n                \"useLegacyTypescriptPlugin\": false\n            },\n            \"outputs\": [\"{options.outputPath}\"]\n        },\n        \"publish\": {\n            \"dependsOn\": [\n                {\n                    \"params\": \"ignore\",\n                    \"target\": \"build\"\n                }\n            ],\n            \"executor\": \"nx:run-commands\",\n            \"options\": {\n                \"command\": \"npm publish ./dist/{projectName} --ignore-scripts\"\n            }\n        },\n        \"test\": {\n            \"executor\": \"@nx/jest:jest\",\n            \"options\": {\n                \"jestConfig\": \"{projectRoot}/jest.config.ts\"\n            },\n            \"outputs\": [\"{workspaceRoot}/coverage/{projectName}\"]\n        }\n    }\n}\n"
  },
  {
    "path": "projects/phone/src/index.ts",
    "content": "export type {MaskitoPhoneParams} from './lib/masks';\nexport {maskitoGetCountryFromNumber, maskitoPhoneOptionsGenerator} from './lib/masks';\n"
  },
  {
    "path": "projects/phone/src/lib/masks/index.ts",
    "content": "export type {MaskitoPhoneParams} from './phone';\nexport {maskitoGetCountryFromNumber, maskitoPhoneOptionsGenerator} from './phone';\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/constants/index.ts",
    "content": "export * from './template-filler';\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/constants/template-filler.ts",
    "content": "export const TEMPLATE_FILLER = 'x';\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/index.ts",
    "content": "export {TEMPLATE_FILLER} from './constants';\nexport type {MaskitoPhoneParams} from './phone-mask';\nexport {maskitoPhoneOptionsGenerator} from './phone-mask';\nexport {\n    browserAutofillPreprocessorGenerator,\n    cutInitCountryCodePreprocessor,\n    phoneLengthPostprocessorGenerator,\n} from './processors';\nexport {maskitoGetCountryFromNumber} from './utils';\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/phone-mask-non-strict.ts",
    "content": "import {MASKITO_DEFAULT_OPTIONS, type MaskitoOptions} from '@maskito/core';\nimport {AsYouType, type CountryCode, type MetadataJson} from 'libphonenumber-js/core';\n\nimport {\n    browserAutofillPreprocessorGenerator,\n    pasteNonStrictPhonePreprocessorGenerator,\n    phoneLengthPostprocessorGenerator,\n    sanitizePreprocessor,\n} from './processors';\nimport {generatePhoneMask, getPhoneTemplate, selectTemplate} from './utils';\n\nexport function maskitoPhoneNonStrictOptionsGenerator({\n    defaultIsoCode,\n    metadata,\n    separator = '-',\n}: {\n    defaultIsoCode?: CountryCode;\n    metadata: MetadataJson;\n    separator?: string;\n}): Required<MaskitoOptions> {\n    const formatter = new AsYouType(defaultIsoCode, metadata);\n    const prefix = '+';\n    let currentTemplate = '';\n    let currentPhoneLength = 0;\n\n    return {\n        ...MASKITO_DEFAULT_OPTIONS,\n        mask: ({value}) => {\n            const newTemplate = getPhoneTemplate({\n                formatter,\n                value,\n                separator,\n            });\n            const newPhoneLength = value.replaceAll(/\\D/g, '').length;\n\n            currentTemplate = selectTemplate({\n                currentTemplate,\n                newTemplate,\n                currentPhoneLength,\n                newPhoneLength,\n            });\n            currentPhoneLength = newPhoneLength;\n\n            return currentTemplate.length === 1\n                ? ['+', /\\d/]\n                : generatePhoneMask({value, template: currentTemplate, prefix});\n        },\n        preprocessors: [\n            sanitizePreprocessor,\n            browserAutofillPreprocessorGenerator({\n                prefix,\n                countryIsoCode: defaultIsoCode,\n                metadata,\n            }),\n            pasteNonStrictPhonePreprocessorGenerator({\n                prefix,\n                countryIsoCode: defaultIsoCode,\n                metadata,\n            }),\n        ],\n        postprocessors: [phoneLengthPostprocessorGenerator(metadata)],\n    };\n}\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/phone-mask-strict.ts",
    "content": "import {MASKITO_DEFAULT_OPTIONS, type MaskitoOptions} from '@maskito/core';\nimport {maskitoCaretGuard, maskitoPrefixPostprocessorGenerator} from '@maskito/kit';\nimport {AsYouType, getCountryCallingCode} from 'libphonenumber-js/core';\n\nimport type {MaskitoPhoneParams} from './phone-mask';\nimport {\n    browserAutofillPreprocessorGenerator,\n    cutInitCountryCodePreprocessor,\n    pasteStrictPhonePreprocessorGenerator,\n    phoneLengthPostprocessorGenerator,\n    sanitizePreprocessor,\n} from './processors';\nimport {generatePhoneMask, getPhoneTemplate, selectTemplate} from './utils';\n\nexport function maskitoPhoneStrictOptionsGenerator({\n    countryIsoCode,\n    metadata,\n    separator = '-',\n    format = 'INTERNATIONAL',\n}: Omit<Required<MaskitoPhoneParams>, 'strict'>): Required<MaskitoOptions> {\n    const isNational = format === 'NATIONAL';\n    const code = getCountryCallingCode(countryIsoCode, metadata);\n    const formatter = new AsYouType(countryIsoCode, metadata);\n    const prefix = isNational ? '' : `+${code} `;\n\n    let currentTemplate = '';\n    let currentPhoneLength = 0;\n\n    return {\n        ...MASKITO_DEFAULT_OPTIONS,\n        mask: ({value}) => {\n            const newTemplate = getPhoneTemplate({\n                formatter,\n                value,\n                separator,\n                countryIsoCode,\n                metadata,\n                format,\n            });\n            const newPhoneLength = value.replaceAll(/\\D/g, '').length;\n\n            currentTemplate = selectTemplate({\n                currentTemplate,\n                newTemplate,\n                currentPhoneLength,\n                newPhoneLength,\n            });\n            currentPhoneLength = newPhoneLength;\n\n            return generatePhoneMask({value, template: currentTemplate, prefix});\n        },\n        plugins: [\n            maskitoCaretGuard((value, [from, to]) => [\n                from === to ? prefix.length : 0,\n                value.length,\n            ]),\n        ],\n        preprocessors: [\n            sanitizePreprocessor,\n            cutInitCountryCodePreprocessor({countryIsoCode, metadata, format}),\n            browserAutofillPreprocessorGenerator({\n                prefix,\n                countryIsoCode,\n                metadata,\n                format,\n            }),\n            pasteStrictPhonePreprocessorGenerator({\n                prefix,\n                countryIsoCode,\n                metadata,\n                format,\n            }),\n        ],\n        postprocessors: isNational\n            ? [phoneLengthPostprocessorGenerator(metadata)]\n            : [\n                  maskitoPrefixPostprocessorGenerator(prefix),\n                  phoneLengthPostprocessorGenerator(metadata),\n              ],\n    };\n}\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/phone-mask.ts",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport type {CountryCode, MetadataJson, NumberFormat} from 'libphonenumber-js/core';\n\nimport {maskitoPhoneNonStrictOptionsGenerator} from './phone-mask-non-strict';\nimport {maskitoPhoneStrictOptionsGenerator} from './phone-mask-strict';\n\nexport interface MaskitoPhoneParams {\n    countryIsoCode?: CountryCode;\n    metadata: MetadataJson;\n    strict?: boolean;\n    separator?: string;\n    /**\n     * Phone number format.\n     * - 'INTERNATIONAL' (default): Includes country code prefix (e.g., +1 212 343-3355)\n     * - 'NATIONAL': Country-specific format without country code (e.g., (212) 343-3355).\n     *   Only works with strict mode (requires countryIsoCode).\n     */\n    format?: Extract<NumberFormat, 'INTERNATIONAL' | 'NATIONAL'>;\n}\n\nexport function maskitoPhoneOptionsGenerator({\n    countryIsoCode,\n    metadata,\n    strict = true,\n    separator = '-',\n    format = 'INTERNATIONAL',\n}: MaskitoPhoneParams): Required<MaskitoOptions> {\n    return strict && countryIsoCode\n        ? maskitoPhoneStrictOptionsGenerator({\n              countryIsoCode,\n              metadata,\n              separator,\n              format,\n          })\n        : maskitoPhoneNonStrictOptionsGenerator({\n              defaultIsoCode: countryIsoCode,\n              metadata,\n              separator,\n          });\n}\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/processors/browser-autofill-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\nimport {\n    AsYouType,\n    type CountryCode,\n    formatIncompletePhoneNumber,\n    type MetadataJson,\n    parsePhoneNumber,\n} from 'libphonenumber-js/core';\n\nimport type {MaskitoPhoneParams} from '../phone-mask';\n\nfunction extractNumberValue(\n    value: string,\n    countryIsoCode: CountryCode | undefined,\n    metadata: MetadataJson,\n): string {\n    const formatter = new AsYouType(countryIsoCode, metadata);\n\n    formatter.input(value);\n\n    const numberValue = formatter.getNumberValue() ?? '';\n\n    formatter.reset();\n\n    return numberValue;\n}\n\n/**\n * Converts an international phone value to national format.\n */\nfunction convertToNationalFormat(\n    value: string,\n    countryIsoCode: CountryCode,\n    metadata: MetadataJson,\n): string {\n    const numberValue = extractNumberValue(value, countryIsoCode, metadata);\n\n    if (!numberValue) {\n        return '';\n    }\n\n    try {\n        const phone = parsePhoneNumber(numberValue, countryIsoCode, metadata);\n\n        return formatIncompletePhoneNumber(\n            phone.nationalNumber,\n            countryIsoCode,\n            metadata,\n        );\n    } catch {\n        return '';\n    }\n}\n\nexport function browserAutofillPreprocessorGenerator({\n    prefix,\n    countryIsoCode,\n    metadata,\n    format = 'INTERNATIONAL',\n}: Pick<MaskitoPhoneParams, 'countryIsoCode' | 'format' | 'metadata'> & {\n    prefix: string;\n}): MaskitoPreprocessor {\n    const isNational = format === 'NATIONAL';\n\n    return ({elementState, data}) => {\n        const {selection, value} = elementState;\n        const cleanCode = prefix.trim();\n\n        /**\n         * Handle autocomplete: when value doesn't match expected format.\n         * For international: value should start with '+' or country code.\n         * For national: value should not start with '+'.\n         */\n        if (value && !data) {\n            if (isNational && value.startsWith('+') && countryIsoCode) {\n                /**\n                 * For national format, if autocomplete provides international format,\n                 * convert it to national format.\n                 */\n                const formattedNational = convertToNationalFormat(\n                    value,\n                    countryIsoCode,\n                    metadata,\n                );\n\n                if (formattedNational) {\n                    return {elementState: {value: formattedNational, selection}};\n                }\n            } else if (!isNational && !value.startsWith(cleanCode)) {\n                /**\n                 * International format autocomplete handling.\n                 */\n                const numberValue = extractNumberValue(value, countryIsoCode, metadata);\n                const formatter = new AsYouType(countryIsoCode, metadata);\n\n                return {elementState: {value: formatter.input(numberValue), selection}};\n            }\n        }\n\n        return {elementState};\n    };\n}\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/processors/cut-init-country-code-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\nimport {\n    formatIncompletePhoneNumber,\n    getCountryCallingCode,\n    parsePhoneNumber,\n} from 'libphonenumber-js/core';\n\nimport type {MaskitoPhoneParams} from '../phone-mask';\n\n/**\n * This preprocessor works only once at initialization phase (when `new Maskito(...)` is executed).\n * This preprocessor helps to avoid conflicts during transition from one mask to another (for the same input).\n */\nexport function cutInitCountryCodePreprocessor({\n    countryIsoCode,\n    metadata,\n    format,\n}: Required<\n    Pick<MaskitoPhoneParams, 'countryIsoCode' | 'format' | 'metadata'>\n>): MaskitoPreprocessor {\n    let isInitializationPhase = true;\n    const code = `+${getCountryCallingCode(countryIsoCode, metadata)} `;\n\n    return ({elementState, data}) => {\n        if (!isInitializationPhase) {\n            return {elementState, data};\n        }\n\n        const {value, selection} = elementState;\n\n        isInitializationPhase = false;\n\n        /**\n         * International format:\n         * If the value already starts with the expected prefix (e.g., \"+7 \"),\n         * don't reformat it. This avoids breaking selection positions when\n         * the input already has a properly formatted value (e.g., an initial\n         * value set on the element before Maskito attaches).\n         *\n         * National format:\n         * If value starts with '+', extract national number.\n         * Otherwise, assume it's already in national format.\n         */\n        if (\n            (format === 'INTERNATIONAL' && value.startsWith(code)) ||\n            (format === 'NATIONAL' && !value.startsWith('+'))\n        ) {\n            return {elementState};\n        }\n\n        try {\n            const {nationalNumber} = parsePhoneNumber(value, countryIsoCode, metadata);\n\n            return {\n                elementState: {\n                    value:\n                        format === 'NATIONAL'\n                            ? formatIncompletePhoneNumber(\n                                  nationalNumber,\n                                  countryIsoCode,\n                                  metadata,\n                              )\n                            : `${code} ${nationalNumber}`,\n                    selection,\n                },\n            };\n        } catch {\n            return {elementState};\n        }\n    };\n}\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/processors/index.ts",
    "content": "export {browserAutofillPreprocessorGenerator} from './browser-autofill-preprocessor';\nexport {cutInitCountryCodePreprocessor} from './cut-init-country-code-preprocessor';\nexport {pasteNonStrictPhonePreprocessorGenerator} from './paste-non-strict-phone-preprocessor';\nexport {pasteStrictPhonePreprocessorGenerator} from './paste-strict-phone-preprocessor';\nexport {phoneLengthPostprocessorGenerator} from './phone-length-postprocessor';\nexport {sanitizePreprocessor} from './sanitize-phone-preprocessor';\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/processors/paste-non-strict-phone-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\nimport {type PhoneNumber} from 'libphonenumber-js';\nimport {\n    type CountryCode,\n    type MetadataJson,\n    parsePhoneNumber,\n} from 'libphonenumber-js/core';\n\nimport {type MaskitoPhoneParams} from '../phone-mask';\n\nfunction parsePhone({\n    data,\n    prefix,\n    countryIsoCode,\n    metadata,\n}: {\n    data: string;\n    prefix: string;\n    countryIsoCode?: CountryCode;\n    metadata: MetadataJson;\n}): PhoneNumber {\n    if (!data.startsWith(prefix) && countryIsoCode) {\n        try {\n            return parsePhoneNumber(`+${data}`, countryIsoCode, metadata);\n        } catch {\n            return parsePhoneNumber(data, countryIsoCode, metadata);\n        }\n    }\n\n    return parsePhoneNumber(data, metadata);\n}\n\nexport function pasteNonStrictPhonePreprocessorGenerator({\n    prefix,\n    countryIsoCode,\n    metadata,\n}: Pick<MaskitoPhoneParams, 'countryIsoCode' | 'metadata'> & {\n    prefix: string;\n}): MaskitoPreprocessor {\n    return ({elementState, data}) => ({\n        elementState,\n        data:\n            data.length > 2 && elementState.value === ''\n                ? parsePhone({\n                      data,\n                      prefix,\n                      countryIsoCode,\n                      metadata,\n                  }).number\n                : data,\n    });\n}\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/processors/paste-strict-phone-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\nimport {parsePhoneNumber} from 'libphonenumber-js/core';\n\nimport type {MaskitoPhoneParams} from '../phone-mask';\n\nexport function pasteStrictPhonePreprocessorGenerator({\n    prefix,\n    countryIsoCode,\n    metadata,\n    format = 'INTERNATIONAL',\n}: Pick<MaskitoPhoneParams, 'countryIsoCode' | 'format' | 'metadata'> & {\n    prefix: string;\n}): MaskitoPreprocessor {\n    const isNational = format === 'NATIONAL';\n\n    return ({elementState, data}) => {\n        const {selection, value} = elementState;\n        const [from] = selection;\n        const selectionIncludesPrefix = from < prefix.length;\n\n        // handle paste of a number when input contains only the prefix\n        if (data.length > 2 && value.trim() === prefix.trim()) {\n            // handle paste-event with different code, for example for 8 / +7\n            const phone = countryIsoCode\n                ? parsePhoneNumber(data, countryIsoCode, metadata)\n                : parsePhoneNumber(data, metadata);\n\n            const {nationalNumber, countryCallingCode} = phone;\n\n            if (isNational && countryIsoCode) {\n                /**\n                 * For national format, always return just the national number.\n                 * The mask will format it according to the country's national format.\n                 */\n                return {\n                    elementState: {\n                        selection,\n                        value: '',\n                    },\n                    data: nationalNumber,\n                };\n            }\n\n            return {\n                elementState: {\n                    selection,\n                    value: selectionIncludesPrefix ? '' : prefix,\n                },\n                data: selectionIncludesPrefix\n                    ? `+${countryCallingCode} ${nationalNumber}`\n                    : nationalNumber,\n            };\n        }\n\n        return {elementState};\n    };\n}\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/processors/phone-length-postprocessor.ts",
    "content": "import type {MaskitoPostprocessor} from '@maskito/core';\nimport type {MetadataJson} from 'libphonenumber-js/core';\n\nimport {cutPhoneByValidLength} from '../utils';\n\nconst MIN_LENGTH = 3;\nexport function phoneLengthPostprocessorGenerator(\n    metadata: MetadataJson,\n): MaskitoPostprocessor {\n    return ({value, selection}) => ({\n        value:\n            value.length > MIN_LENGTH\n                ? cutPhoneByValidLength({phone: value, metadata})\n                : value,\n        selection,\n    });\n}\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/processors/sanitize-phone-preprocessor.ts",
    "content": "import type {MaskitoPreprocessor} from '@maskito/core';\n\nexport const sanitizePreprocessor: MaskitoPreprocessor = ({elementState, data}) => ({\n    elementState,\n    data: data.replaceAll(/[^\\d+]/g, ''),\n});\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/tests/get-phone-template.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\nimport {AsYouType} from 'libphonenumber-js/core';\nimport metadata from 'libphonenumber-js/min/metadata';\n\nimport {getPhoneTemplate} from '../utils/get-phone-template';\n\ndescribe('getPhoneTemplate', () => {\n    describe('International format', () => {\n        it('generates template for value with \"+\" prefix', () => {\n            const formatter = new AsYouType(undefined, metadata);\n            const template = getPhoneTemplate({\n                formatter,\n                value: '+12125552365',\n                separator: '-',\n            });\n\n            // Template uses 'x' for all digit positions (including country code)\n            expect(template).toBe('xx xxx xxx-xxxx');\n        });\n\n        it('generates same template for value without \"+\" prefix (pasted number)', () => {\n            const formatter = new AsYouType(undefined, metadata);\n            const template = getPhoneTemplate({\n                formatter,\n                value: '12125552365',\n                separator: '-',\n            });\n\n            // Should produce the same template as with '+' prefix\n            expect(template).toBe('xx xxx xxx-xxxx');\n        });\n\n        it('returns empty template for empty value', () => {\n            const formatter = new AsYouType(undefined, metadata);\n            const template = getPhoneTemplate({\n                formatter,\n                value: '',\n                separator: '-',\n            });\n\n            expect(template).toBe('');\n        });\n\n        it('returns single \"x\" for value with only \"+\"', () => {\n            const formatter = new AsYouType(undefined, metadata);\n            const template = getPhoneTemplate({\n                formatter,\n                value: '+',\n                separator: '-',\n            });\n\n            expect(template).toBe('x');\n        });\n\n        it('handles formatted value with spaces', () => {\n            const formatter = new AsYouType(undefined, metadata);\n            const template = getPhoneTemplate({\n                formatter,\n                value: '+1 212 555 2365',\n                separator: '-',\n            });\n\n            expect(template).toBe('xx xxx xxx-xxxx');\n        });\n\n        it('handles value with only formatting characters (no digits)', () => {\n            const formatter = new AsYouType(undefined, metadata);\n            const template = getPhoneTemplate({\n                formatter,\n                value: '( ) -',\n                separator: '-',\n            });\n\n            expect(template).toBe('');\n        });\n\n        describe('pasting numbers without \"+\" prefix produces correct template', () => {\n            it('using US number: 12125552365', () => {\n                const formatter = new AsYouType(undefined, metadata);\n                const templateWithPlus = getPhoneTemplate({\n                    formatter,\n                    value: '+12125552365',\n                    separator: '-',\n                });\n\n                formatter.reset();\n\n                const templateWithoutPlus = getPhoneTemplate({\n                    formatter,\n                    value: '12125552365',\n                    separator: '-',\n                });\n\n                // Both should produce the same template\n                expect(templateWithoutPlus).toBe(templateWithPlus);\n                expect(templateWithoutPlus).toBe('xx xxx xxx-xxxx');\n            });\n\n            it('using RU number: 79202800155', () => {\n                const formatter = new AsYouType(undefined, metadata);\n                const templateWithPlus = getPhoneTemplate({\n                    formatter,\n                    value: '+79202800155',\n                    separator: '-',\n                });\n\n                formatter.reset();\n\n                const templateWithoutPlus = getPhoneTemplate({\n                    formatter,\n                    value: '79202800155',\n                    separator: '-',\n                });\n\n                // Both should produce the same template\n                expect(templateWithoutPlus).toBe(templateWithPlus);\n                expect(templateWithoutPlus).toBe('xx xxx xxx-xx-xx');\n            });\n\n            it('using BY number: 375447488269', () => {\n                const formatter = new AsYouType(undefined, metadata);\n                const templateWithPlus = getPhoneTemplate({\n                    formatter,\n                    value: '+375447488269',\n                    separator: '-',\n                });\n\n                formatter.reset();\n\n                const templateWithoutPlus = getPhoneTemplate({\n                    formatter,\n                    value: '375447488269',\n                    separator: '-',\n                });\n\n                // Both should produce the same template\n                expect(templateWithoutPlus).toBe(templateWithPlus);\n                expect(templateWithoutPlus).toBe('xxxx xx xxx-xx-xx');\n            });\n        });\n\n        describe('custom separator', () => {\n            it('uses space as separator', () => {\n                const formatter = new AsYouType(undefined, metadata);\n                const template = getPhoneTemplate({\n                    formatter,\n                    value: '+12125552365',\n                    separator: ' ',\n                });\n\n                expect(template).toBe('xx xxx xxx xxxx');\n            });\n\n            it('uses dot as separator', () => {\n                const formatter = new AsYouType(undefined, metadata);\n                const template = getPhoneTemplate({\n                    formatter,\n                    value: '+12125552365',\n                    separator: '.',\n                });\n\n                expect(template).toBe('xx xxx xxx.xxxx');\n            });\n        });\n    });\n\n    describe('National format', () => {\n        it('generates US national template', () => {\n            const formatter = new AsYouType('US', metadata);\n            const template = getPhoneTemplate({\n                formatter,\n                value: '2125552365',\n                separator: '-',\n                countryIsoCode: 'US',\n                metadata,\n                format: 'NATIONAL',\n            });\n\n            expect(template).toBe('(xxx) xxx-xxxx');\n        });\n\n        it('generates RU national template', () => {\n            const formatter = new AsYouType('RU', metadata);\n            const template = getPhoneTemplate({\n                formatter,\n                value: '9202800155',\n                separator: '-',\n                countryIsoCode: 'RU',\n                metadata,\n                format: 'NATIONAL',\n            });\n\n            expect(template).toBe('xxx xxx-xx-xx');\n        });\n\n        it('returns empty template for empty value', () => {\n            const formatter = new AsYouType('US', metadata);\n            const template = getPhoneTemplate({\n                formatter,\n                value: '',\n                separator: '-',\n                countryIsoCode: 'US',\n                metadata,\n                format: 'NATIONAL',\n            });\n\n            expect(template).toBe('');\n        });\n\n        describe('incomplete US numbers with leading country code', () => {\n            it('1212555 => x (xxx) xxx (no hyphen after parenthesis)', () => {\n                const formatter = new AsYouType('US', metadata);\n                const template = getPhoneTemplate({\n                    formatter,\n                    value: '1212555',\n                    separator: '-',\n                    countryIsoCode: 'US',\n                    metadata,\n                    format: 'NATIONAL',\n                });\n\n                // Should NOT produce 'x (xxx)-xxx' - space after ) must be preserved\n                expect(template).toBe('x (xxx) xxx');\n            });\n\n            it('12125553 => x (xxx) xxx-x (hyphen only before last group)', () => {\n                const formatter = new AsYouType('US', metadata);\n                const template = getPhoneTemplate({\n                    formatter,\n                    value: '12125553',\n                    separator: '-',\n                    countryIsoCode: 'US',\n                    metadata,\n                    format: 'NATIONAL',\n                });\n\n                expect(template).toBe('x (xxx) xxx-x');\n            });\n\n            it('1212 => x (xxx) (incomplete area code)', () => {\n                const formatter = new AsYouType('US', metadata);\n                const template = getPhoneTemplate({\n                    formatter,\n                    value: '1212',\n                    separator: '-',\n                    countryIsoCode: 'US',\n                    metadata,\n                    format: 'NATIONAL',\n                });\n\n                expect(template).toBe('x (xxx)');\n            });\n\n            it('12125 => x (xxx) x', () => {\n                const formatter = new AsYouType('US', metadata);\n                const template = getPhoneTemplate({\n                    formatter,\n                    value: '12125',\n                    separator: '-',\n                    countryIsoCode: 'US',\n                    metadata,\n                    format: 'NATIONAL',\n                });\n\n                expect(template).toBe('x (xxx) x');\n            });\n        });\n\n        describe('incomplete US numbers with custom separator', () => {\n            it('1212555 with space separator => x (xxx) xxx', () => {\n                const formatter = new AsYouType('US', metadata);\n                const template = getPhoneTemplate({\n                    formatter,\n                    value: '1212555',\n                    separator: ' ',\n                    countryIsoCode: 'US',\n                    metadata,\n                    format: 'NATIONAL',\n                });\n\n                expect(template).toBe('x (xxx) xxx');\n            });\n\n            it('12125553 with space separator => x (xxx) xxx x', () => {\n                const formatter = new AsYouType('US', metadata);\n                const template = getPhoneTemplate({\n                    formatter,\n                    value: '12125553',\n                    separator: ' ',\n                    countryIsoCode: 'US',\n                    metadata,\n                    format: 'NATIONAL',\n                });\n\n                expect(template).toBe('x (xxx) xxx x');\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/tests/phone-mask.spec.ts",
    "content": "import {beforeEach, describe, expect, it} from '@jest/globals';\nimport {\n    MASKITO_DEFAULT_OPTIONS,\n    type MaskitoOptions,\n    maskitoTransform,\n} from '@maskito/core';\nimport metadata from 'libphonenumber-js/min/metadata';\n\nimport {maskitoPhoneOptionsGenerator} from '../phone-mask';\n\ndescribe('Phone (maskitoTransform)', () => {\n    describe('RU number', () => {\n        let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n        beforeEach(() => {\n            options = maskitoPhoneOptionsGenerator({\n                countryIsoCode: 'RU',\n                metadata,\n            });\n        });\n\n        it('full number +7 code', () => {\n            expect(maskitoTransform('+79202800155', options)).toBe('+7 920 280-01-55');\n        });\n\n        it('full number 8 code', () => {\n            expect(maskitoTransform('89202800155', options)).toBe('+7 920 280-01-55');\n        });\n\n        it('full number without code', () => {\n            expect(maskitoTransform('9202800155', options)).toBe('+7 920 280-01-55');\n        });\n\n        it('full number with extra chars', () => {\n            expect(maskitoTransform('8 (920) 280-01-55', options)).toBe(\n                '+7 920 280-01-55',\n            );\n        });\n    });\n\n    describe('non-strict', () => {\n        let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n        beforeEach(() => {\n            options = maskitoPhoneOptionsGenerator({\n                metadata,\n                strict: false,\n                countryIsoCode: 'RU',\n            });\n        });\n\n        it('full number +7 code', () => {\n            expect(maskitoTransform('+79202800155', options)).toBe('+7 920 280-01-55');\n        });\n\n        it('full number 8 code', () => {\n            expect(maskitoTransform('89202800155', options)).toBe('+7 920 280-01-55');\n        });\n\n        it('full number with extra chars', () => {\n            expect(maskitoTransform('8 (920) 280-01-55', options)).toBe(\n                '+7 920 280-01-55',\n            );\n        });\n    });\n\n    describe('National format', () => {\n        describe('US number', () => {\n            let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n            beforeEach(() => {\n                options = maskitoPhoneOptionsGenerator({\n                    countryIsoCode: 'US',\n                    metadata,\n                    format: 'NATIONAL',\n                });\n            });\n\n            it('formats digits to national format (xxx) xxx-xxxx', () => {\n                expect(maskitoTransform('2123433355', options)).toBe('(212) 343-3355');\n            });\n\n            it('strips country code from international format', () => {\n                expect(maskitoTransform('+12123433355', options)).toBe('(212) 343-3355');\n            });\n\n            it('handles partial input - area code (library adds closing paren)', () => {\n                expect(maskitoTransform('212', options)).toBe('(212)');\n            });\n\n            it('handles area code plus digit', () => {\n                expect(maskitoTransform('2123', options)).toBe('(212) 3');\n            });\n\n            it('handles formatted input with extra chars', () => {\n                expect(maskitoTransform('(212) 343-3355', options)).toBe(\n                    '(212) 343-3355',\n                );\n            });\n        });\n\n        describe('RU number', () => {\n            let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n            beforeEach(() => {\n                options = maskitoPhoneOptionsGenerator({\n                    countryIsoCode: 'RU',\n                    metadata,\n                    format: 'NATIONAL',\n                });\n            });\n\n            it('formats digits to national format', () => {\n                expect(maskitoTransform('9202800155', options)).toBe('920 280-01-55');\n            });\n\n            it('strips country code from international format', () => {\n                expect(maskitoTransform('+79202800155', options)).toBe('920 280-01-55');\n            });\n\n            /**\n             * Note: When input starts with '8', libphonenumber-js interprets it\n             * as part of the number rather than the old Russian trunk prefix.\n             * We test that the national number portion is extracted correctly.\n             */\n            it('strips alternate country code (8) via parsing', () => {\n                expect(maskitoTransform('89202800155', options)).toBe(\n                    '8 (920) 280-01-55',\n                );\n            });\n        });\n\n        describe('Custom separator', () => {\n            let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;\n\n            beforeEach(() => {\n                options = maskitoPhoneOptionsGenerator({\n                    countryIsoCode: 'US',\n                    metadata,\n                    format: 'NATIONAL',\n                    separator: ' ',\n                });\n            });\n\n            it('uses custom separator in national format', () => {\n                expect(maskitoTransform('2123433355', options)).toBe('(212) 343 3355');\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/utils/cut-phone-by-valid-length.ts",
    "content": "import {type MetadataJson, validatePhoneNumberLength} from 'libphonenumber-js/core';\n\nexport function cutPhoneByValidLength({\n    phone,\n    metadata,\n}: {\n    phone: string;\n    metadata: MetadataJson;\n}): string {\n    const validationResult = validatePhoneNumberLength(phone, metadata);\n\n    if (validationResult === 'TOO_LONG') {\n        return cutPhoneByValidLength({\n            phone: phone.slice(0, phone.length - 1),\n            metadata,\n        });\n    }\n\n    return phone;\n}\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/utils/generate-phone-mask.ts",
    "content": "import type {MaskitoMaskExpression} from '@maskito/core';\n\nimport {TEMPLATE_FILLER} from '../constants';\n\nexport function generatePhoneMask({\n    value,\n    template,\n    prefix,\n}: {\n    value: string;\n    template: string;\n    prefix: string;\n}): MaskitoMaskExpression {\n    return [\n        ...prefix,\n        ...(template\n            ? template\n                  .slice(prefix.length)\n                  .split('')\n                  .map((char) =>\n                      char === TEMPLATE_FILLER || /\\d/.test(char) ? /\\d/ : char,\n                  )\n            : Array.from<RegExp>({\n                  length: Math.max(value.length - prefix.length, prefix.length),\n              }).fill(/\\d/)),\n    ];\n}\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/utils/get-country-from-number.ts",
    "content": "import {AsYouType, type CountryCode, type MetadataJson} from 'libphonenumber-js/core';\n\nexport function maskitoGetCountryFromNumber(\n    number: string,\n    metadata: MetadataJson,\n): CountryCode | undefined {\n    const formatter = new AsYouType({}, metadata);\n\n    formatter.input(number);\n\n    return formatter.getCountry();\n}\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/utils/get-phone-template.ts",
    "content": "import {\n    type AsYouType,\n    type CountryCode,\n    formatIncompletePhoneNumber,\n    type MetadataJson,\n} from 'libphonenumber-js/core';\n\nimport type {MaskitoPhoneParams} from '../phone-mask';\n\nexport function getPhoneTemplate({\n    formatter,\n    value,\n    separator,\n    countryIsoCode,\n    metadata,\n    format = 'INTERNATIONAL',\n}: {\n    formatter: AsYouType;\n    value: string;\n    separator: string;\n    countryIsoCode?: CountryCode;\n    metadata?: MetadataJson;\n    format?: MaskitoPhoneParams['format'];\n}): string {\n    const isNational = format === 'NATIONAL';\n\n    if (isNational && countryIsoCode && metadata) {\n        const normalizedValue = value && !value.startsWith('+') ? `+${value}` : value;\n\n        formatter.input(normalizedValue.replaceAll(/[^\\d+]/g, ''));\n\n        return getNationalPhoneTemplate({\n            value: normalizedValue,\n            countryIsoCode,\n            metadata,\n            separator,\n        });\n    }\n\n    return getInternationalPhoneTemplate({formatter, value, separator});\n}\n\nfunction getInternationalPhoneTemplate({\n    formatter,\n    value,\n    separator,\n}: {\n    formatter: AsYouType;\n    value: string;\n    separator: string;\n}): string {\n    const hasDigitsOrPlus = /[\\d+]/.test(value);\n\n    if (!hasDigitsOrPlus) {\n        return '';\n    }\n\n    const normalizedValue = value.startsWith('+') ? value : `+${value}`;\n\n    formatter.input(normalizedValue.replaceAll(/[^\\d+]/g, ''));\n\n    const initialTemplate = formatter.getTemplate();\n    const split = initialTemplate.split(' ');\n    // Join first two parts with space, remaining parts with custom separator\n    const template =\n        split.length > 1\n            ? `${split.slice(0, 2).join(' ')} ${split.slice(2).join(separator)}`\n            : initialTemplate;\n\n    formatter.reset();\n\n    return template.trim();\n}\n\nfunction getNationalPhoneTemplate({\n    value,\n    countryIsoCode,\n    metadata,\n    separator,\n}: {\n    value: string;\n    countryIsoCode: CountryCode;\n    metadata: MetadataJson;\n    separator: string;\n}): string {\n    const digitsOnly = value.replaceAll(/\\D/g, '');\n\n    if (!digitsOnly) {\n        return '';\n    }\n\n    const formatted = formatIncompletePhoneNumber(digitsOnly, countryIsoCode, metadata);\n    const template = formatted.replaceAll(/\\d/g, 'x');\n\n    // Parenthesis-based formats (like US): preserve space after ), only replace dashes\n    if (template.includes(')')) {\n        return template.replaceAll('-', separator);\n    }\n\n    // Space-separated formats (like FR): join groups after first with separator\n    if (!formatted.includes('-')) {\n        const parts = template.split(' ');\n\n        return parts.length > 1\n            ? `${parts[0]} ${parts.slice(1).join(separator)}`\n            : template;\n    }\n\n    // Dash-separated formats (like RU): swap dashes for custom separator\n    return template.replaceAll('-', separator);\n}\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/utils/index.ts",
    "content": "export * from './cut-phone-by-valid-length';\nexport * from './generate-phone-mask';\nexport * from './get-country-from-number';\nexport * from './get-phone-template';\nexport * from './select-template';\n"
  },
  {
    "path": "projects/phone/src/lib/masks/phone/utils/select-template.ts",
    "content": "export function selectTemplate({\n    currentTemplate,\n    newTemplate,\n    currentPhoneLength,\n    newPhoneLength,\n}: {\n    currentTemplate: string;\n    newTemplate: string;\n    currentPhoneLength: number;\n    newPhoneLength: number;\n}): string {\n    return newTemplate.length < currentTemplate.length &&\n        newPhoneLength > currentPhoneLength\n        ? currentTemplate\n        : newTemplate;\n}\n"
  },
  {
    "path": "projects/react/.babelrc",
    "content": "{\n    \"presets\": [\n        [\n            \"@nx/react/babel\",\n            {\n                \"runtime\": \"automatic\"\n            }\n        ]\n    ],\n    \"plugins\": []\n}\n"
  },
  {
    "path": "projects/react/README.md",
    "content": "# @maskito/react\n\n[![npm version](https://img.shields.io/npm/v/@maskito/react.svg)](https://npmjs.com/package/@maskito/react)\n[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@maskito/react)](https://bundlephobia.com/result?p=@maskito/react)\n\n<p align=\"center\">\n    <img src=\"https://raw.githubusercontent.com/taiga-family/maskito/main/projects/demo/src/assets/icons/maskito.svg\" alt=\"Maskito logo\" width=\"120px\">\n</p>\n\n<p align=\"center\">\n    <a href=\"https://maskito.dev/frameworks/react\">Documentation</a> •\n    <a href=\"https://github.com/taiga-family/maskito/issues/new/choose\">Submit an Issue</a> •\n    <a href=\"https://t.me/taiga_ui/10600\">Contact Us</a>\n</p>\n\n> The React-specific library.<br /> It provides a convenient way to use Maskito as a hook.\n\n## How to install\n\n```bash\nnpm i @maskito/{core,react}\n```\n"
  },
  {
    "path": "projects/react/jest.config.ts",
    "content": "export default {\n    displayName: 'react',\n    preset: '../../jest.preset.js',\n    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],\n    coverageDirectory: '../../coverage/projects/react',\n};\n"
  },
  {
    "path": "projects/react/package.json",
    "content": "{\n    \"name\": \"@maskito/react\",\n    \"version\": \"5.2.2\",\n    \"description\": \"The React-specific Maskito's library\",\n    \"keywords\": [\n        \"input\",\n        \"mask\",\n        \"inputmask\",\n        \"input-mask\",\n        \"text-mask\",\n        \"format\",\n        \"input-format\",\n        \"input-formatting\",\n        \"react\"\n    ],\n    \"homepage\": \"https://maskito.dev\",\n    \"bugs\": \"https://github.com/taiga-family/maskito/issues\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/taiga-family/maskito.git\"\n    },\n    \"license\": \"Apache-2.0\",\n    \"author\": {\n        \"email\": \"nextzeddicus@gmail.com\",\n        \"name\": \"Georgiy Lunin\",\n        \"url\": \"https://github.com/nextZed\"\n    },\n    \"contributors\": [\n        {\n            \"email\": \"alexander@inkin.ru\",\n            \"name\": \"Alex Inkin\"\n        },\n        {\n            \"email\": \"vladimir.potekh@gmail.com\",\n            \"name\": \"Vladimir Potekhin\"\n        },\n        {\n            \"email\": \"nikita.s.barsukov@gmail.com\",\n            \"name\": \"Nikita Barsukov\"\n        },\n        {\n            \"email\": \"nextzeddicus@gmail.com\",\n            \"name\": \"Georgiy Lunin\"\n        }\n    ],\n    \"devDependencies\": {\n        \"@testing-library/react\": \"16.3.2\",\n        \"@testing-library/user-event\": \"14.6.1\",\n        \"@types/react\": \"19.2.14\",\n        \"@types/react-dom\": \"19.2.3\",\n        \"react\": \"19.2.5\",\n        \"react-dom\": \"19.2.5\",\n        \"react-test-renderer\": \"19.2.5\"\n    },\n    \"peerDependencies\": {\n        \"@maskito/core\": \"^5.2.2\",\n        \"react\": \">=16.8\",\n        \"react-dom\": \">=16.8\"\n    }\n}\n"
  },
  {
    "path": "projects/react/project.json",
    "content": "{\n    \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n    \"name\": \"react\",\n    \"projectType\": \"library\",\n    \"sourceRoot\": \"projects/react/src\",\n    \"tags\": [],\n    \"targets\": {\n        \"build\": {\n            \"dependsOn\": [\n                {\n                    \"dependencies\": true,\n                    \"params\": \"forward\",\n                    \"target\": \"build\"\n                }\n            ],\n            \"executor\": \"@nx/rollup:rollup\",\n            \"options\": {\n                \"assets\": [\n                    {\n                        \"glob\": \"README.md\",\n                        \"input\": \"{projectRoot}\",\n                        \"output\": \".\"\n                    }\n                ],\n                \"compiler\": \"tsc\",\n                \"external\": \"all\",\n                \"format\": [\"esm\", \"cjs\"],\n                \"main\": \"{projectRoot}/src/index.ts\",\n                \"outputPath\": \"dist/{projectName}\",\n                \"project\": \"{projectRoot}/package.json\",\n                \"tsConfig\": \"tsconfig.build.json\",\n                \"useLegacyTypescriptPlugin\": false\n            },\n            \"outputs\": [\"{options.outputPath}\"]\n        },\n        \"lint\": {\n            \"executor\": \"@nx/eslint:lint\",\n            \"options\": {\n                \"lintFilePatterns\": [\"{projectRoot}/**/*.{ts,tsx,js,jsx}\"]\n            },\n            \"outputs\": [\"{options.outputFile}\"]\n        },\n        \"publish\": {\n            \"dependsOn\": [\n                {\n                    \"params\": \"ignore\",\n                    \"target\": \"build\"\n                }\n            ],\n            \"executor\": \"nx:run-commands\",\n            \"options\": {\n                \"command\": \"npm publish ./dist/{projectName} --ignore-scripts\"\n            }\n        },\n        \"test\": {\n            \"executor\": \"@nx/jest:jest\",\n            \"options\": {\n                \"jestConfig\": \"{projectRoot}/jest.config.ts\"\n            },\n            \"outputs\": [\"{workspaceRoot}/coverage/{projectName}\"]\n        }\n    }\n}\n"
  },
  {
    "path": "projects/react/src/index.ts",
    "content": "export * from './lib/useMaskito';\n"
  },
  {
    "path": "projects/react/src/lib/adaptControlledElement.tsx",
    "content": "import type {MaskitoElement} from '@maskito/core';\n\n/**\n * React adds `_valueTracker` property to every textfield elements for its internal logic with controlled inputs.\n * Also, React monkey-patches `value`-setter of the native textfield elements to update state inside its `_valueTracker`.\n * @see https://github.com/facebook/react/blob/ee76351917106c6146745432a52e9a54a41ee181/packages/react-dom-bindings/src/client/inputValueTracking.js#L12-L19\n *\n * React depends on `_valueTracker` to know if the value was changed to decide:\n * - should it revert state for controlled input (if its state handler does not update value)\n * - should it dispatch its synthetic (not native!) `change` event\n *\n * When Maskito patches textfield with a valid value (using setter of `value` property),\n * it also updates `_valueTracker` state and React mistakenly decides that nothing has happened.\n * React should update `_valueTracker` state by itself!\n * ___\n * @see https://github.com/facebook/react/blob/ee76351917106c6146745432a52e9a54a41ee181/packages/react-dom-bindings/src/client/inputValueTracking.js#L173-L177\n */\nexport function adaptReactControlledElement(element: MaskitoElement): MaskitoElement {\n    const valueSetter = Object.getOwnPropertyDescriptor(getPrototype(element), 'value')?.set;\n\n    if (!valueSetter) {\n        return element;\n    }\n\n    const adapter = {\n        set value(value: string) {\n            /**\n             * Mimics exactly what happens when a browser silently changes the value property.\n             * Bypass the React monkey-patching.\n             */\n            valueSetter.call(element, value);\n        },\n    };\n\n    return new Proxy(element, {\n        get(target, prop: keyof HTMLElement) {\n            const nativeProperty = target[prop];\n\n            return typeof nativeProperty === 'function' ? nativeProperty.bind(target) : nativeProperty;\n        },\n        set(target, prop: keyof HTMLElement, val, receiver) {\n            return Reflect.set(prop in adapter ? adapter : target, prop, val, receiver);\n        },\n    });\n}\n\nfunction getPrototype(element: MaskitoElement): HTMLInputElement | HTMLTextAreaElement | null | undefined {\n    switch (element.nodeName) {\n        case 'INPUT':\n            return globalThis.HTMLInputElement.prototype;\n        case 'TEXTAREA':\n            return globalThis.HTMLTextAreaElement.prototype;\n        default:\n            return null;\n    }\n}\n"
  },
  {
    "path": "projects/react/src/lib/tests/controlledInput.spec.tsx",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport {useMaskito} from '@maskito/react';\nimport {render, type RenderResult} from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport {type JSX, useCallback, useState} from 'react';\n\ndescribe('Maskito with React controlled input', () => {\n    let testElement: RenderResult;\n\n    const setValue = async (user: ReturnType<typeof userEvent.setup>, v: string): Promise<void> =>\n        user.type(testElement.getByRole('textbox'), v);\n    const getValue = (): string => (testElement.getByRole('textbox') as HTMLInputElement).value;\n\n    function TestComponent({\n        handler,\n        options,\n    }: Readonly<{handler?: (value: string) => void; options: MaskitoOptions}>): JSX.Element {\n        const inputRef = useMaskito({options});\n        const [value, setValue] = useState('');\n\n        return (\n            <input\n                ref={inputRef}\n                value={value}\n                onInput={(e) => {\n                    const value = (e.target as HTMLInputElement).value;\n\n                    return handler ? handler(value) : setValue(value);\n                }}\n            />\n        );\n    }\n\n    describe('works with basic mask without processors (only mask expression)', () => {\n        const options: MaskitoOptions = {mask: /^[a-z]$/i};\n\n        it('updates value for setState-like action', async () => {\n            testElement = render(<TestComponent options={options} />);\n\n            const user = userEvent.setup();\n\n            await setValue(user, '1'); // invalid character\n            expect(getValue()).toBe('');\n            await setValue(user, 'T'); // valid character\n            expect(getValue()).toBe('T');\n        });\n\n        it('does not update value for noop handler of onInput event', async () => {\n            const noop = (): void => {};\n\n            testElement = render(\n                <TestComponent\n                    handler={noop}\n                    options={options}\n                />,\n            );\n\n            const user = userEvent.setup();\n\n            await setValue(user, '1'); // invalid character\n            expect(getValue()).toBe('');\n            await setValue(user, 'T'); // valid character\n            expect(getValue()).toBe('');\n        });\n\n        it('triggers onInput handler on every valid input', async () => {\n            const handler = jest.fn();\n\n            testElement = render(\n                <TestComponent\n                    handler={handler}\n                    options={options}\n                />,\n            );\n\n            const user = userEvent.setup();\n\n            await setValue(user, '1'); // invalid character\n            expect(handler).not.toHaveBeenCalled();\n            await setValue(user, 'T'); // valid character\n            expect(handler).toHaveBeenCalledWith('T');\n        });\n\n        it('state-handler can modify element value', async () => {\n            function App(): JSX.Element {\n                const inputRef = useMaskito({options});\n                const [value, setValue] = useState('');\n                const onInputHandler = useCallback(\n                    ({value}: HTMLInputElement) => setValue(value.toUpperCase()),\n                    [setValue],\n                );\n\n                return (\n                    <input\n                        ref={inputRef}\n                        value={value}\n                        onInput={(e) => onInputHandler(e.target as HTMLInputElement)}\n                    />\n                );\n            }\n\n            testElement = render(<App />);\n\n            const user = userEvent.setup();\n\n            await setValue(user, 't');\n            expect(getValue()).toBe('T');\n        });\n    });\n\n    describe('works with complex mask with processors', () => {\n        const options: MaskitoOptions = {\n            mask: /^[a-z]$/i,\n            postprocessors: [({value, selection}) => ({selection, value: value.toUpperCase()})],\n        };\n\n        it('updates value for setState-like action', async () => {\n            testElement = render(<TestComponent options={options} />);\n\n            const user = userEvent.setup();\n\n            await setValue(user, '1'); // invalid character\n            expect(getValue()).toBe('');\n            await setValue(user, 't'); // valid character\n            expect(getValue()).toBe('T');\n        });\n\n        it('does not update value for noop handler of onInput event', async () => {\n            const noop = (): void => {};\n\n            testElement = render(\n                <TestComponent\n                    handler={noop}\n                    options={options}\n                />,\n            );\n\n            const user = userEvent.setup();\n\n            await setValue(user, '1'); // invalid character\n            expect(getValue()).toBe('');\n            await setValue(user, 't'); // valid character\n            expect(getValue()).toBe('');\n        });\n\n        it('triggers onInput handler on every valid input', async () => {\n            const handler = jest.fn();\n\n            testElement = render(\n                <TestComponent\n                    handler={handler}\n                    options={options}\n                />,\n            );\n\n            const user = userEvent.setup();\n\n            await setValue(user, '1'); // invalid character\n            expect(handler).not.toHaveBeenCalled();\n            await setValue(user, 't'); // valid character\n            expect(handler).toHaveBeenCalledWith('T');\n        });\n\n        it('state-handler can modify element value', async () => {\n            function App(): JSX.Element {\n                const inputRef = useMaskito({options});\n                const [value, setValue] = useState('');\n                const onInputHandler = useCallback(({value}: HTMLInputElement) => setValue(`###${value}`), [setValue]);\n\n                return (\n                    <input\n                        ref={inputRef}\n                        value={value}\n                        onInput={(e) => onInputHandler(e.target as HTMLInputElement)}\n                    />\n                );\n            }\n\n            testElement = render(<App />);\n\n            const user = userEvent.setup();\n\n            await setValue(user, 't');\n            expect(getValue()).toBe('###T');\n        });\n    });\n\n    afterEach(() => {\n        testElement.unmount();\n    });\n});\n"
  },
  {
    "path": "projects/react/src/lib/tests/elementPredicate.spec.tsx",
    "content": "import {\n    MASKITO_DEFAULT_ELEMENT_PREDICATE,\n    type MaskitoElementPredicate,\n    maskitoInitialCalibrationPlugin,\n    type MaskitoOptions,\n} from '@maskito/core';\nimport {render, type RenderResult, waitFor} from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport type {JSX} from 'react';\n\nimport {useMaskito} from '../useMaskito';\n\ndescribe('@maskito/react | `elementPredicate` property', () => {\n    const options: MaskitoOptions = {\n        mask: /^\\d+$/,\n        plugins: [maskitoInitialCalibrationPlugin()],\n    };\n    let predicate = MASKITO_DEFAULT_ELEMENT_PREDICATE;\n\n    const correctPredicate: MaskitoElementPredicate = (host) => host.querySelector<HTMLInputElement>('.real-input')!;\n    const wrongPredicate: MaskitoElementPredicate = (host) => host.querySelector('input')!;\n\n    function TestComponent({elementPredicate = predicate}): JSX.Element {\n        const inputRef = useMaskito({options, elementPredicate});\n\n        return (\n            <div ref={inputRef}>\n                <input className=\"hidden-input\" />\n                <input\n                    className=\"real-input\"\n                    placeholder=\"Enter number\"\n                />\n                <input className=\"hidden-input\" />\n            </div>\n        );\n    }\n\n    let testElement: RenderResult;\n\n    const setValue = async (user: ReturnType<typeof userEvent.setup>, v: string): Promise<void> =>\n        user.type(testElement.getByPlaceholderText('Enter number'), v);\n    const getValue = (): string => (testElement.getByPlaceholderText('Enter number') as HTMLInputElement).value;\n\n    afterEach(() => {\n        testElement.unmount();\n    });\n\n    describe('Sync predicate', () => {\n        it('applies mask to the textfield if predicate is correct', async () => {\n            predicate = correctPredicate;\n            testElement = render(<TestComponent />);\n\n            const user = userEvent.setup();\n\n            await setValue(user, '123blah45');\n            expect(getValue()).toBe('12345');\n        });\n\n        it('does not applies mask to the textfield if predicate is incorrect', async () => {\n            predicate = wrongPredicate;\n            testElement = render(<TestComponent />);\n\n            const user = userEvent.setup();\n\n            await setValue(user, '123blah45');\n            expect(getValue()).toBe('123blah45');\n        });\n    });\n\n    describe('Async predicate', () => {\n        it('predicate resolves in next micro task', async () => {\n            const user = userEvent.setup();\n\n            predicate = async (host) => Promise.resolve(correctPredicate(host));\n            testElement = render(<TestComponent />);\n\n            await setValue(user, '123blah45');\n\n            await waitFor(() => {\n                expect(getValue()).toBe('12345');\n            });\n        });\n\n        it('predicate resolves in next macro task', async () => {\n            const user = userEvent.setup();\n\n            predicate = async (host) =>\n                new Promise((resolve) => {\n                    setTimeout(() => resolve(correctPredicate(host)));\n                });\n            testElement = render(<TestComponent />);\n\n            await setValue(user, '123blah45');\n\n            await waitFor(() => {\n                expect(getValue()).toBe('12345');\n            });\n        });\n\n        it('predicate resolves in 100ms', async () => {\n            const user = userEvent.setup();\n\n            predicate = async (host) =>\n                new Promise((resolve) => {\n                    setTimeout(() => resolve(correctPredicate(host)), 100);\n                });\n            testElement = render(<TestComponent />);\n\n            await setValue(user, '123blah45');\n\n            await waitFor(() => {\n                expect(getValue()).toBe('12345');\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "projects/react/src/lib/tests/useMaskito.spec.tsx",
    "content": "import type {MaskitoOptions} from '@maskito/core';\nimport * as maskitoCore from '@maskito/core';\nimport {render, type RenderResult, waitFor} from '@testing-library/react';\nimport userEvent, {type UserEvent} from '@testing-library/user-event';\nimport type {JSX} from 'react';\n\nimport {useMaskito} from '../useMaskito';\n\nconst options: MaskitoOptions = {\n    mask: /^\\d+(,\\d{0,2})?$/,\n    preprocessors: [\n        ({elementState, data}) => {\n            const {value, selection} = elementState;\n\n            return {\n                elementState: {\n                    selection,\n                    value: value.replace('.', ','),\n                },\n                data: data.replace('.', ','),\n            };\n        },\n    ],\n};\n\ndescribe('Maskito React package', () => {\n    function TestComponent({onChangeHandler}: Readonly<{onChangeHandler?: (value: string) => void}>): JSX.Element {\n        const inputRef = useMaskito({options});\n\n        return (\n            <input\n                ref={inputRef}\n                onChange={(e) => onChangeHandler?.(e.target.value)}\n            />\n        );\n    }\n\n    function ConditionalInputComponent({showInput}: Readonly<{showInput: boolean}>): JSX.Element {\n        const inputRef = useMaskito({options});\n\n        return showInput ? <input ref={inputRef} /> : <></>;\n    }\n\n    let testElement: RenderResult;\n    let user: UserEvent;\n\n    const type = async (v: string): Promise<void> => user.type(testElement.getByRole('textbox'), v);\n    const getValue = (): string => (testElement.getByRole('textbox') as HTMLInputElement).value;\n\n    it('should format input value', async () => {\n        testElement = render(<TestComponent />);\n        user = userEvent.setup();\n\n        await type('12345.6789');\n        expect(getValue()).toBe('12345,67');\n    });\n\n    it('should trigger onChange event even when Maskito edits value', async () => {\n        const handler = jest.fn();\n\n        testElement = render(<TestComponent onChangeHandler={handler} />);\n        user = userEvent.setup();\n\n        await type('1.');\n        expect(handler).toHaveBeenLastCalledWith('1,');\n        expect(handler).toHaveBeenCalledTimes(2);\n        expect(getValue()).toBe('1,');\n    });\n\n    it('should destroy Maskito instance when input element is removed', async () => {\n        const destroySpy = jest.spyOn(maskitoCore.Maskito.prototype, 'destroy');\n\n        testElement = render(<ConditionalInputComponent showInput={true} />);\n        user = userEvent.setup();\n\n        await type('1.2');\n        expect(getValue()).toBe('1,2');\n\n        testElement.rerender(<ConditionalInputComponent showInput={false} />);\n\n        await waitFor(() => {\n            expect(destroySpy).toHaveBeenCalledTimes(1);\n        });\n\n        destroySpy.mockRestore();\n    });\n\n    afterEach(() => {\n        testElement.unmount();\n    });\n});\n"
  },
  {
    "path": "projects/react/src/lib/useIsomorphicLayoutEffect.tsx",
    "content": "import {useEffect, useLayoutEffect} from 'react';\n\nexport const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n"
  },
  {
    "path": "projects/react/src/lib/useMaskito.tsx",
    "content": "import {\n    Maskito,\n    MASKITO_DEFAULT_ELEMENT_PREDICATE,\n    type MaskitoElement,\n    type MaskitoElementPredicate,\n    type MaskitoOptions,\n} from '@maskito/core';\nimport {type RefCallback, useCallback, useRef, useState} from 'react';\n\nimport {adaptReactControlledElement} from './adaptControlledElement';\nimport {useIsomorphicLayoutEffect} from './useIsomorphicLayoutEffect';\n\nfunction isThenable<T = unknown>(x: PromiseLike<T> | T): x is PromiseLike<T> {\n    return x && typeof x === 'object' && 'then' in x;\n}\n\n/**\n * Hook for convenient use of Maskito in React\n * @description For controlled inputs use `onInput` event\n * @param options options used for creating Maskito\n * @param elementPredicate function that can help find nested Input or TextArea\n * @returns ref callback to pass it in React Element\n * @example\n * // To avoid unnecessary hook runs with Maskito recreation pass named variables\n * // good example ✅\n * useMaskito({ options: maskitoOptions, elementPredicate: maskitoPredicate })\n *\n * // bad example ❌\n * useMaskito({ options: { mask: /^.*$/ }, elementPredicate: () => e.querySelector('input') })\n */\nexport const useMaskito = ({\n    options = null,\n    elementPredicate = MASKITO_DEFAULT_ELEMENT_PREDICATE,\n}: {\n    options?: MaskitoOptions | null;\n    elementPredicate?: MaskitoElementPredicate;\n} = {}): RefCallback<HTMLElement> => {\n    const [hostElement, setHostElement] = useState<HTMLElement | null>(null);\n    const [element, setElement] = useState<MaskitoElement | null>(null);\n\n    const onRefChange: RefCallback<HTMLElement> = useCallback((node: HTMLElement | null) => {\n        setHostElement(node);\n    }, []);\n\n    const latestPredicateRef = useRef(elementPredicate);\n    const latestOptionsRef = useRef(options);\n\n    latestPredicateRef.current = elementPredicate;\n    latestOptionsRef.current = options;\n\n    useIsomorphicLayoutEffect(() => {\n        if (!hostElement) {\n            return;\n        }\n\n        const elementOrPromise = elementPredicate(hostElement);\n\n        if (isThenable(elementOrPromise)) {\n            void elementOrPromise.then((el) => {\n                if (latestPredicateRef.current === elementPredicate && latestOptionsRef.current === options) {\n                    setElement(el);\n                }\n            });\n        } else {\n            setElement(elementOrPromise);\n        }\n\n        return () => {\n            setElement(null);\n        };\n    }, [hostElement, elementPredicate, latestPredicateRef, options, latestOptionsRef]);\n\n    useIsomorphicLayoutEffect(() => {\n        if (!element || !options) {\n            return;\n        }\n\n        const maskedElement = new Maskito(adaptReactControlledElement(element), options);\n\n        return () => {\n            maskedElement.destroy();\n            setElement(null);\n        };\n    }, [options, element]);\n\n    return onRefChange;\n};\n"
  },
  {
    "path": "projects/vue/README.md",
    "content": "# @maskito/vue\n\n[![npm version](https://img.shields.io/npm/v/@maskito/vue.svg)](https://npmjs.com/package/@maskito/vue)\n[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@maskito/vue)](https://bundlephobia.com/result?p=@maskito/vue)\n\n<p align=\"center\">\n    <img src=\"https://raw.githubusercontent.com/taiga-family/maskito/main/projects/demo/src/assets/icons/maskito.svg\" alt=\"Maskito logo\" width=\"120px\">\n</p>\n\n<p align=\"center\">\n    <a href=\"https://maskito.dev/frameworks/vue\">Documentation</a> •\n    <a href=\"https://github.com/taiga-family/maskito/issues/new/choose\">Submit an Issue</a> •\n    <a href=\"https://t.me/taiga_ui/10600\">Contact Us</a>\n</p>\n\n> The Vue-specific library.<br /> It provides a convenient way to use Maskito as a directive.\n\n## How to install\n\n```bash\nnpm i @maskito/{core,vue}\n```\n"
  },
  {
    "path": "projects/vue/jest.config.ts",
    "content": "export default {\n    displayName: 'vue',\n    preset: '../../jest.preset.js',\n    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],\n    coverageDirectory: '../../coverage/projects/vue',\n    testEnvironmentOptions: {customExportConditions: ['node', 'node-addons']},\n};\n"
  },
  {
    "path": "projects/vue/package.json",
    "content": "{\n    \"name\": \"@maskito/vue\",\n    \"version\": \"5.2.2\",\n    \"description\": \"The Vue-specific Maskito's library\",\n    \"keywords\": [\n        \"input\",\n        \"mask\",\n        \"inputmask\",\n        \"input-mask\",\n        \"text-mask\",\n        \"format\",\n        \"input-format\",\n        \"input-formatting\",\n        \"vue\"\n    ],\n    \"homepage\": \"https://maskito.dev\",\n    \"bugs\": \"https://github.com/taiga-family/maskito/issues\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/taiga-family/maskito.git\"\n    },\n    \"license\": \"Apache-2.0\",\n    \"author\": {\n        \"email\": \"alexander@inkin.ru\",\n        \"name\": \"Alex Inkin\",\n        \"url\": \"https://twitter.com/waterplea\"\n    },\n    \"contributors\": [\n        {\n            \"email\": \"alexander@inkin.ru\",\n            \"name\": \"Alex Inkin\"\n        },\n        {\n            \"email\": \"vladimir.potekh@gmail.com\",\n            \"name\": \"Vladimir Potekhin\"\n        },\n        {\n            \"email\": \"nikita.s.barsukov@gmail.com\",\n            \"name\": \"Nikita Barsukov\"\n        },\n        {\n            \"email\": \"nextzeddicus@gmail.com\",\n            \"name\": \"Georgiy Lunin\"\n        }\n    ],\n    \"devDependencies\": {\n        \"@vue/test-utils\": \"2.4.6\",\n        \"@vue/vue3-jest\": \"29.2.6\",\n        \"vue\": \"3.5.32\"\n    },\n    \"peerDependencies\": {\n        \"@maskito/core\": \"^5.2.2\",\n        \"vue\": \">=3.0.0\"\n    }\n}\n"
  },
  {
    "path": "projects/vue/project.json",
    "content": "{\n    \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n    \"name\": \"vue\",\n    \"projectType\": \"library\",\n    \"sourceRoot\": \"projects/vue/src\",\n    \"tags\": [],\n    \"targets\": {\n        \"build\": {\n            \"dependsOn\": [\n                {\n                    \"dependencies\": true,\n                    \"params\": \"forward\",\n                    \"target\": \"build\"\n                }\n            ],\n            \"executor\": \"@nx/rollup:rollup\",\n            \"options\": {\n                \"assets\": [\n                    {\n                        \"glob\": \"README.md\",\n                        \"input\": \"{projectRoot}\",\n                        \"output\": \".\"\n                    }\n                ],\n                \"compiler\": \"tsc\",\n                \"external\": \"all\",\n                \"format\": [\"esm\", \"cjs\"],\n                \"main\": \"{projectRoot}/src/index.ts\",\n                \"outputPath\": \"dist/{projectName}\",\n                \"project\": \"{projectRoot}/package.json\",\n                \"tsConfig\": \"tsconfig.build.json\",\n                \"useLegacyTypescriptPlugin\": false\n            },\n            \"outputs\": [\"{options.outputPath}\"]\n        },\n        \"lint\": {\n            \"executor\": \"@nx/eslint:lint\",\n            \"options\": {\n                \"lintFilePatterns\": [\"{projectRoot}/**/*.{ts,js}\"]\n            },\n            \"outputs\": [\"{options.outputFile}\"]\n        },\n        \"publish\": {\n            \"dependsOn\": [\n                {\n                    \"params\": \"ignore\",\n                    \"target\": \"build\"\n                }\n            ],\n            \"executor\": \"nx:run-commands\",\n            \"options\": {\n                \"command\": \"npm publish ./dist/{projectName} --ignore-scripts\"\n            }\n        },\n        \"test\": {\n            \"executor\": \"@nx/jest:jest\",\n            \"options\": {\n                \"jestConfig\": \"{projectRoot}/jest.config.ts\"\n            },\n            \"outputs\": [\"{workspaceRoot}/coverage/{projectName}\"]\n        }\n    }\n}\n"
  },
  {
    "path": "projects/vue/src/index.ts",
    "content": "export * from './lib/maskito';\n"
  },
  {
    "path": "projects/vue/src/lib/maskito.spec.ts",
    "content": "import {describe, expect, it} from '@jest/globals';\nimport {maskitoInitialCalibrationPlugin} from '@maskito/core';\nimport {maskito} from '@maskito/vue';\nimport {mount} from '@vue/test-utils';\n\ndescribe('Maskito Vue package', () => {\n    const options = {\n        mask: [\n            ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n            ' ',\n            ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n            ' ',\n            ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n            ' ',\n            ...Array.from<RegExp>({length: 4}).fill(/\\d/),\n        ],\n        plugins: [maskitoInitialCalibrationPlugin()],\n    };\n    const component = {\n        template: '<input v-model=\"value\" v-maskito=\"options\" />',\n        directives: {maskito},\n        data: () => ({\n            value: '1234567890123456',\n            options,\n        }),\n    };\n\n    it('formats text', async () => {\n        const mounted = mount(component);\n\n        await Promise.resolve();\n\n        expect(mounted.find('input').element.value).toBe('1234 5678 9012 3456');\n    });\n});\n"
  },
  {
    "path": "projects/vue/src/lib/maskito.ts",
    "content": "import {\n    Maskito,\n    MASKITO_DEFAULT_ELEMENT_PREDICATE,\n    type MaskitoElementPredicate,\n    type MaskitoOptions,\n} from '@maskito/core';\nimport type {ObjectDirective} from 'vue';\n\nconst teardown = new Map<HTMLElement, Maskito>();\nconst predicates = new Map<HTMLElement, MaskitoElementPredicate>();\n\nasync function update(\n    element: HTMLElement,\n    options:\n        | (MaskitoOptions & {\n              elementPredicate?: MaskitoElementPredicate;\n          })\n        | null,\n): Promise<void> {\n    const predicate = options?.elementPredicate ?? MASKITO_DEFAULT_ELEMENT_PREDICATE;\n\n    predicates.set(element, predicate);\n\n    const predicateResult = await predicate(element);\n\n    if (predicates.get(element) !== predicate) {\n        return;\n    }\n\n    teardown.get(element)?.destroy();\n\n    if (options) {\n        teardown.set(element, new Maskito(predicateResult, options));\n    }\n}\n\nexport const maskito: ObjectDirective<\n    HTMLElement,\n    | (MaskitoOptions & {\n          elementPredicate?: MaskitoElementPredicate;\n      })\n    | null\n> = {\n    unmounted: (element) => {\n        teardown.get(element)?.destroy();\n        teardown.delete(element);\n        predicates.delete(element);\n    },\n    mounted: async (element, {value}) => update(element, value),\n    updated: async (element, {value, oldValue}) => {\n        if (value !== oldValue) {\n            await update(element, value);\n        }\n    },\n};\n"
  },
  {
    "path": "tsconfig.build.json",
    "content": "{\n    \"extends\": \"./tsconfig.json\",\n    \"compilerOptions\": {\n        \"declaration\": true,\n        \"declarationMap\": true,\n        \"incremental\": false,\n        \"inlineSources\": true,\n        \"types\": []\n    },\n    \"angularCompilerOptions\": {\n        \"compilationMode\": \"partial\"\n    },\n    \"include\": [\n        \"**/src/**/*.ts\",\n        \"**/src/**/*.tsx\",\n        \"**/src/**/*.d.ts\"\n    ],\n    \"exclude\": [\"**/*.spec.ts\", \"**/*.spec.tsx\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n    \"extends\": \"@taiga-ui/tsconfig\",\n    \"compilerOptions\": {\n        \"baseUrl\": \"./\",\n        \"jsx\": \"react-jsx\",\n        \"outDir\": \"./dist/out-tsc\",\n        \"paths\": {\n            \"@demo/constants\": [\"projects/demo/src/app/constants/index.ts\"],\n            \"@maskito/angular\": [\"projects/angular/src/index.ts\"],\n            \"@maskito/core\": [\"projects/core/src/index.ts\"],\n            \"@maskito/kit\": [\"projects/kit/src/index.ts\"],\n            \"@maskito/phone\": [\"projects/phone/src/index.ts\"],\n            \"@maskito/react\": [\"projects/react/src/index.ts\"],\n            \"@maskito/vue\": [\"projects/vue/src/index.ts\"]\n        },\n        \"typeRoots\": [\"node_modules/@types\"]\n    },\n    \"include\": [\n        \"**/*.ts\",\n        \"**/*.tsx\",\n        \"**/*.d.ts\"\n    ],\n    \"references\": [\n        {\n            \"path\": \"./tsconfig.spec.json\"\n        }\n    ]\n}\n"
  },
  {
    "path": "tsconfig.spec.json",
    "content": "{\n    \"extends\": \"./tsconfig.json\",\n    \"compilerOptions\": {\n        \"types\": [\"jest\", \"node\"]\n    },\n    \"include\": [\n        \"**/*.spec.ts\",\n        \"**/*.d.ts\",\n        \"**/*.spec.tsx\"\n    ]\n}\n"
  }
]