diff --git a/.commitlintrc.json b/.commitlintrc.json index 7b00321..e1d4c0a 100644 --- a/.commitlintrc.json +++ b/.commitlintrc.json @@ -2,13 +2,7 @@ "extends": ["@commitlint/config-angular"], "rules": { "subject-max-length": [2, "always", 120], - "scope-enum": [ - 2, - "always", - [ - "lib", - "demo" - ]], + "scope-enum": [2, "always", ["lib", "demo"]], "type-enum": [ 2, "always", diff --git a/.eslintrc.json b/.eslintrc.json index 273c93b..6b1dec1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,8 +1,6 @@ { "root": true, - "ignorePatterns": [ - "**/*" - ], + "ignorePatterns": ["**/*"], "reportUnusedDisableDirectives": true, "env": { "browser": true, @@ -10,16 +8,8 @@ }, "overrides": [ { - "files": [ - "*.ts" - ], - "plugins": [ - "@typescript-eslint", - "prefer-arrow", - "import", - "jsdoc", - "jasmine" - ], + "files": ["*.ts"], + "plugins": ["@typescript-eslint", "prefer-arrow", "import", "jsdoc", "jasmine"], "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended-type-checked", @@ -28,7 +18,8 @@ "plugin:import/errors", "plugin:import/warnings", "plugin:import/typescript", - "plugin:jsdoc/recommended" + "plugin:jsdoc/recommended", + "prettier" ], "parser": "@typescript-eslint/parser", "parserOptions": { @@ -38,16 +29,12 @@ }, "settings": { "import/parsers": { - "@typescript-eslint/parser": [ - ".ts" - ] + "@typescript-eslint/parser": [".ts"] }, "import/resolver": { "node": true, "typescript": { - "project": [ - "./tsconfig.json" - ] + "project": ["./tsconfig.json"] } } }, @@ -58,23 +45,11 @@ "ignoreStatic": true } ], - "arrow-parens": [ - "error", - "as-needed" - ], "comma-dangle": "error", "import/order": [ "error", { - "groups": [ - [ - "builtin", - "external" - ], - "parent", - "sibling", - "internal" - ], + "groups": [["builtin", "external"], "parent", "sibling", "internal"], "pathGroups": [ { "pattern": "@lib/**", @@ -113,9 +88,7 @@ "jsdoc/require-returns-type": [ "off", { - "contexts": [ - "any" - ] + "contexts": ["any"] } ], "no-multiple-empty-lines": [ @@ -142,36 +115,23 @@ "classPropertiesAllowed": false } ], - "semi": [ - "error", - "always" - ] + "semi": ["error", "always"] } }, { - "files": [ - "*.spec.ts" - ], + "files": ["*.spec.ts"], "env": { "jasmine": true }, - "plugins": [ - "jasmine" - ], - "extends": [ - "plugin:jasmine/recommended" - ], + "plugins": ["jasmine"], + "extends": ["plugin:jasmine/recommended"], "rules": { "jasmine/missing-expect": "warn" } }, { - "files": [ - "*.json" - ], - "extends": [ - "plugin:json/recommended-legacy" - ], + "files": ["*.json"], + "extends": ["plugin:json/recommended-legacy"], "rules": { "json/*": [ "error", @@ -182,12 +142,8 @@ } }, { - "files": [ - "*.html" - ], - "extends": [ - "plugin:@angular-eslint/template/recommended" - ], + "files": ["*.html"], + "extends": ["plugin:@angular-eslint/template/recommended"], "rules": {} } ] diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a571136..a017eb4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,39 +1,39 @@ version: 2 updates: - package-ecosystem: npm - directory: "/" + directory: '/' schedule: - interval: "monthly" + interval: 'monthly' cooldown: default-days: 7 open-pull-requests-limit: 10 ignore: - - dependency-name: "eslint" - versions: ["9.x", "10.x"] - - dependency-name: "eslint-plugin-cypress" - versions: ["4.x", "5.x", "6.x"] - - dependency-name: "@angular/*" - versions: ["18.x", "19.x", "20.x", "21.x", "22.x"] - - dependency-name: "@angular-devkit/build-angular" - versions: ["18.x", "19.x", "20.x", "21.x", "22.x"] - - dependency-name: "@angular-eslint/*" - versions: ["18.x", "19.x", "20.x", "21.x", "22.x"] - - dependency-name: "ng-packagr" - versions: ["18.x", "19.x", "20.x", "21.x", "22.x"] - - dependency-name: "typescript" - versions: ["5.5.x", "5.6.x", "5.7.x", "5.8.x", "5.9.x", "6.x", "7.x"] - - dependency-name: "zone" - versions: ["0.15.x", "0.16.x"] - - dependency-name: "cypress" - versions: ["15.x"] - - dependency-name: "@cypress/schematic" - versions: ["4.x", "5.x"] - - dependency-name: "@cypress/webpack-preprocessor" - versions: ["7.x"] + - dependency-name: 'eslint' + versions: ['9.x', '10.x'] + - dependency-name: 'eslint-plugin-cypress' + versions: ['4.x', '5.x', '6.x'] + - dependency-name: '@angular/*' + versions: ['18.x', '19.x', '20.x', '21.x', '22.x'] + - dependency-name: '@angular-devkit/build-angular' + versions: ['18.x', '19.x', '20.x', '21.x', '22.x'] + - dependency-name: '@angular-eslint/*' + versions: ['18.x', '19.x', '20.x', '21.x', '22.x'] + - dependency-name: 'ng-packagr' + versions: ['18.x', '19.x', '20.x', '21.x', '22.x'] + - dependency-name: 'typescript' + versions: ['5.5.x', '5.6.x', '5.7.x', '5.8.x', '5.9.x', '6.x', '7.x'] + - dependency-name: 'zone' + versions: ['0.15.x', '0.16.x'] + - dependency-name: 'cypress' + versions: ['15.x'] + - dependency-name: '@cypress/schematic' + versions: ['4.x', '5.x'] + - dependency-name: '@cypress/webpack-preprocessor' + versions: ['7.x'] - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: 'github-actions' + directory: '/' schedule: - interval: "monthly" + interval: 'monthly' cooldown: default-days: 7 diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index ef2d66d..2e880c4 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -5,9 +5,9 @@ name: Node.js CI on: push: - branches: ["main"] + branches: ['main'] pull_request: - branches: ["main"] + branches: ['main'] workflow_dispatch: jobs: @@ -30,7 +30,7 @@ jobs: uses: actions/setup-node@v6.4.0 with: node-version: ${{ matrix.node-version }} - cache: "npm" + cache: 'npm' - run: npm ci - run: npm run lint diff --git a/.github/workflows/publish-demo.yml b/.github/workflows/publish-demo.yml index 477fc64..dc5bfe9 100644 --- a/.github/workflows/publish-demo.yml +++ b/.github/workflows/publish-demo.yml @@ -2,7 +2,7 @@ name: publish-demo-to-github-pages on: push: - branches: ["main"] + branches: ['main'] workflow_dispatch: jobs: @@ -20,8 +20,8 @@ jobs: - name: Use Node v24 uses: actions/setup-node@v6.4.0 with: - node-version: "24.x" - cache: "npm" + node-version: '24.x' + cache: 'npm' - name: npm ci and npm run build files for ghpages run: | diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..1053b8f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "printWidth": 100 +} diff --git a/.stylelintrc b/.stylelintrc index 8f9573d..3569106 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -1,15 +1,12 @@ { - "extends": [ - "stylelint-config-standard", - "stylelint-config-recommended-scss" + "extends": ["stylelint-config-standard", "stylelint-config-recommended-scss"], + "rules": { + "value-keyword-case": [ + "lower", + { + "ignoreKeywords": ["/A\\d00/"] + } ], - "rules": { - "value-keyword-case": [ - "lower", - { - "ignoreKeywords": [ "/A\\d00/" ] - } - ], - "declaration-property-value-no-unknown": null - } + "declaration-property-value-no-unknown": null + } } diff --git a/.unimportedrc.json b/.unimportedrc.json index 240c8dd..12f04c2 100644 --- a/.unimportedrc.json +++ b/.unimportedrc.json @@ -34,13 +34,6 @@ "projects/mat-datatable-lib/src/directives/datatable-sort/wrapped-error-message.ts", "projects/mat-datatable-lib/src/lib/mocking-data/mock-data.simple.ts" ], - "ignoreUnused": [ - "@angular/compiler", - "material-icons", - "roboto-fontface", - "tslib" - ], - "ignoreUnresolved": [ - "mat-datatable" - ] + "ignoreUnused": ["@angular/compiler", "material-icons", "roboto-fontface", "tslib"], + "ignoreUnresolved": ["mat-datatable"] } diff --git a/CHANGELOG.md b/CHANGELOG.md index e1e9bd1..99f3523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,76 +4,68 @@ All notable changes to this project will be documented in this file. See [commit ## [17.2.13](https://github.com/BePo65/mat-datatable/compare/v17.2.12...v17.2.13) (2026-04-03) - ### Bug Fixes -* update packages to fix github dependabot security warning ([fb6b1bb](https://github.com/BePo65/mat-datatable/commit/fb6b1bb50a57120e8c706a3d1e823f7c80a63e9d)) -* update packages to fix github dependabot security warning ([e59b4df](https://github.com/BePo65/mat-datatable/commit/e59b4df18ca09ef61ff86c591abfe4569f5d3d18)) -* update packages to fix github dependabot security warnings ([9980c84](https://github.com/BePo65/mat-datatable/commit/9980c840772838b52699abbaec90a8ef420febfc)) +- update packages to fix github dependabot security warning ([fb6b1bb](https://github.com/BePo65/mat-datatable/commit/fb6b1bb50a57120e8c706a3d1e823f7c80a63e9d)) +- update packages to fix github dependabot security warning ([e59b4df](https://github.com/BePo65/mat-datatable/commit/e59b4df18ca09ef61ff86c591abfe4569f5d3d18)) +- update packages to fix github dependabot security warnings ([9980c84](https://github.com/BePo65/mat-datatable/commit/9980c840772838b52699abbaec90a8ef420febfc)) ## [17.2.12](https://github.com/BePo65/mat-datatable/compare/v17.2.11...v17.2.12) (2025-10-25) - ### Bug Fixes -* update packages to latest versions ([3e82442](https://github.com/BePo65/mat-datatable/commit/3e82442debaee05f97af67906b2046872d2e5c25)) +- update packages to latest versions ([3e82442](https://github.com/BePo65/mat-datatable/commit/3e82442debaee05f97af67906b2046872d2e5c25)) ## [17.2.11](https://github.com/BePo65/mat-datatable/compare/v17.2.10...v17.2.11) (2025-10-04) ## [17.2.10](https://github.com/BePo65/mat-datatable/compare/v17.2.9...v17.2.10) (2025-10-03) - ### Bug Fixes -* update packages to fix github dependabot security warning ([17b0331](https://github.com/BePo65/mat-datatable/commit/17b0331f1fc7340356ba51bc087494c25330ecf5)) -* update packages to fix github dependabot security warning ([30e8433](https://github.com/BePo65/mat-datatable/commit/30e8433ee9ee5bcf6e7de29bb36bd43373d97917)) -* update packages to fix github dependabot security warning ([ae52d6c](https://github.com/BePo65/mat-datatable/commit/ae52d6c74ac53b4b215cd3e9f277eb0b240aec25)) -* update packages to fix github security warnings ([8ddc68f](https://github.com/BePo65/mat-datatable/commit/8ddc68fb65e72158569d64af8ae0188895ee1e24)) +- update packages to fix github dependabot security warning ([17b0331](https://github.com/BePo65/mat-datatable/commit/17b0331f1fc7340356ba51bc087494c25330ecf5)) +- update packages to fix github dependabot security warning ([30e8433](https://github.com/BePo65/mat-datatable/commit/30e8433ee9ee5bcf6e7de29bb36bd43373d97917)) +- update packages to fix github dependabot security warning ([ae52d6c](https://github.com/BePo65/mat-datatable/commit/ae52d6c74ac53b4b215cd3e9f277eb0b240aec25)) +- update packages to fix github security warnings ([8ddc68f](https://github.com/BePo65/mat-datatable/commit/8ddc68fb65e72158569d64af8ae0188895ee1e24)) ## [17.2.9](https://github.com/BePo65/mat-datatable/compare/v17.2.8...v17.2.9) (2025-07-02) - ### Bug Fixes -* update packages to security issues ([f792ee4](https://github.com/BePo65/mat-datatable/commit/f792ee492d58968b904c85862c68adbd954f882b)) +- update packages to security issues ([f792ee4](https://github.com/BePo65/mat-datatable/commit/f792ee492d58968b904c85862c68adbd954f882b)) ## [17.2.8](https://github.com/BePo65/mat-datatable/compare/v17.2.7...v17.2.8) (2025-05-03) - ### Bug Fixes -* update packages to fix github dependabot security warning ([4acbd0b](https://github.com/BePo65/mat-datatable/commit/4acbd0bee40a01e574b4b7f0e91bda70f14c4764)) -* update packages to fix github dependabot security warning ([dd52c3a](https://github.com/BePo65/mat-datatable/commit/dd52c3a04a675ae8f1d6911d01c4bc190b4d8824)) -* update packages to fix github dependabot security warning ([06d4934](https://github.com/BePo65/mat-datatable/commit/06d493450718ac79d266cd807af109e20f20dd8a)) -* update packages to fix github dependabot security warning ([df9facb](https://github.com/BePo65/mat-datatable/commit/df9facb7cae2f37731fe6c6aece51368c619767b)) -* update packages to fix github dependabot security warnings ([90160a6](https://github.com/BePo65/mat-datatable/commit/90160a691c0f80871a8a558b1cabff5244bac3b0)) -* update packages to fix github dependabot security warnings ([97e0d3e](https://github.com/BePo65/mat-datatable/commit/97e0d3e83de751d8720a396b72c920f0a27ebb96)) +- update packages to fix github dependabot security warning ([4acbd0b](https://github.com/BePo65/mat-datatable/commit/4acbd0bee40a01e574b4b7f0e91bda70f14c4764)) +- update packages to fix github dependabot security warning ([dd52c3a](https://github.com/BePo65/mat-datatable/commit/dd52c3a04a675ae8f1d6911d01c4bc190b4d8824)) +- update packages to fix github dependabot security warning ([06d4934](https://github.com/BePo65/mat-datatable/commit/06d493450718ac79d266cd807af109e20f20dd8a)) +- update packages to fix github dependabot security warning ([df9facb](https://github.com/BePo65/mat-datatable/commit/df9facb7cae2f37731fe6c6aece51368c619767b)) +- update packages to fix github dependabot security warnings ([90160a6](https://github.com/BePo65/mat-datatable/commit/90160a691c0f80871a8a558b1cabff5244bac3b0)) +- update packages to fix github dependabot security warnings ([97e0d3e](https://github.com/BePo65/mat-datatable/commit/97e0d3e83de751d8720a396b72c920f0a27ebb96)) ## [17.2.7](https://github.com/BePo65/mat-datatable/compare/v17.2.6...v17.2.7) (2024-12-10) - ### Bug Fixes -* update packages to fix github dependabot security warnings ([682c4fb](https://github.com/BePo65/mat-datatable/commit/682c4fb762a86b4805881a7be0a41bc8ff476a0f)) -* update packages to fix github dependabot security warnings ([aca6b75](https://github.com/BePo65/mat-datatable/commit/aca6b750e679e77e24958d78cee1466a043458e7)) -* update packages to fix github dependabot security warnings ([4c5f840](https://github.com/BePo65/mat-datatable/commit/4c5f84004fe0ffeada49ca268d28083e9a3fa094)) -* update packages to fix github dependabot security warnings ([a8d81e1](https://github.com/BePo65/mat-datatable/commit/a8d81e186eedca49af7c9980ad8e0fd1c5ad4df3)) +- update packages to fix github dependabot security warnings ([682c4fb](https://github.com/BePo65/mat-datatable/commit/682c4fb762a86b4805881a7be0a41bc8ff476a0f)) +- update packages to fix github dependabot security warnings ([aca6b75](https://github.com/BePo65/mat-datatable/commit/aca6b750e679e77e24958d78cee1466a043458e7)) +- update packages to fix github dependabot security warnings ([4c5f840](https://github.com/BePo65/mat-datatable/commit/4c5f84004fe0ffeada49ca268d28083e9a3fa094)) +- update packages to fix github dependabot security warnings ([a8d81e1](https://github.com/BePo65/mat-datatable/commit/a8d81e186eedca49af7c9980ad8e0fd1c5ad4df3)) ## [17.2.6](https://github.com/BePo65/mat-datatable/compare/v17.2.5...v17.2.6) (2024-09-26) - ### Bug Fixes -* update packages to fix github dependabot security warning ([6244b0f](https://github.com/BePo65/mat-datatable/commit/6244b0f7cbede2caedcd0391d9f44d5f7a779201)) -* update packages to fix github dependabot security warnings ([1fda0e1](https://github.com/BePo65/mat-datatable/commit/1fda0e18b12d87268297c38185a09f91f1e4ba6a)) +- update packages to fix github dependabot security warning ([6244b0f](https://github.com/BePo65/mat-datatable/commit/6244b0f7cbede2caedcd0391d9f44d5f7a779201)) +- update packages to fix github dependabot security warnings ([1fda0e1](https://github.com/BePo65/mat-datatable/commit/1fda0e18b12d87268297c38185a09f91f1e4ba6a)) ## [17.2.5](https://github.com/BePo65/mat-datatable/compare/v17.2.4...v17.2.5) (2024-08-14) - ### Bug Fixes -* fix linting errors ([6d824f8](https://github.com/BePo65/mat-datatable/commit/6d824f810d9cccc584db9df03aa5166e140fb2f0)) -* update packages to fix github dependabot security warning ([f8b9990](https://github.com/BePo65/mat-datatable/commit/f8b999023f1243c56e0dd92ea554226f57e88a5a)) +- fix linting errors ([6d824f8](https://github.com/BePo65/mat-datatable/commit/6d824f810d9cccc584db9df03aa5166e140fb2f0)) +- update packages to fix github dependabot security warning ([f8b9990](https://github.com/BePo65/mat-datatable/commit/f8b999023f1243c56e0dd92ea554226f57e88a5a)) ## [17.2.4](https://github.com/BePo65/mat-datatable/compare/v17.2.3...v17.2.4) (2024-07-02) @@ -85,63 +77,56 @@ All notable changes to this project will be documented in this file. See [commit ## [17.2.0](https://github.com/BePo65/mat-datatable/compare/v17.1.1...v17.2.0) (2024-03-06) - ### Features -* fix problems with using the mat-datatable theme ([81fbc7a](https://github.com/BePo65/mat-datatable/commit/81fbc7a00bf4def71a3bcaed37c584fecb42629e)) -* **lib:** make theme an export of library ([42ee128](https://github.com/BePo65/mat-datatable/commit/42ee128d016eaf8c568324d38e31c1c6923e8066)) - +- fix problems with using the mat-datatable theme ([81fbc7a](https://github.com/BePo65/mat-datatable/commit/81fbc7a00bf4def71a3bcaed37c584fecb42629e)) +- **lib:** make theme an export of library ([42ee128](https://github.com/BePo65/mat-datatable/commit/42ee128d016eaf8c568324d38e31c1c6923e8066)) ### Bug Fixes -* remove scope from library ([28b1b59](https://github.com/BePo65/mat-datatable/commit/28b1b59c3b5908088de8cded9ba7e7e06a39084f)) +- remove scope from library ([28b1b59](https://github.com/BePo65/mat-datatable/commit/28b1b59c3b5908088de8cded9ba7e7e06a39084f)) ## [17.1.1](https://github.com/BePo65/mat-datatable/compare/v17.1.0...v17.1.1) (2024-03-05) ## [17.1.0](https://github.com/BePo65/mat-datatable/compare/v17.0.1...v17.1.0) (2024-03-05) - ### Features -* update app and lib to standalone ([917fd2a](https://github.com/BePo65/mat-datatable/commit/917fd2ae46e94f8aaebf033cb1dd44153cad6de9)) +- update app and lib to standalone ([917fd2a](https://github.com/BePo65/mat-datatable/commit/917fd2ae46e94f8aaebf033cb1dd44153cad6de9)) ## [17.0.1](https://github.com/BePo65/mat-datatable/compare/v17.0.0...v17.0.1) (2024-03-02) ## [17.0.0](https://github.com/BePo65/mat-datatable/compare/v16.0.0...v17.0.0) (2024-03-02) - ### ⚠ BREAKING CHANGES -* tha project now requires angular v17 +- tha project now requires angular v17 ### Features -* update to angular v17 ([dbe1ae8](https://github.com/BePo65/mat-datatable/commit/dbe1ae8fe06a5335d65babe6a420c230f73d3baa)) +- update to angular v17 ([dbe1ae8](https://github.com/BePo65/mat-datatable/commit/dbe1ae8fe06a5335d65babe6a420c230f73d3baa)) ## [16.0.0](https://github.com/BePo65/mat-datatable/compare/v15.0.0...v16.0.0) (2024-03-02) - ### ⚠ BREAKING CHANGES -* tha project now requires angular v16 +- tha project now requires angular v16 ### Features -* update to angular v16 ([5ecb525](https://github.com/BePo65/mat-datatable/commit/5ecb5253eaa1f226bf4d97c01ad4be311d81a3c2)) +- update to angular v16 ([5ecb525](https://github.com/BePo65/mat-datatable/commit/5ecb5253eaa1f226bf4d97c01ad4be311d81a3c2)) ## [15.0.0](https://github.com/BePo65/mat-datatable/compare/v0.0.0...v15.0.0) (2024-03-01) - ### Features -* **demo:** add link to source on github to header of demo page ([a01ea22](https://github.com/BePo65/mat-datatable/commit/a01ea22aa116a973bdf8f3ec71a7ebe88c435b47)) - +- **demo:** add link to source on github to header of demo page ([a01ea22](https://github.com/BePo65/mat-datatable/commit/a01ea22aa116a973bdf8f3ec71a7ebe88c435b47)) ### Bug Fixes -* **demo:** remove base tag from index.html to fix error 404 ([eecdfa5](https://github.com/BePo65/mat-datatable/commit/eecdfa5b85ec061f8c8aa5ae77b9c2cb7b60e9bf)) -* **demo:** remove routing from demo (was not used anyway) ([91d8a09](https://github.com/BePo65/mat-datatable/commit/91d8a09ca71c4a10d9bd41cc8e2110900117743b)) -* make missing icon in show/hide button visible ([96dd315](https://github.com/BePo65/mat-datatable/commit/96dd3159024bc510fbeda0ea63118843cbcf362a)), closes [github.com/angular/components/issues/14959#issuecomment-481797135](https://github.com/github.com/angular/components/issues/14959/issues/issuecomment-481797135) +- **demo:** remove base tag from index.html to fix error 404 ([eecdfa5](https://github.com/BePo65/mat-datatable/commit/eecdfa5b85ec061f8c8aa5ae77b9c2cb7b60e9bf)) +- **demo:** remove routing from demo (was not used anyway) ([91d8a09](https://github.com/BePo65/mat-datatable/commit/91d8a09ca71c4a10d9bd41cc8e2110900117743b)) +- make missing icon in show/hide button visible ([96dd315](https://github.com/BePo65/mat-datatable/commit/96dd3159024bc510fbeda0ea63118843cbcf362a)), closes [github.com/angular/components/issues/14959#issuecomment-481797135](https://github.com/github.com/angular/components/issues/14959/issues/issuecomment-481797135) ## 0.0.0 (2024-02-26) diff --git a/README.md b/README.md index 407a76e..5212984 100644 --- a/README.md +++ b/README.md @@ -55,19 +55,21 @@ A simple data table with virtual scrolling using Angular Material. + ## About The Project This project extends 'angular material table' so that it can be used as a replacement for [ngx-datatable](https://github.com/swimlane/ngx-datatable) in one of my projects. Unluckily ngx-datatable seems to be dead as it is still on angular v12 and an update to a more recent angular version is not in sight. Mat-Datatable implements a table with virtual scrolling, sorting and filtering. Only a minimal set of the functionality of ngx-datatable is implemented. -![Screenshot](assets/screenshot.jpg "Screenshot of the demo page") +![Screenshot](assets/screenshot.jpg 'Screenshot of the demo page') Try out the [live demo](https://bepo65.github.io/mat-datatable/).

(back to top)

+ ## Getting Started To use this package in your project just follow these simple steps. @@ -78,41 +80,73 @@ The package can be used in Angular apps with Angular Material installed. ### Installation -Install the package from npmjs - ```sh - npm install @bepo65/mat-datatable - ``` +Install the package from npmjs: + +```sh +npm install mat-datatable +``` ### Embed Mat-Datatable In Your Project -Configure your angular application module (e.g: app.module.ts): +Use the component as a standalone Angular component. Some properties of mat-datatable must be configured in the component class (for example in app.component.ts): + ```ts -... -import { MatDatatableModule } from 'mat-datatable'; - -@NgModule({ - ... - imports: [ - ... - MatDatatableModule - ] +import { Component } from '@angular/core'; +import { MatDatatableComponent, MatColumnDefinition } from 'mat-datatable'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [MatDatatableComponent], + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] }) +export class AppComponent { + protected dataStore = new MyTableDataStore(); + + protected columnDefinitions: MatColumnDefinition[] = [ + { + columnId: 'id', + header: 'ID', + cell: (row) => ({ type: 'string', text: row.id.toString() }) + }, + { + columnId: 'name', + sortable: true, + resizable: true, + header: 'Name', + cell: (row) => ({ type: 'string', text: row.name }) + } + ]; + + protected displayedColumns: string[] = ['id', 'name']; +} ``` -Add Mat-Datatable to your html file (e.g: app.component.html): +The `cell` callback returns a typed content descriptor, so you can render plain strings, mailto links or images without changing the table API: + +```ts +cell: (row) => ({ type: 'mailtoLink', text: row.email, url: `mailto:${row.email}` }); +``` + +Add Mat-Datatable to your HTML file (for example app.component.html): + ```html
- - loading... + [trackBy]="trackBy" + [withFooter]="true" + > + Loading...
``` -The height of the element containing the mat-datatable must be set explicitly (e.g: app.component.scss): +The height of the element containing the mat-datatable must be set explicitly (for example app.component.scss): + ```css .content-table { height: 400px; @@ -121,35 +155,16 @@ The height of the element containing the mat-datatable must be set explicitly (e } ``` -Some properties of mat-datatable must be configured in the component class (e,g, app.component.ts): -```ts -export class AppComponent { - ... - protected dataStore = new MyTableDataStore(); - protected columnDefinitions: MatColumnDefinition[] = [ - { - columnId: 'id', - header: 'ID', - cell: (row: DemoTableItem) => row?.userId?.toString(), - footer: 'id' - }, - { - columnId: 'name', - sortable: true, - resizable: true, - header: 'Name', - cell: (row: DemoTableItem) => row?.firstName, - footer: 'name' - } - - ]; - protected displayedColumns: string[] = ['id', 'name']; +The `cell` callback returns a typed content descriptor, so you can render plain strings, mailto links or images without changing the table API: +```ts +cell: (row) => ({ type: 'mailtoLink', text: row.email, url: `mailto:${row.email}` }); ```

(back to top)

+ ## Used Assets The component is based on Angular Material and uses [Google Fonts](https://fonts.google.com/specimen/Roboto) and the [Google Material Icons](https://google.github.io/material-design-icons/#icon-font-for-the-web) font. @@ -158,6 +173,7 @@ Both fonts are part of the project and not fetched via https.

(back to top)

+ ## Mat-Datatable Demo Demo project to show all features of Mat-Datatable. @@ -173,114 +189,68 @@ Navigate to http://localhost:4200

(back to top)

+ ## API Reference -`import { MatDatatable } from '@bepo65/mat-datatable';` +```ts +import { + MatDatatableComponent, + MatColumnDefinition, + CellValueString, + CellValueImage, + CellValueMailTo, + MatSortDefinition, + DataStoreProvider, + FieldFilterDefinition, + RowSelectionType +} from 'mat-datatable'; +``` ### Classes -#### **MatDatatable** +#### MatDatatableComponent -Component to create an angular material table based datatable. -The component is generic; the given type is used to define the object for the row data. +Standalone Angular component for rendering a virtualized Material table. The component is generic so the row type can be provided with the template parameter. -##### **Properties** +##### Inputs -| Name | Description | -| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `@Input() columnDefinitions: MatColumnDefinition[]` | The definition of the columns used in the table. The order of the definitions needs not to correspond to the order of the columns in the table. Default value: `[]`. | -| | | - -| Name | Description | -| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| `@Input() displayedColumns: string[]` | A list with the names of the columns in the table. The array contains the 'columnId' of the corresponding column definition. Default value: `[]`. | -| | | - -| Name | Description | -| --------------------------------------------- | --------------------------------------------------- | -| `@Input() rowSelectionMode: RowSelectionType` | The type of row selection. Default value: `'none'`. | -| | | - -| Name | Description | -| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `@Input() dataStoreProvider: DataStoreProvider` | An object that connects the mat-datatable with the data source. The object must be the instance of a class implementing the DataStoreProvider interface. Default value: `new EmptyDataStoreProvider`. | -| | | - -| Name | Description | -| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `@Input() trackBy(): TrackByFunction` | A function that returns a value that identifies a single row. Default value: `(index: number, item: T) => JSON.stringify(item)`. | -| | | - -| Name | Description | -| ------------------- | ---------------------------------------------------------- | -| withFooter: boolean | Whether the table has a footer row. Default value: `true`. | -| | | - -| Name | Description | -| ------------------------------------- | ------------------------------ | -| `@Output() rowClick: EventEmitter` | Emitted when a row is clicked. | -| | | - -| Name | Description | -| ----------------------------------------------- | ----------------------------------------------- | -| @Output() rowSelectionChange: EventEmitter | Emitted when the list of selected rows changes. | -| | | - -| Name | Description | -| --------------------------------------------------------- | ----------------------------------------- | -| `@Output() sortChange: EventEmitter` | Emitted when the sort definition changes. | -| | | - -| Name | Description | -| ---------------------------------------------- | -------------------------------------------------------- | -| `firstVisibleIndexChanged: Observable` | Emitted when the index of the first visible row changes. | -| | | - -| Name | Description | -| -------------------------------------- | ------------------------------------------------------------------------------------------------------ | -| `totalRowsChanged: Observable` | Emitted when the total number of rows in the datastore changes (does not depend on any active filter). | -| | | - -| Name | Description | -| ----------------------------------------- | ------------------------------------------------- | -| `filteredRowsChanged: Observable` | Emitted when the number of filtered rows changes. | -| | | - -| Name | Description | -| ------------------------------ | ------------------------ | -| `activatedRow: T \| undefined` | Marks a row as 'active'. | -| | | - -| Name | Description | -| ------------------- | ------------------------- | -| `selectedRows: T[]` | Marks rows as 'selected'. | -| | | - -| Name | Description | -| -------------------------------------- | ---------------------------------------- | -| `sortDefinitions: MatSortDefinition[]` | Gets / sets the current sort definition. | -| | | - -| Name | Description | -| ----------------------------------------------- | ------------------------------------------ | -| `filterDefinitions: FieldFilterDefinition[]` | Gets / sets the current filter definition. | -| | | +| Name | Description | +| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `columnDefinitions: MatColumnDefinition[]` | Column definitions for the table. The order of the definitions does not need to match the visible column order. | +| `displayedColumns: string[]` | List of visible column IDs in the order they should be rendered. | +| `rowSelectionMode: RowSelectionType` | Controls row selection behaviour. Supported values are `none`, `single` and `multi`. | +| `dataStoreProvider: DataStoreProvider` | Data source implementation for the table. | +| `trackBy: TrackByFunction` | Optional row identity function. | +| `withFooter: boolean` | Enables the footer row. | -##### **Methods** +##### Outputs + +| Name | Description | +| ----------------------------------------------- | ------------------------------------------------ | +| `rowClick: EventEmitter` | Emitted when a row is clicked. | +| `rowSelectionChange: EventEmitter` | Emitted when the current selection changes. | +| `sortChange: EventEmitter` | Emitted when the active sort definition changes. | -| | | -| -------------- | ----------------------------------------------- | -| `scrollToRow` | Scrolls to the given row. | -| **Parameters** | -| row: T | Row to show on the top of the current viewport. | -| | | +##### Properties -| | | -| ------------- | ------------------------------ | -| `reloadTable` | Reloads the rows of the table. | -| | | +| Name | Description | +| ----------------------------------------------- | ------------------------------------------------ | +| `activatedRow: T \| undefined` | Marks a row as active. | +| `selectedRows: T[]` | Contains the current selection. | +| `sortDefinitions: MatSortDefinition[]` | Gets or sets the current sort definition. | +| `filterDefinitions: FieldFilterDefinition[]` | Gets or sets the active filter definition. | +| `firstVisibleIndexChanged: Observable` | Emits the index of the first visible row. | +| `totalRowsChanged: Observable` | Emits the total number of rows in the datastore. | +| `filteredRowsChanged: Observable` | Emits the number of rows after filtering. | + +##### Methods + +| Name | Description | +| --------------------- | ------------------------------------------------------ | +| `scrollToRow(row: T)` | Scrolls the viewport so the given row becomes visible. | +| `reloadTable()` | Reloads the table from its datastore. | @@ -288,84 +258,81 @@ The component is generic; the given type is used to define the object for the ro #### DataStoreProvider -Interface for a component that fetches data from the datastore respecting sorting and filtering. -The component is generic; the given type is used to define the object for the row data. +Interface for a component that fetches data from the datastore while respecting sorting and filtering. -##### **Methods** +##### Methods + +| Name | Description | +| ----------------------------------------- | ----------------------------------- | +| `getPagedData(rowsRange, sorts, filters)` | Fetches the requested page of data. | -| | | -| ----------------------------------- | ----------------------------------------------------------------- | -| `getPagedData` | Fetches data from the datastore respecting sorting and filtering. | -| **Parameters** | -| rowsRange: RequestRowsRange | The range of rows to fetch. | -| sorts: FieldSortDefinition[] | The sort definitions to use. | -| filters: FieldFilterDefinition[] | The filter definitions to use. | -| **Returns** | -| Observable> | Emitting fetched data from the datastore. | -| | | +| **Parameters** | | +| ----------------------------------- | ------------------------------ | +| rowsRange: RequestRowsRange | The range of rows to fetch. | +| sorts: FieldSortDefinition[] | The sort definitions to use. | +| filters: FieldFilterDefinition[] | The filter definitions to use. | + +| **Return value** | | +| ------------------- | ----------------------------------------- | +| Observable> | Emitting fetched data from the datastore. | #### MatColumnDefinition Interface for the definition of a single table column. -##### **Properties** +##### Properties -| Name | Description | -| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| `columnId: string` | The ID of the column. | -| `sortable: boolean` | Whether this column can be used for sorting. By default a column is not sortable. | -| `resizable: boolean` | Whether this column can be resized. By default a column is not resizable. | -| `header: string` | The text in the header row of a column. | -| `headerAlignment: ColumnAlignmentType` | The alignment of the header row of a column. | -| `cell: (element: TRowData) => string` | The function to get the content of a cell. | -| `cellAlignment: ColumnAlignmentType` | The alignment of a data row in a column. | -| `width: string` | The width of the column. | -| `tooltip: (element: TRowData) => string` | The function to get the tooltip for a cell. | -| `showAsMailtoLink: boolean` | Whether this cell should be shown a 'mailto' link. By default a column is not shown as mailto link. | -| `showAsSingleLine: boolean` | Whether this cell should be truncated to a single line. By default multiline text in a column is not shown as a single line. | -| `footer: string` | The text in the footer row of a column. | -| `footerAlignment: ColumnAlignmentType` | The alignment of the footer row of a column. | -| `footerColumnSpan: number` | The number of columns a footer should span. By default a footer spans 1 column. | -| | | +| Name | Description | +| ----------------------------------------------- | ------------------------------------------------------ | +| `columnId: string` | The unique ID of the column. | +| `sortable?: boolean` | Enables sorting for the column. | +| `resizable?: boolean` | Enables resizing of the column. | +| `header: string` | The text shown in the header row. | +| `headerAlignment?: ColumnAlignmentType` | Header alignment. | +| `cell: (element: TRowData) => CellContentValue` | Returns the rendered cell content for the current row. | +| `cellAlignment?: ColumnAlignmentType` | Alignment of the cell content. | +| `width?: string` | Optional column width, for example `8em`. | +| `tooltip?: (element: TRowData) => string` | Optional tooltip callback. | +| `showAsSingleLine?: boolean` | Truncates the content to a single line. | +| `footer?: string` | Optional footer text. | +| `footerAlignment?: ColumnAlignmentType` | Footer alignment. | +| `footerColumnSpan?: number` | Optional number of columns the footer should span. | #### MatSortDefinition -Interface for the definition of the sorting of 1 table column. +Interface for the definition of sorting for one column. -##### **Properties** +##### Properties -| Name | Description | -| -------------------------- | ------------------------------------------------ | -| `columnId: string` | The 'columnId' of the column to use for sorting. | -| `direction: SortDirection` | The direction used to sort the column. | -| | | +| Name | Description | +| -------------------------- | ------------------------------------- | +| `columnId: string` | The `columnId` of the column to sort. | +| `direction: SortDirection` | The sort direction. | #### RequestRowsRange -Interface defining the properties of a requests for a range of rows. +Interface defining the properties of a request for a range of rows. -##### **Properties** +##### Properties | Name | Description | | ----------------------- | ------------------------------------- | | `startRowIndex: number` | The index of the first row to return. | | `numberOfRows: number` | The number of rows to return. | -| | | #### Page Interface defining the properties of a page of rows returned from the datastore. -##### **Properties** +##### Properties -| Name | Description | -| ----------------------- | ------------------------------------------------ | -| `content:T[]` | The array of the requested rows. | -| `startRowIndex` | The index of the first row returned. | -| `returnedElements` | The number of rows in 'content'. | -| `totalElements` | The number of rows in the unfiltered data store. | -| `totalFilteredElements` | The number of rows after filtering. | -| | | +| Name | Description | +| ------------------------------- | ----------------------------------------------- | +| `content: T[]` | The array of requested rows. | +| `startRowIndex: number` | The index of the first row returned. | +| `returnedElements: number` | The number of rows in `content`. | +| `totalElements: number` | The number of rows in the unfiltered datastore. | +| `totalFilteredElements: number` | The number of rows after filtering. | @@ -373,68 +340,146 @@ Interface defining the properties of a page of rows returned from the datastore. #### ColumnAlignmentType -The alignment of the content of a column +```ts +type ColumnAlignmentType = 'left' | 'center' | 'right'; +``` + +#### ColumnDisplayType -| | -| --------------------------------------------------------- | -| type ColumnAlignmentType = "left" \| "center" \| "right"; | -| | +How the column should be rendered (as text, mail-to link or as image). + +```ts +type ColumnDisplayType = 'string' | 'mailtoLink' | 'image'; +``` + +#### CellValueString + +Type for a literal object containing the values to be used to render a cell as plain text. + +```ts +interface CellValueString extends CellValueBase { + type: 'string'; + text: string; +} +``` + +##### Properties + +| Name | Description | +| ---------------- | ---------------------------------------------- | +| `type: 'string'` | Indicates that the cell content is plain text. | +| `text: string` | The text displayed in the cell. | + +#### CellValueMailTo + +Type for a literal object containing the values to be used to render a cell as a mail-to link. + +```ts +interface CellValueMailTo extends CellValueBase { + type: 'mailtoLink'; + url: string; + text: string; +} +``` + +##### Properties + +| Name | Description | +| -------------------- | ------------------------------------------------------ | +| `type: 'mailtoLink'` | Indicates that the cell content is rendered as a link. | +| `url: string` | The mail address or URL used for the link target. | +| `text: string` | The visible text shown for the link. | + +#### CellValueImage + +Type for a literal object containing the values to be used to render a cell as an image. + +```ts +interface CellValueImage extends CellValueBase { + type: 'image'; + url: string; + altText: string; + title?: string; +} +``` + +##### Properties + +| Name | Description | +| ----------------- | -------------------------------------------------------- | +| `type: 'image'` | Indicates that the cell content is rendered as an image. | +| `url: string` | The URL of the image to display. | +| `altText: string` | The alternative text for the image. | +| `title?: string` | An optional title shown for the image. | + +#### CellContentValue + +Type of the literal object returned by the function of type CellContentDefType. + +```ts +type CellContentValue = CellValueString | CellValueMailTo | CellValueImage; +``` + +#### CellContentDefType + +Type of the function of the field `cell` of the MatColumnDefinition. + +```ts +type CellContentDefType = (element: TRowData) => CellContentValue; +``` #### FieldFilterDefinition -The definition of a parameter filtering for the column identified by the given 'fieldName'. +The definition of a parameter for filtering the displayed data using the column identified by the given 'fieldName'. -| | -| ------------------------------------------------------------------------------------------------------------------ | -| type FieldFilterDefinition = StrictUnion\<(FieldFilterDefinitionSimple\ \| FieldFilterDefinitionRange\)>; | -| | +```ts +type FieldFilterDefinition = StrictUnion< + FieldFilterDefinitionSimple | FieldFilterDefinitionRange +>; +``` #### FieldFilterDefinitionRange -The definition of a parameter filtering for a range of values. - -| | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| type FieldFilterDefinitionSimple = {
  fieldName: keyof T
  valueFrom: string \| number \| Date
  valueTo: string \| number \| Date
}; | -| | +```ts +type FieldFilterDefinitionRange = { + fieldName: keyof T; + valueFrom: string | number | Date; + valueTo: string | number | Date; +}; +``` #### FieldFilterDefinitionSimple -The definition of a parameter filtering for a single value. - -| | -| ------------------------------------------------------------------------------------------------------------------------------ | -| type FieldFilterDefinitionSimple = {
  fieldName: keyof T
  value: string \| number \| Date
}; | -| | +```ts +type FieldFilterDefinitionSimple = { + fieldName: keyof T; + value: string | number | Date; +}; +``` #### FieldSortDefinition -The definition of a single sort parameter. - -| | -| -------------------------------------------------------------------------------------------------------------------------- | -| type FieldSortDefinition = {
  fieldName: keyof T
  sortDirection: SortDirectionAscDesc
}; | -| | +```ts +type FieldSortDefinition = { + fieldName: keyof T; + sortDirection: SortDirectionAscDesc; +}; +``` #### RowSelectionType -How many rows can be selected. - -| | -| ------------------------------------------------------ | -| type RowSelectionType = 'none' \| 'single' \| 'multi'; | -| | +```ts +type RowSelectionType = 'none' | 'single' | 'multi'; +``` #### SortDirectionAscDesc -The direction of a sort. - -| | -| ------------------------------------- | -| type SortDirection = 'asc' \| 'desc'; | -| | +```ts +type SortDirectionAscDesc = 'asc' | 'desc'; +``` + ## API Testing Harnesses `import { MatDatatableHarness } from '@bepo65/mat-datatable/testing';` @@ -445,7 +490,7 @@ The direction of a sort. -#### **_MatRowCellHarnessBase** extends [ContentContainerComponentHarness\](https://material.angular.io/cdk/test-harnesses/api#ContentContainerComponentHarness) +#### **\_MatRowCellHarnessBase** extends [ContentContainerComponentHarness\](https://material.angular.io/cdk/test-harnesses/api#ContentContainerComponentHarness) ##### **Methods** @@ -482,7 +527,7 @@ The direction of a sort. -#### **_MatRowHarnessBase** extends [ComponentHarness](https://material.angular.io/cdk/test-harnesses/api#ComponentHarness) +#### **\_MatRowHarnessBase** extends [ComponentHarness](https://material.angular.io/cdk/test-harnesses/api#ComponentHarness) Abstract class used as base for harnesses that interact with a mat-datatable row. @@ -1018,6 +1063,7 @@ A set of criteria that can be used to filter a list of row harness instances.

(back to top)

+ ## Roadmap See the [open issues](https://github.com/BePo65/mat-datatable/issues) for a full list of proposed features (and known issues). @@ -1025,13 +1071,15 @@ See the [open issues](https://github.com/BePo65/mat-datatable/issues) for a full

(back to top)

+ ## Hints On Possible Extensions -+ to make footer turn on / off dynamically it is not sufficient to wrap the footer cell and row definitions in ng-container. Details see [stackoverflow](https://stackoverflow.com/questions/63644938/angular-material-mat-table-dynamic-footer-header-rowdef/63648914#63648914). The demo uses [ngx-rerender](https://www.npmjs.com/package/ngx-rerender). +- to make footer turn on / off dynamically it is not sufficient to wrap the footer cell and row definitions in ng-container. Details see [stackoverflow](https://stackoverflow.com/questions/63644938/angular-material-mat-table-dynamic-footer-header-rowdef/63648914#63648914). The demo uses [ngx-rerender](https://www.npmjs.com/package/ngx-rerender).

(back to top)

+ ## Contributing Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. @@ -1046,15 +1094,18 @@ Don't forget to give the project a star! Thanks again! 5. Open a Pull Request ### Changelog + The project uses 'standard-version' to create the changelog. To enable this system, commit messages are linted before commits are executed by git. To enable this system you have to run the following scripts in your local repository home directory: + ``` npx husky install npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1" ``` **The structure of commit messages is**: + ```
@@ -1064,30 +1115,33 @@ npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1" ``` **header** + ``` (): ``` - + type and scope - - build: Changes that affect the build system or external dependencies (example scope: npm) - - docs: Documentation only changes - - feat: A new feature - - fix: A bug fix - - perf: A code change that improves performance - - refactor: A code change that neither fixes a bug nor adds a feature - - test: Adding missing tests or correcting existing tests (example scopes: demo, lib, e2e) + +- build: Changes that affect the build system or external dependencies (example scope: npm) +- docs: Documentation only changes +- feat: A new feature +- fix: A bug fix +- perf: A code change that improves performance +- refactor: A code change that neither fixes a bug nor adds a feature +- test: Adding missing tests or correcting existing tests (example scopes: demo, lib, e2e) **footer** + ``` BREAKING CHANGE: ... (requires MAJOR in Semantic Versioning) ``` For details of the commit messages format see [Contributing to Angular](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit). -

(back to top)

+ ## License Copyright © 2025 [Bernhard Pottler](https://github.com/BePo65). @@ -1099,7 +1153,9 @@ This project uses the fonts '[Roboto](https://fonts.google.com/specimen/Roboto/a

(back to top)

-## Hints + +## Hints on dependencies + As `eslint` V9 requires a fundamental change to the configuration files, the update will be done in a later version. As a consequence the package `eslint-plugin-cypress` cannot be updated to a version 4.x or 5.x (as this version has a peerDependency of eslint >= 9). diff --git a/angular.json b/angular.json index 67aaa39..b55834c 100644 --- a/angular.json +++ b/angular.json @@ -20,18 +20,14 @@ "outputPath": "dist/mat-datatable-demo", "index": "projects/mat-datatable-demo/src/index.html", "main": "projects/mat-datatable-demo/src/main.ts", - "polyfills": [ - "zone.js" - ], + "polyfills": ["zone.js"], "tsConfig": "projects/mat-datatable-demo/tsconfig.app.json", "inlineStyleLanguage": "scss", "assets": [ "projects/mat-datatable-demo/src/favicon.ico", "projects/mat-datatable-demo/src/assets" ], - "styles": [ - "projects/mat-datatable-demo/src/styles.scss" - ], + "styles": ["projects/mat-datatable-demo/src/styles.scss"], "scripts": [] }, "configurations": { @@ -82,10 +78,7 @@ "test": { "builder": "@angular-devkit/build-angular:karma", "options": { - "polyfills": [ - "zone.js", - "zone.js/testing" - ], + "polyfills": ["zone.js", "zone.js/testing"], "tsConfig": "projects/mat-datatable-demo/tsconfig.spec.json", "karmaConfig": "projects/mat-datatable-demo/karma.conf.js", "inlineStyleLanguage": "scss", @@ -93,9 +86,7 @@ "projects/mat-datatable-demo/src/favicon.ico", "projects/mat-datatable-demo/src/assets" ], - "styles": [ - "projects/mat-datatable-demo/src/styles.scss" - ], + "styles": ["projects/mat-datatable-demo/src/styles.scss"], "scripts": [] } }, @@ -137,10 +128,7 @@ "options": { "tsConfig": "projects/mat-datatable-lib/tsconfig.spec.json", "karmaConfig": "projects/mat-datatable-lib/karma.conf.js", - "polyfills": [ - "zone.js", - "zone.js/testing" - ] + "polyfills": ["zone.js", "zone.js/testing"] } }, "lint": { diff --git a/assets/screenshot.jpg b/assets/screenshot.jpg index 321a63d..835b7f6 100644 Binary files a/assets/screenshot.jpg and b/assets/screenshot.jpg differ diff --git a/backlog.md b/backlog.md index f08c23e..28cbf5b 100644 --- a/backlog.md +++ b/backlog.md @@ -1,6 +1,6 @@ -* Change fonts to @fontsource/roboto and @fontsource/material-icons -* Add prettier -* set new selector for TableItemSizeDirective (today it uses the selector used by virtual-scroll-viewport) -* use @SkipSelf or @Optional instead of default providers (see https://medium.com/@matsal.dev/angular-short-explanation-of-skip-skipself-optional-decorators-69c0c20b25c4) -* Migrate to new build system -* Add ssr +- Change fonts to @fontsource/roboto and @fontsource/material-icons +- Add prettier +- set new selector for TableItemSizeDirective (today it uses the selector used by virtual-scroll-viewport) +- use @SkipSelf or @Optional instead of default providers (see https://medium.com/@matsal.dev/angular-short-explanation-of-skip-skipself-optional-decorators-69c0c20b25c4) +- Migrate to new build system +- Add ssr diff --git a/package-lock.json b/package-lock.json index 91b4c48..f54a605 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "@angular/cli": "~17.3.17", "@angular/compiler-cli": "^17.3.12", "@angular/platform-browser-dynamic": "^17.3.12", - "@commitlint/cli": "^21.2.0", + "@commitlint/cli": "^21.2.1", "@commitlint/config-angular": "^21.2.0", "@cypress/schematic": "^3.0.0", "@cypress/webpack-preprocessor": "^6.0.4", @@ -47,11 +47,12 @@ "cypress": "^14.5.4", "del-cli": "^7.0.0", "eslint": "^8.57.1", + "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.5", "eslint-plugin-cypress": "^3.6.0", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jasmine": "^4.2.2", - "eslint-plugin-jsdoc": "^63.0.12", + "eslint-plugin-jsdoc": "^63.0.13", "eslint-plugin-json": "^4.0.1", "eslint-plugin-prefer-arrow": "^1.2.3", "husky": "^9.1.7", @@ -62,6 +63,7 @@ "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "^2.2.0", "ng-packagr": "^17.3.0", + "prettier": "^3.9.5", "start-server-and-test": "^3.0.11", "stylelint": "^17.14.0", "stylelint-config-recommended-scss": "^17.0.1", @@ -3100,9 +3102,9 @@ } }, "node_modules/@commitlint/cli": { - "version": "21.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.2.0.tgz", - "integrity": "sha512-4GLVIhUaT3c3GBlQ0GB80/5H3xXdn/Tgw4lrsuoOQVDu2wl4Xw0GuzSar8xZKSMv4H3xaKaQXmvH91GmdyYBZA==", + "version": "21.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.2.1.tgz", + "integrity": "sha512-blsZGe29hJ72VGEFVl72IVYX+1vsfINpjA9yWQA6i7OKD/McGEOXg08sKIRKjFk4JvzhV/9n0l3i6NooPLTNfg==", "dev": true, "license": "MIT", "dependencies": { @@ -3110,7 +3112,7 @@ "@commitlint/format": "^21.2.0", "@commitlint/lint": "^21.2.0", "@commitlint/load": "^21.2.0", - "@commitlint/read": "^21.2.0", + "@commitlint/read": "^21.2.1", "@commitlint/types": "^21.2.0", "tinyexec": "^1.0.0", "yargs": "^18.0.0" @@ -3416,15 +3418,15 @@ } }, "node_modules/@commitlint/read": { - "version": "21.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.2.0.tgz", - "integrity": "sha512-ELx8Ovh/JoAw5lpvDgxc6Y0We9skf2IPI2RFN+gnYgDGjRdMSF8zeodxhZmcclLWzfUIF7hXLOa8gOlllYcvBA==", + "version": "21.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.2.1.tgz", + "integrity": "sha512-hUW7EJQnNTL0vPOmVMNK4CrnrNBN0nN+JJHReFkdHO5y4iyHeEmTBwuC15OCqUTjxWo7idnH1LftfpWVIaPWIA==", "dev": true, "license": "MIT", "dependencies": { "@commitlint/top-level": "^21.2.0", "@commitlint/types": "^21.2.0", - "git-raw-commits": "^5.0.0", + "@conventional-changelog/git-client": "^3.0.0", "tinyexec": "^1.0.0" }, "engines": { @@ -3501,6 +3503,33 @@ "node": ">=22.12.0" } }, + "node_modules/@conventional-changelog/git-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-3.1.0.tgz", + "integrity": "sha512-Tqa/gHco2WJWa740NRjOrfKVvzIqxkZpecb8bemaQ8sKM5PXb1UK4uTyTb/1wIqNuOVaDOFxyBdhTIQZn6gdjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/child-process-utils": "^2.0.0", + "@simple-libs/stream-utils": "^2.0.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "conventional-commits-filter": "^6.0.1", + "conventional-commits-parser": "^7.0.1" + }, + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } + } + }, "node_modules/@conventional-changelog/template": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@conventional-changelog/template/-/template-1.2.1.tgz", @@ -4312,9 +4341,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -4453,9 +4482,9 @@ "license": "MIT" }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -5836,9 +5865,9 @@ "license": "MIT" }, "node_modules/@npmcli/package-json/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -6205,6 +6234,9 @@ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -6219,6 +6251,9 @@ "arm" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -6233,6 +6268,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -6247,6 +6285,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -6261,6 +6302,9 @@ "loong64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -6275,6 +6319,9 @@ "loong64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -6289,6 +6336,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -6303,6 +6353,9 @@ "ppc64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -6317,6 +6370,9 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -6331,6 +6387,9 @@ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -6345,6 +6404,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -6359,6 +6421,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -6373,6 +6438,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -6598,29 +6666,16 @@ } }, "node_modules/@simple-libs/child-process-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-1.0.2.tgz", - "integrity": "sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-2.0.0.tgz", + "integrity": "sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==", "dev": true, "license": "MIT", "dependencies": { - "@simple-libs/stream-utils": "^1.2.0" + "@simple-libs/stream-utils": "^2.0.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://ko-fi.com/dangreen" - } - }, - "node_modules/@simple-libs/child-process-utils/node_modules/@simple-libs/stream-utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz", - "integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" + "node": ">=22" }, "funding": { "url": "https://ko-fi.com/dangreen" @@ -6711,9 +6766,9 @@ "license": "MIT" }, "node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -6820,9 +6875,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", - "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.2.tgz", + "integrity": "sha512-d3KvEXBSo/lOAMc2u6fkyDHBvetBHeqD7wm/AcXfLpSOQwlmG9D/aQ0SFswVjv05p7ullQS7Mjohj6/VdbZuTg==", "dev": true, "license": "MIT", "dependencies": { @@ -6833,9 +6888,9 @@ } }, "node_modules/@types/express/node_modules/@types/express-serve-static-core": { - "version": "4.19.8", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", - "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "version": "4.19.9", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.9.tgz", + "integrity": "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg==", "dev": true, "license": "MIT", "dependencies": { @@ -6898,13 +6953,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz", - "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.21.0" } }, "node_modules/@types/node-forge": { @@ -7119,9 +7174,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { @@ -7307,9 +7362,9 @@ "license": "MIT" }, "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -7541,9 +7596,9 @@ "license": "MIT" }, "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -7642,9 +7697,9 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", - "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", "dev": true, "license": "ISC" }, @@ -7754,6 +7809,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -7768,6 +7826,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -7782,6 +7843,9 @@ "loong64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -7796,6 +7860,9 @@ "loong64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -7810,6 +7877,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -7824,6 +7894,9 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -7838,6 +7911,9 @@ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -7852,6 +7928,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -7866,6 +7945,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -7880,6 +7962,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -8508,6 +8593,19 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/argue-cli": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/argue-cli/-/argue-cli-3.1.0.tgz", + "integrity": "sha512-DhBpBfXL4SS2uC0N922MMajKR3CdrTG0u2or1PNYgXMsrSzViJrbtvT0nCLlLGUI0plam/ZZCs7aAauHtW9thw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -9007,9 +9105,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.42", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz", - "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==", + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -9086,9 +9184,9 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "dev": true, "license": "MIT", "dependencies": { @@ -9145,9 +9243,9 @@ } }, "node_modules/bonjour-service": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.4.2.tgz", - "integrity": "sha512-lMskhnsW70yWHr4PhPeh2rvaIkLSaDpp+nmtbXBZaNKTXwxL73QOkW6HhbzqTImXjevn9TreGT4GACGBCGP9nQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.4.3.tgz", + "integrity": "sha512-2Kd5UYlFUVgAKMTyuBLl6w49wqfOnbxHqmuH0oCl/n7TfAikR0zoowNOP5BU4dfXmm+Vr9JyEN370auSMx+CNg==", "dev": true, "license": "MIT", "dependencies": { @@ -9189,9 +9287,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.5.tgz", - "integrity": "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ==", + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", "dev": true, "funding": [ { @@ -9210,9 +9308,9 @@ "license": "MIT", "dependencies": { "baseline-browser-mapping": "^2.10.42", - "caniuse-lite": "^1.0.30001800", - "electron-to-chromium": "^1.5.387", - "node-releases": "^2.0.50", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -9306,9 +9404,9 @@ "license": "MIT" }, "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -9499,9 +9597,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001802", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001802.tgz", - "integrity": "sha512-vmv8ub2xwTNmljSKf82mtCk5JH7hC+YgzLj3P5zotvA0tPQ9016tdNNOG8WRca1IxOnhSsivB+J0z5FeE5LOUw==", + "version": "1.0.30001805", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", "dev": true, "funding": [ { @@ -10230,25 +10328,6 @@ "node": ">=14" } }, - "node_modules/conventional-changelog-core/node_modules/git-raw-commits": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-3.0.0.tgz", - "integrity": "sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==", - "deprecated": "Deprecated and no longer maintained. Use @conventional-changelog/git-client instead.", - "dev": true, - "license": "MIT", - "dependencies": { - "dargs": "^7.0.0", - "meow": "^8.1.2", - "split2": "^3.2.2" - }, - "bin": { - "git-raw-commits": "cli.js" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/conventional-changelog-ember": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-3.0.0.tgz", @@ -10339,6 +10418,20 @@ "node": ">=14" } }, + "node_modules/conventional-changelog-writer/node_modules/conventional-commits-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz", + "integrity": "sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.1" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/conventional-changelog/node_modules/conventional-changelog-angular": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-6.0.0.tgz", @@ -10366,28 +10459,24 @@ } }, "node_modules/conventional-commits-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz", - "integrity": "sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==", - "dev": true, + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-6.0.1.tgz", + "integrity": "sha512-cs+LadpH7Kpw0M3k8wurk+sOVVDAENA0iK4OBOrkL94j5lEVYRJ4j3zd2bhY9qgzyrPqthdcYT3axzRN7AliMg==", + "extraneous": true, "license": "MIT", - "dependencies": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.1" - }, "engines": { - "node": ">=14" + "node": ">=22" } }, "node_modules/conventional-commits-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-7.0.1.tgz", - "integrity": "sha512-6VtskFpPsNkGVk/TY2RnV/MEdKfvCPBtQZN9x8jh9+k5RWBQ+tiaWn5UFCzTr0Dd88iKx7xghxbjBRp5uIzp3g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-7.1.0.tgz", + "integrity": "sha512-DPp6hkUjvwIivxbkrTiLXeRswNv1A/4GFA2X6scXma0AMa9632V3TwxmrlkUIEtUktiM3Ln+RrSH2xlP3/jUTw==", "dev": true, "license": "MIT", "dependencies": { "@simple-libs/stream-utils": "^2.0.0", - "meow": "^14.0.0" + "argue-cli": "^3.1.0" }, "bin": { "conventional-commits-parser": "dist/cli/index.js" @@ -10396,19 +10485,6 @@ "node": ">=22" } }, - "node_modules/conventional-commits-parser/node_modules/meow": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-14.1.0.tgz", - "integrity": "sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/conventional-recommended-bump": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-7.0.1.tgz", @@ -10431,39 +10507,34 @@ "node": ">=14" } }, - "node_modules/conventional-recommended-bump/node_modules/conventional-commits-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz", - "integrity": "sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==", + "node_modules/conventional-recommended-bump/node_modules/conventional-commits-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz", + "integrity": "sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==", "dev": true, "license": "MIT", "dependencies": { - "is-text-path": "^1.0.1", - "JSONStream": "^1.3.5", - "meow": "^8.1.2", - "split2": "^3.2.2" - }, - "bin": { - "conventional-commits-parser": "cli.js" + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.1" }, "engines": { "node": ">=14" } }, - "node_modules/conventional-recommended-bump/node_modules/git-raw-commits": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-3.0.0.tgz", - "integrity": "sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==", - "deprecated": "Deprecated and no longer maintained. Use @conventional-changelog/git-client instead.", + "node_modules/conventional-recommended-bump/node_modules/conventional-commits-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz", + "integrity": "sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==", "dev": true, "license": "MIT", "dependencies": { - "dargs": "^7.0.0", + "is-text-path": "^1.0.1", + "JSONStream": "^1.3.5", "meow": "^8.1.2", "split2": "^3.2.2" }, "bin": { - "git-raw-commits": "cli.js" + "conventional-commits-parser": "cli.js" }, "engines": { "node": ">=14" @@ -10756,9 +10827,9 @@ } }, "node_modules/cpy-cli/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { @@ -10817,9 +10888,9 @@ } }, "node_modules/cpy/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { @@ -11619,9 +11690,9 @@ } }, "node_modules/del/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { @@ -11885,9 +11956,9 @@ "license": "MIT" }, "node_modules/dotgitignore/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -12015,9 +12086,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.388", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.388.tgz", - "integrity": "sha512-Pl/aJaqOOxYxda3vcx1IKSJimwYXHDkEnGn0F+kG2EE68dDtx2uCinaS+Vih8Z91B9t8CSAbiF/HKyWcnXjhzw==", + "version": "1.5.389", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz", + "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==", "dev": true, "license": "ISC" }, @@ -12322,9 +12393,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", - "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "dev": true, "license": "MIT", "peer": true @@ -12540,6 +12611,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-import-context": { "version": "0.1.9", "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", @@ -12729,9 +12816,9 @@ "license": "MIT" }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -12797,9 +12884,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "63.0.12", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-63.0.12.tgz", - "integrity": "sha512-99VfBHyoLlF9n5w9wh+jLWDBzCgYd3tfw3M/aynAd+H2ylGHWLbde/GPTuo/5az3jMAN0fH7QzCYvEOY6u+Ypg==", + "version": "63.0.13", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-63.0.13.tgz", + "integrity": "sha512-ahG1kWA8jYNwaQJtzJlnF+v4Gb9w5r+WL98gp+L8qjLN9ErpL5sevGuemN+fCYsU3Np27F36KmDc8UPi1ml/dg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -12984,9 +13071,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -13548,9 +13635,9 @@ "license": "MIT" }, "node_modules/fast-xml-builder": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.1.tgz", - "integrity": "sha512-tPb5TTWfgfVx5BNSi2xV0eLr89POeXXn0dXIsCJ9m1narrWxeIyx6je9d7Rce/3NyXLbvuQmLkxq+RuxMWejvw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", + "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", "dev": true, "funding": [ { @@ -13560,14 +13647,14 @@ ], "license": "MIT", "dependencies": { - "path-expression-matcher": "^1.5.0", - "xml-naming": "^0.1.0" + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" } }, "node_modules/fast-xml-parser": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.9.3.tgz", - "integrity": "sha512-brCNCeScma/kqa54J4PIDriSSSLssRkuYaUCpvHJulGc3HGI/xxKUCTDcYkAdqJsyb//ydpbxecjC3hB9+tb/g==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.10.0.tgz", + "integrity": "sha512-SLhnTEqE5QpJHq/6zl9bsmImEP2adv+y6Wy+cJa7nVTRzQh1OZfCe9k29M5xN74LWnu0xa1zrUrq3KnOKl92Fg==", "dev": true, "funding": [ { @@ -13579,10 +13666,10 @@ "dependencies": { "@nodable/entities": "^2.2.0", "fast-xml-builder": "^1.2.0", - "is-unsafe": "^1.0.1", - "path-expression-matcher": "^1.5.0", + "is-unsafe": "^2.0.0", + "path-expression-matcher": "^1.6.2", "strnum": "^2.4.1", - "xml-naming": "^0.1.0" + "xml-naming": "^0.3.0" }, "bin": { "fxparser": "src/cli/cli.js" @@ -14327,105 +14414,22 @@ } }, "node_modules/git-raw-commits": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.1.tgz", - "integrity": "sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-3.0.0.tgz", + "integrity": "sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==", "deprecated": "Deprecated and no longer maintained. Use @conventional-changelog/git-client instead.", "dev": true, "license": "MIT", "dependencies": { - "@conventional-changelog/git-client": "^2.6.0", - "meow": "^13.0.0" - }, - "bin": { - "git-raw-commits": "src/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/git-raw-commits/node_modules/@conventional-changelog/git-client": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.7.0.tgz", - "integrity": "sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@simple-libs/child-process-utils": "^1.0.0", - "@simple-libs/stream-utils": "^1.2.0", - "semver": "^7.5.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "conventional-commits-filter": "^5.0.0", - "conventional-commits-parser": "^6.4.0" - }, - "peerDependenciesMeta": { - "conventional-commits-filter": { - "optional": true - }, - "conventional-commits-parser": { - "optional": true - } - } - }, - "node_modules/git-raw-commits/node_modules/@simple-libs/stream-utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz", - "integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://ko-fi.com/dangreen" - } - }, - "node_modules/git-raw-commits/node_modules/conventional-commits-filter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", - "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/git-raw-commits/node_modules/conventional-commits-parser": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.4.0.tgz", - "integrity": "sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@simple-libs/stream-utils": "^1.2.0", - "meow": "^13.0.0" + "dargs": "^7.0.0", + "meow": "^8.1.2", + "split2": "^3.2.2" }, "bin": { - "conventional-commits-parser": "dist/cli/index.js" + "git-raw-commits": "cli.js" }, "engines": { - "node": ">=18" - } - }, - "node_modules/git-raw-commits/node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14" } }, "node_modules/git-remote-origin-url": { @@ -14527,9 +14531,9 @@ "license": "MIT" }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -15335,9 +15339,9 @@ "license": "MIT" }, "node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -16141,9 +16145,9 @@ } }, "node_modules/is-unsafe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-1.0.1.tgz", - "integrity": "sha512-CLK2+VdgERgD96EYm5lUQssZYlRg2tkZnbsxZoacmSiRxiFJ4Nk4SzjCl+Ur+v3kXIY9dTIdb3IH22y1mZ56LA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.0.tgz", + "integrity": "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==", "dev": true, "funding": [ { @@ -16750,9 +16754,9 @@ "license": "MIT" }, "node_modules/karma-coverage/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -16842,9 +16846,9 @@ "license": "MIT" }, "node_modules/karma/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -18146,9 +18150,9 @@ "peer": true }, "node_modules/minimizer-webpack-plugin/node_modules/terser": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", - "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -18716,9 +18720,9 @@ "license": "MIT" }, "node_modules/node-gyp/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -18800,9 +18804,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", - "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "dev": true, "license": "MIT", "engines": { @@ -19680,9 +19684,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.1.tgz", - "integrity": "sha512-h7bxdzhHk8Knyc4Tj+jMaa7fEEoUJy7p1qtbVgkYg1Uhpe5Np5VuGXCRZnkZvU+Q42M1vStt0ifa3ueykRJPmQ==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", "dev": true, "funding": [ { @@ -20142,6 +20146,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/prettier": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", + "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -20416,9 +20436,9 @@ "license": "MIT" }, "node_modules/read-package-json/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -21631,9 +21651,9 @@ } }, "node_modules/shell-quote": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", - "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", "dev": true, "license": "MIT", "engines": { @@ -22716,9 +22736,9 @@ } }, "node_modules/stylelint/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { @@ -22739,9 +22759,9 @@ } }, "node_modules/stylelint/node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "version": "8.5.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.17.tgz", + "integrity": "sha512-J7EF+8X+CzRPaJPOv9Ck2wNWJvGnnl3PcNPAdGg6GTLjyVpyQ0yATMSXRFRV01BviT/9Gwuc3rjEyJbDJG9a4w==", "dev": true, "funding": [ { @@ -22768,9 +22788,9 @@ } }, "node_modules/stylelint/node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", "dev": true, "license": "MIT", "dependencies": { @@ -23133,9 +23153,9 @@ "license": "MIT" }, "node_modules/terser-webpack-plugin/node_modules/terser": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", - "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -23181,9 +23201,9 @@ "license": "MIT" }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -23726,9 +23746,9 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, @@ -24510,9 +24530,9 @@ } }, "node_modules/vite/node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "version": "8.5.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.17.tgz", + "integrity": "sha512-J7EF+8X+CzRPaJPOv9Ck2wNWJvGnnl3PcNPAdGg6GTLjyVpyQ0yATMSXRFRV01BviT/9Gwuc3rjEyJbDJG9a4w==", "dev": true, "funding": [ { @@ -25218,9 +25238,9 @@ } }, "node_modules/xml-naming": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", - "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index 26d0e9a..9ac9dd5 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@angular/cli": "~17.3.17", "@angular/compiler-cli": "^17.3.12", "@angular/platform-browser-dynamic": "^17.3.12", - "@commitlint/cli": "^21.2.0", + "@commitlint/cli": "^21.2.1", "@commitlint/config-angular": "^21.2.0", "@cypress/schematic": "^3.0.0", "@cypress/webpack-preprocessor": "^6.0.4", @@ -77,11 +77,12 @@ "cypress": "^14.5.4", "del-cli": "^7.0.0", "eslint": "^8.57.1", + "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.5", "eslint-plugin-cypress": "^3.6.0", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jasmine": "^4.2.2", - "eslint-plugin-jsdoc": "^63.0.12", + "eslint-plugin-jsdoc": "^63.0.13", "eslint-plugin-json": "^4.0.1", "eslint-plugin-prefer-arrow": "^1.2.3", "husky": "^9.1.7", @@ -92,6 +93,7 @@ "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "^2.2.0", "ng-packagr": "^17.3.0", + "prettier": "^3.9.5", "start-server-and-test": "^3.0.11", "stylelint": "^17.14.0", "stylelint-config-recommended-scss": "^17.0.1", diff --git a/projects/.prettierignore b/projects/.prettierignore new file mode 100644 index 0000000..28bfa3e --- /dev/null +++ b/projects/.prettierignore @@ -0,0 +1,5 @@ +/.angular +/.nx +/coverage +/dist +node_modules diff --git a/projects/mat-datatable-demo/.eslintrc.json b/projects/mat-datatable-demo/.eslintrc.json index 5766e55..b743fb6 100644 --- a/projects/mat-datatable-demo/.eslintrc.json +++ b/projects/mat-datatable-demo/.eslintrc.json @@ -1,14 +1,9 @@ { "extends": "../../.eslintrc.json", - "ignorePatterns": [ - "!**/*", - "**/.angular/**" - ], + "ignorePatterns": ["!**/*", "**/.angular/**"], "overrides": [ { - "files": [ - "*.ts" - ], + "files": ["*.ts"], "excludedFiles": "cypress.config.ts", "rules": { "@angular-eslint/directive-selector": [ @@ -30,9 +25,7 @@ } }, { - "files": [ - "cypress.config.ts" - ], + "files": ["cypress.config.ts"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "projects/mat-datatable-demo/cypress/tsconfig.json", @@ -41,16 +34,12 @@ }, "settings": { "import/parsers": { - "@typescript-eslint/parser": [ - ".ts" - ] + "@typescript-eslint/parser": [".ts"] }, "import/resolver": { "node": true, "typescript": { - "project": [ - "projects/mat-datatable-demo/cypress/tsconfig.json" - ] + "project": ["projects/mat-datatable-demo/cypress/tsconfig.json"] } } }, @@ -61,18 +50,12 @@ "ignoreStatic": true } ], - "arrow-parens": [ - "error", - "as-needed" - ], "comma-dangle": "error", "jsdoc/require-param-type": "off", "jsdoc/require-returns-type": [ "off", { - "contexts": [ - "any" - ] + "contexts": ["any"] } ], "no-multiple-empty-lines": [ @@ -99,10 +82,7 @@ "classPropertiesAllowed": false } ], - "semi": [ - "error", - "always" - ] + "semi": ["error", "always"] } } ] diff --git a/projects/mat-datatable-demo/cypress/e2e/app.e2e.cy.ts b/projects/mat-datatable-demo/cypress/e2e/app.e2e.cy.ts index adf7579..ea7f9ea 100644 --- a/projects/mat-datatable-demo/cypress/e2e/app.e2e.cy.ts +++ b/projects/mat-datatable-demo/cypress/e2e/app.e2e.cy.ts @@ -4,40 +4,31 @@ describe('Test demo page', () => { }); it('contains the main components', () => { - cy.get('.page-title') - .should('have.text', 'Mat-Datatable-Demo'); + cy.get('.page-title').should('have.text', 'Mat-Datatable-Demo'); cy.get('#mat-mdc-slide-toggle-1-button') .should('be.visible') .should('have.class', 'mdc-switch--selected'); - cy.get('#mat-mdc-slide-toggle-1-button') - .invoke('attr', 'aria-checked') - .should('eq', 'true'); + cy.get('#mat-mdc-slide-toggle-1-button').invoke('attr', 'aria-checked').should('eq', 'true'); - cy.get('.content-table') - .should('be.visible'); + cy.get('.content-table').should('be.visible'); - cy.get('.content-table .mat-mdc-footer-cell').should($elements => { - expect($elements).to.have.length(3); + cy.get('.content-table .mat-mdc-footer-cell').should(($elements) => { + expect($elements).to.have.length(4); // 4 cells in row expect($elements.eq(0)).to.contain('filtered 103 / total 103'); }); - cy.get('#test-buttons') - .should('be.visible'); + cy.get('#test-buttons').should('be.visible'); - cy.get('.toggle-buttons-pane-button') - .should('be.visible'); + cy.get('.toggle-buttons-pane-button').should('be.visible'); }); it('should hide the buttons pane on click to hide-button', () => { - cy.get('#test-buttons') - .should('be.visible'); + cy.get('#test-buttons').should('be.visible'); - cy.get('.toggle-buttons-pane-button') - .click(); + cy.get('.toggle-buttons-pane-button').click(); - cy.get('#test-buttons') - .should('not.exist'); + cy.get('#test-buttons').should('not.exist'); }); }); diff --git a/projects/mat-datatable-demo/cypress/support/component-index.html b/projects/mat-datatable-demo/cypress/support/component-index.html index ac6e79f..faf3b5f 100644 --- a/projects/mat-datatable-demo/cypress/support/component-index.html +++ b/projects/mat-datatable-demo/cypress/support/component-index.html @@ -1,12 +1,12 @@ - + - - - + + + Components App
- \ No newline at end of file + diff --git a/projects/mat-datatable-demo/cypress/support/component.ts b/projects/mat-datatable-demo/cypress/support/component.ts index 6356a7b..3c0d594 100644 --- a/projects/mat-datatable-demo/cypress/support/component.ts +++ b/projects/mat-datatable-demo/cypress/support/component.ts @@ -29,7 +29,7 @@ declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface Chainable { - mount: typeof mount + mount: typeof mount; } } } diff --git a/projects/mat-datatable-demo/cypress/tsconfig.json b/projects/mat-datatable-demo/cypress/tsconfig.json index 8b3b8c8..ca827c1 100644 --- a/projects/mat-datatable-demo/cypress/tsconfig.json +++ b/projects/mat-datatable-demo/cypress/tsconfig.json @@ -1,17 +1,13 @@ /* "include": ["../cypress.config.ts"] is required for eslint */ - { +{ "extends": "../tsconfig.app.json", "compilerOptions": { "inlineSources": false, "sourceMap": false, "types": [] }, - "include": [ - "**/*.ts", - "../cypress.config.ts", - "node_modules/cypress" - ], + "include": ["**/*.ts", "../cypress.config.ts", "node_modules/cypress"], "exclude": [] } diff --git a/projects/mat-datatable-demo/karma.conf.js b/projects/mat-datatable-demo/karma.conf.js index 380af59..039313a 100644 --- a/projects/mat-datatable-demo/karma.conf.js +++ b/projects/mat-datatable-demo/karma.conf.js @@ -21,10 +21,7 @@ module.exports = function (config) { coverageReporter: { dir: require('path').join(__dirname, '../../coverage/mat-datatable-demo'), subdir: '.', - reporters: [ - { type: 'html' }, - { type: 'text-summary' } - ] + reporters: [{ type: 'html' }, { type: 'text-summary' }] }, reporters: ['progress', 'kjhtml'], port: 9876, diff --git a/projects/mat-datatable-demo/src/app/app.component.html b/projects/mat-datatable-demo/src/app/app.component.html index bddf31a..41418be 100644 --- a/projects/mat-datatable-demo/src/app/app.component.html +++ b/projects/mat-datatable-demo/src/app/app.component.html @@ -1,14 +1,20 @@
-

{{title}}

+

{{ title }}

Source on github: BePo65/mat-datatable
- With footer + With footer
- {{title}} (rowSelectionChange)="onRowSelectionChange($event)" (sortChange)="onSortChanged($event)" *mcRerender="withFooter" - > + > loading...
-
+
Current sort: - - {{headerFromColumnId(sort.columnId)}}, {{sort.direction}} + + {{ headerFromColumnId(sort.columnId) }}, {{ sort.direction }} @@ -40,24 +49,47 @@

{{title}}

- - - - - + + + + +
- - - + + + Active row: - {{activatedRowAsString}} + {{ activatedRowAsString }} - +
@@ -70,29 +102,65 @@

{{title}}

multi - - + + - Selected rows:{{selectedRowsAsString}} + Selected rows:{{ selectedRowsAsString }}
- - - - Current filter:{{currentFilterAsString}} + + + + Current filter:{{ currentFilterAsString }}
- - - + + +
-
diff --git a/projects/mat-datatable-demo/src/app/app.component.scss b/projects/mat-datatable-demo/src/app/app.component.scss index 9258a3e..0f587a4 100644 --- a/projects/mat-datatable-demo/src/app/app.component.scss +++ b/projects/mat-datatable-demo/src/app/app.component.scss @@ -1,7 +1,7 @@ .main { display: flex; flex-flow: column; - height: 100vH; + height: 100vh; overflow: hidden; width: 100vw; } @@ -23,7 +23,7 @@ } .content-table { - flex-grow : 1; + flex-grow: 1; margin: 0 1em 1.5em; overflow: auto; } @@ -38,7 +38,7 @@ .current-sort { margin: 0 0 0 1em; - min-height: 40px;; + min-height: 40px; } .current-sort-label { @@ -56,7 +56,7 @@ } .activate-row-container { - margin: .5em 0 0 1em; + margin: 0.5em 0 0 1em; } .activation-button-container, @@ -73,7 +73,7 @@ margin: 0 0.75em 0 1em; } - .activated-row-label { +.activated-row-label { margin: 0 0.25em 0 0; } @@ -101,7 +101,7 @@ margin: 1em 0 0 1em; } -.selected-rows-label { +.selected-rows-label { margin: 0 0.25em 0 1em; } @@ -121,8 +121,8 @@ margin: 0 0.25em 0 1em; } -.change-content-container{ - margin: .5em 0 0 1em; +.change-content-container { + margin: 0.5em 0 0 1em; } .toggle-buttons-pane-button { diff --git a/projects/mat-datatable-demo/src/app/app.component.spec.ts b/projects/mat-datatable-demo/src/app/app.component.spec.ts index 11a1c98..e100c8a 100644 --- a/projects/mat-datatable-demo/src/app/app.component.spec.ts +++ b/projects/mat-datatable-demo/src/app/app.component.spec.ts @@ -41,7 +41,7 @@ describe('AppComponent', () => { expect(app).toBeTruthy(); }); - it('should have as title \'Mat-Datatable-Demo\'', () => { + it("should have as title 'Mat-Datatable-Demo'", () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.componentInstance; diff --git a/projects/mat-datatable-demo/src/app/app.component.ts b/projects/mat-datatable-demo/src/app/app.component.ts index 42d77e8..3ad5b93 100644 --- a/projects/mat-datatable-demo/src/app/app.component.ts +++ b/projects/mat-datatable-demo/src/app/app.component.ts @@ -17,6 +17,8 @@ import { DemoTableDataStore } from './services/demo-table-datastore.class'; import { DemoTableItem } from './shared/demo-table-item.interface'; import { + CastPipe, + CellValueImage, FieldFilterDefinition, MatColumnDefinition, MatDatatableComponent, @@ -25,51 +27,75 @@ import { } from 'mat-datatable'; @Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], - animations: [ - trigger('slideInBottom', [ - transition(':enter', [ - style({ transform: 'translateY(100%)' }), - animate('500ms ease-out', style({ transform: 'translateY(0)' })) - ]), - transition(':leave', [ - animate('500ms ease-out', style({ transform: 'translateY(100%)' })) - ]) - ]) - ], - standalone: true, - imports: [ - AsyncPipe, - MatButton, - MatChip, - MatChipListbox, - MatChipRemove, - MatDatatableComponent, - MatFormField, - MatIcon, - MatLabel, - MatMiniFabButton, - MatOption, - MatSelect, - MatSlideToggle, - NgIf, - NgFor, - NgxRerenderModule - ] + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'], + animations: [ + trigger('slideInBottom', [ + transition(':enter', [ + style({ transform: 'translateY(100%)' }), + animate('500ms ease-out', style({ transform: 'translateY(0)' })) + ]), + transition(':leave', [animate('500ms ease-out', style({ transform: 'translateY(100%)' }))]) + ]) + ], + standalone: true, + imports: [ + AsyncPipe, + CastPipe, + MatButton, + MatChip, + MatChipListbox, + MatChipRemove, + MatDatatableComponent, + MatFormField, + MatIcon, + MatLabel, + MatMiniFabButton, + MatOption, + MatSelect, + MatSlideToggle, + NgIf, + NgFor, + NgxRerenderModule + ] }) export class AppComponent implements AfterViewInit, OnDestroy { @ViewChild('datatable') table!: MatDatatableComponent; title = 'Mat-Datatable-Demo'; + // Example for formatting boolean values in a column as images. + // The function is used in the column definition for the 'remoteAccess' column. + protected formatBool = (row: DemoTableItem) => { + const cellContentDefinition: CellValueImage = { type: 'image', url: '', altText: '' }; + switch (row?.remoteAccess) { + case true: + cellContentDefinition.url = 'assets/images/circle-green.png'; + cellContentDefinition.altText = 'with remote access'; + cellContentDefinition.title = 'with remote access'; + break; + case false: + cellContentDefinition.url = 'assets/images/circle-red.png'; + cellContentDefinition.altText = 'without remote access'; + cellContentDefinition.title = 'without remote access'; + break; + default: + cellContentDefinition.url = 'assets/images/circle-gray.png'; + cellContentDefinition.altText = 'unknown remote access'; + cellContentDefinition.title = 'unknown remote access'; + } + return cellContentDefinition; + }; + protected dataStore = new DemoTableDataStore(this.trackBy); protected columnDefinitions: MatColumnDefinition[] = [ { columnId: 'userId', header: 'ID', - cell: (row: DemoTableItem) => row?.userId?.toString(), + cell: (row: DemoTableItem) => { + return { type: 'string', text: row?.userId?.toString() }; + }, headerAlignment: 'right', cellAlignment: 'right', width: '5em', @@ -82,7 +108,9 @@ export class AppComponent implements AfterViewInit, OnDestroy { resizable: true, header: 'First Name', headerAlignment: 'left', - cell: (row: DemoTableItem) => row?.firstName, + cell: (row: DemoTableItem) => { + return { type: 'string', text: row?.firstName }; + }, cellAlignment: 'left', width: '10em' }, @@ -91,7 +119,9 @@ export class AppComponent implements AfterViewInit, OnDestroy { sortable: true, header: 'Last Name', headerAlignment: 'right', - cell: (row: DemoTableItem) => row?.lastName, + cell: (row: DemoTableItem) => { + return { type: 'string', text: row?.lastName }; + }, cellAlignment: 'right', width: '10em', footer: 'Filter: -', @@ -101,10 +131,11 @@ export class AppComponent implements AfterViewInit, OnDestroy { columnId: 'email', resizable: true, header: 'EMail', - cell: (row: DemoTableItem) => row?.email, + cell: (row: DemoTableItem) => { + return { type: 'mailtoLink', text: row?.email, url: `mailto:${row?.email}` }; + }, width: '20em', - tooltip: (row: DemoTableItem) => row?.email, - showAsMailtoLink: true + tooltip: (row: DemoTableItem) => row?.email }, { columnId: 'birthday', @@ -112,22 +143,45 @@ export class AppComponent implements AfterViewInit, OnDestroy { resizable: false, header: 'Birthday', headerAlignment: 'center', - cell: (row: DemoTableItem) => row?.birthday?.toLocaleDateString(this.currentLocale, { dateStyle: 'medium' }), + cell: (row: DemoTableItem) => { + return { + type: 'string', + text: row?.birthday?.toLocaleDateString(this.currentLocale, { dateStyle: 'medium' }) + }; + }, cellAlignment: 'center', width: '8em' }, { columnId: 'description', header: 'Description', - cell: (row: DemoTableItem) => row?.description, + cell: (row: DemoTableItem) => { + return { type: 'string', text: row?.description }; + }, tooltip: (row: DemoTableItem) => row?.description, showAsSingleLine: true, footer: 'First visible row: -', footerAlignment: 'right' + }, + { + columnId: 'remoteAccess', + sortable: true, + header: 'Remote Access?', + cell: this.formatBool, + cellAlignment: 'center', + width: '8em' } ]; protected withFooter = true; - protected displayedColumns: string[] = ['userId', 'firstName', 'lastName', 'email', 'birthday', 'description']; + protected displayedColumns: string[] = [ + 'userId', + 'firstName', + 'lastName', + 'email', + 'birthday', + 'remoteAccess', + 'description' + ]; protected currentSorts: MatSortDefinition[] = []; protected readonly currentSorts$ = new BehaviorSubject([]); protected currentSelectionMode: RowSelectionType = 'none'; @@ -147,47 +201,55 @@ export class AppComponent implements AfterViewInit, OnDestroy { private formattedDatastoreLengths = 'filtered {0} / total {1}'; private formattedFirstVisibleRow = 'First visible row: {0}'; - constructor(private iconRegistry: MatIconRegistry, private sanitizer: DomSanitizer) { + constructor( + private iconRegistry: MatIconRegistry, + private sanitizer: DomSanitizer + ) { this.currentLocale = new Intl.NumberFormat().resolvedOptions().locale; // Register svg icons this.iconRegistry .addSvgIconLiteral('show_button_pane', this.sanitizer.bypassSecurityTrustHtml(ARROW_UPWARD)) - .addSvgIconLiteral('hide_button_pane', this.sanitizer.bypassSecurityTrustHtml(ARROW_DOWNWARD)); + .addSvgIconLiteral( + 'hide_button_pane', + this.sanitizer.bypassSecurityTrustHtml(ARROW_DOWNWARD) + ); this.headersTextFromColumnDefinitions(); } ngAfterViewInit(): void { this.table.totalRowsChanged - .pipe( - takeUntil(this.unsubscribe$), - delay(0) - ) - .subscribe(numberOfRows => { - this.numberOfRows = numberOfRows.toString(); - this.columnDefinitions[0].footer = this.formatString(this.formattedDatastoreLengths, this.numberOfFilteredRows.toString(), this.numberOfRows.toString()); - }); + .pipe(takeUntil(this.unsubscribe$), delay(0)) + .subscribe((numberOfRows) => { + this.numberOfRows = numberOfRows.toString(); + this.columnDefinitions[0].footer = this.formatString( + this.formattedDatastoreLengths, + this.numberOfFilteredRows.toString(), + this.numberOfRows.toString() + ); + }); this.table.filteredRowsChanged - .pipe( - takeUntil(this.unsubscribe$), - delay(0) - ) - .subscribe(numberOfRows => { - this.numberOfFilteredRows = numberOfRows.toString(); - this.columnDefinitions[0].footer = this.formatString(this.formattedDatastoreLengths, this.numberOfFilteredRows.toString(), this.numberOfRows.toString()); - }); + .pipe(takeUntil(this.unsubscribe$), delay(0)) + .subscribe((numberOfRows) => { + this.numberOfFilteredRows = numberOfRows.toString(); + this.columnDefinitions[0].footer = this.formatString( + this.formattedDatastoreLengths, + this.numberOfFilteredRows.toString(), + this.numberOfRows.toString() + ); + }); this.table.firstVisibleIndexChanged - .pipe( - takeUntil(this.unsubscribe$), - delay(0) - ) - .subscribe(firstVisibleRow => { - this.firstVisibleRow = firstVisibleRow.toString(); - this.columnDefinitions[5].footer = this.formatString(this.formattedFirstVisibleRow, this.firstVisibleRow.toString()); - }); + .pipe(takeUntil(this.unsubscribe$), delay(0)) + .subscribe((firstVisibleRow) => { + this.firstVisibleRow = firstVisibleRow.toString(); + this.columnDefinitions[5].footer = this.formatString( + this.formattedFirstVisibleRow, + this.firstVisibleRow.toString() + ); + }); } ngOnDestroy(): void { @@ -211,10 +273,11 @@ export class AppComponent implements AfterViewInit, OnDestroy { protected onRowSelectionChange($event: DemoTableItem[]) { let result = '-'; if ($event.length > 0) { - result = $event - .sort((a: DemoTableItem, b: DemoTableItem) => a.userId - b.userId ) - .map(row => row?.userId) - .join('; ') || '-'; + result = + $event + .sort((a: DemoTableItem, b: DemoTableItem) => a.userId - b.userId) + .map((row) => row?.userId) + .join('; ') || '-'; } this.selectedRowsAsString = result; } @@ -240,49 +303,43 @@ export class AppComponent implements AfterViewInit, OnDestroy { * @param columnId - ID of column to be removed from sorting */ protected removeSort(columnId: string): void { - const newSort = this.currentSorts.filter(sort => sort.columnId !== columnId); + const newSort = this.currentSorts.filter((sort) => sort.columnId !== columnId); this.table.sortDefinitions = newSort; } // Demo to show sorting by code protected onSortId() { - const newSort: MatSortDefinition[] = [ - { columnId:'userId', direction:'asc' } - ]; + const newSort: MatSortDefinition[] = [{ columnId: 'userId', direction: 'asc' }]; this.table.sortDefinitions = newSort; } protected onSortLastNameFirstNameBirthday() { const newSort: MatSortDefinition[] = [ - { columnId:'lastName', direction:'asc' }, - { columnId:'firstName', direction:'asc' }, - { columnId:'birthday', direction:'asc' } + { columnId: 'lastName', direction: 'asc' }, + { columnId: 'firstName', direction: 'asc' }, + { columnId: 'birthday', direction: 'asc' } ]; this.table.sortDefinitions = newSort; } protected onSortLastNameBirthdayAsc() { const newSort: MatSortDefinition[] = [ - { columnId:'lastName', direction:'asc' }, - { columnId:'birthday', direction:'asc' } + { columnId: 'lastName', direction: 'asc' }, + { columnId: 'birthday', direction: 'asc' } ]; this.table.sortDefinitions = newSort; } protected onSortLastNameBirthdayDesc() { const newSort: MatSortDefinition[] = [ - { columnId:'lastName', direction:'asc' }, - { columnId:'birthday', direction:'desc' } + { columnId: 'lastName', direction: 'asc' }, + { columnId: 'birthday', direction: 'desc' } ]; this.table.sortDefinitions = newSort; } protected onSortBirthdayAsc() { - const newSort: MatSortDefinition[] = [ - { columnId:'birthday', direction:'asc' } - ]; + const newSort: MatSortDefinition[] = [{ columnId: 'birthday', direction: 'asc' }]; this.table.sortDefinitions = newSort; } protected onSortBirthdayDesc() { - const newSort: MatSortDefinition[] = [ - { columnId:'birthday', direction:'desc' } - ]; + const newSort: MatSortDefinition[] = [{ columnId: 'birthday', direction: 'desc' }]; this.table.sortDefinitions = newSort; } protected onClearSort() { @@ -302,7 +359,7 @@ export class AppComponent implements AfterViewInit, OnDestroy { // Demo to show setting selected rows by code protected onSetSelection() { - this.table.selectedRows = this.selectRowsByUserId([ 1, 3, 66 ]); + this.table.selectedRows = this.selectRowsByUserId([1, 3, 66]); } protected onClearSelection() { this.table.selectedRows = []; @@ -311,7 +368,7 @@ export class AppComponent implements AfterViewInit, OnDestroy { // Demo of scrollToRów protected onScrollIntoView(rowId?: number) { // row.userId or rowId are used, as 'trackBy' uses 'userId' as reference - const rowToScrollTo = this.dataStore.getUnsortedData().find(row => row.userId === rowId); + const rowToScrollTo = this.dataStore.getUnsortedData().find((row) => row.userId === rowId); if (rowToScrollTo) { this.table.scrollToRow(rowToScrollTo); } @@ -319,19 +376,23 @@ export class AppComponent implements AfterViewInit, OnDestroy { // Demo to show filtering by code protected onFilterByValue() { - const currentFilter = [{ fieldName:'lastName', value:'Abbott' }] as FieldFilterDefinition[]; + const currentFilter = [ + { fieldName: 'lastName', value: 'Abbott' } + ] as FieldFilterDefinition[]; this.table.filterDefinitions = currentFilter; this.currentFilterAsString = this.filterDefinitionToString(currentFilter); this.columnDefinitions[2].footer = `Filter: ${this.currentFilterAsString}`; } protected onFilterByRange() { - const currentFilter = [{ fieldName:'userId', valueFrom:50, valueTo:67 }] as FieldFilterDefinition[]; + const currentFilter = [ + { fieldName: 'userId', valueFrom: 50, valueTo: 67 } + ] as FieldFilterDefinition[]; this.table.filterDefinitions = currentFilter; this.currentFilterAsString = this.filterDefinitionToString(currentFilter); this.columnDefinitions[2].footer = `Filter: ${this.currentFilterAsString}`; } protected onClearFilter() { - const currentFilter = [] as FieldFilterDefinition[]; + const currentFilter = [] as FieldFilterDefinition[]; this.table.filterDefinitions = currentFilter; this.currentFilterAsString = this.filterDefinitionToString(currentFilter); this.columnDefinitions[2].footer = `Filter: ${this.currentFilterAsString}`; @@ -342,52 +403,52 @@ export class AppComponent implements AfterViewInit, OnDestroy { /* spell-checker: disable */ const newRows = [ { - 'userId': 205, - 'firstName': 'Gerty', - 'lastName': 'Briiginshaw', - 'email': 'gbriiginshaw0@nytimes.com', - 'birthday': new Date('2023-11-21'), - 'description': 'Aliquam quis turpis eget elit sodales scelerisque. Mauris sit amet eros.' + userId: 205, + firstName: 'Gerty', + lastName: 'Briiginshaw', + email: 'gbriiginshaw0@nytimes.com', + birthday: new Date('2023-11-21'), + description: 'Aliquam quis turpis eget elit sodales scelerisque. Mauris sit amet eros.' }, { - 'userId': 138, - 'firstName': 'Susannah', - 'lastName': 'Stellin', - 'email': 'sstellin1@booking.com', - 'birthday': new Date('2023-08-02'), - 'description': 'Sed sagittis.' + userId: 138, + firstName: 'Susannah', + lastName: 'Stellin', + email: 'sstellin1@booking.com', + birthday: new Date('2023-08-02'), + description: 'Sed sagittis.' }, { - 'userId': 301, - 'firstName': 'Buckie', - 'lastName': 'Beardall', - 'email': 'bbeardall2@tripod.com', - 'birthday': new Date('2023-10-14'), - 'description': 'Pellentesque ultrices mattis odio. Donec vitae nisi.' + userId: 301, + firstName: 'Buckie', + lastName: 'Beardall', + email: 'bbeardall2@tripod.com', + birthday: new Date('2023-10-14'), + description: 'Pellentesque ultrices mattis odio. Donec vitae nisi.' }, { - 'userId': 114, - 'firstName': 'Winifred', - 'lastName': 'Mabbott', - 'email': 'wmabbott3@artisteer.com', - 'birthday': new Date('2023-10-24'), - 'description': 'Morbi non lectus.' + userId: 114, + firstName: 'Winifred', + lastName: 'Mabbott', + email: 'wmabbott3@artisteer.com', + birthday: new Date('2023-10-24'), + description: 'Morbi non lectus.' }, { - 'userId': 257, - 'firstName': 'Tadd', - 'lastName': 'Tindall', - 'email': 'ttindall4@who.int', - 'birthday': new Date('2023-11-22'), - 'description': 'Pellentesque eget nunc.' + userId: 257, + firstName: 'Tadd', + lastName: 'Tindall', + email: 'ttindall4@who.int', + birthday: new Date('2023-11-22'), + description: 'Pellentesque eget nunc.' }, { - 'userId': 146, - 'firstName': 'Debora', - 'lastName': 'Lawry', - 'email': 'dlawry5@tamu.edu', - 'birthday': new Date('2023-05-23'), - 'description': 'In hac habitasse platea dictumst.' + userId: 146, + firstName: 'Debora', + lastName: 'Lawry', + email: 'dlawry5@tamu.edu', + birthday: new Date('2023-05-23'), + description: 'In hac habitasse platea dictumst.' } ] as DemoTableItem[]; /* spell-checker: enable */ @@ -419,7 +480,7 @@ export class AppComponent implements AfterViewInit, OnDestroy { * @returns row selected by the given userId */ private selectSingleRowByUserId(rowId: number) { - return this.dataStore.getUnsortedData().find(row => row.userId === rowId); + return this.dataStore.getUnsortedData().find((row) => row.userId === rowId); } /** @@ -429,8 +490,8 @@ export class AppComponent implements AfterViewInit, OnDestroy { */ private selectRowsByUserId(rowIds: number[]): DemoTableItem[] { let result: DemoTableItem[] = []; - if (rowIds && Array.isArray(rowIds) && (rowIds.length > 0)) { - result = this.dataStore.getUnsortedData().filter(row => rowIds.includes(row.userId)); + if (rowIds && Array.isArray(rowIds) && rowIds.length > 0) { + result = this.dataStore.getUnsortedData().filter((row) => rowIds.includes(row.userId)); } return result; } @@ -476,13 +537,15 @@ export class AppComponent implements AfterViewInit, OnDestroy { * @param filterDefinition - array with filter definitions * @returns filter definition, formatted string */ - private filterDefinitionToString(filterDefinition: FieldFilterDefinition[]): string { + private filterDefinitionToString( + filterDefinition: FieldFilterDefinition[] + ): string { let filterAsString = '-'; - if(filterDefinition && Array.isArray(filterDefinition) && (filterDefinition.length > 0)) { + if (filterDefinition && Array.isArray(filterDefinition) && filterDefinition.length > 0) { const filter = filterDefinition[0]; if (filter.value !== undefined) { filterAsString = `'${filter.fieldName}' = '${filter.value.toString()}' `; - } else if (filter.valueFrom !== undefined) { + } else if (filter.valueFrom !== undefined) { filterAsString = `'${filter.fieldName}' = ${filter.valueFrom.toString()}-${filter.valueTo.toString()}`; } } diff --git a/projects/mat-datatable-demo/src/app/app.svg.ts b/projects/mat-datatable-demo/src/app/app.svg.ts index 5d5edba..8077202 100644 --- a/projects/mat-datatable-demo/src/app/app.svg.ts +++ b/projects/mat-datatable-demo/src/app/app.svg.ts @@ -4,14 +4,12 @@ * License: Apache License Version 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt) */ -export const ARROW_UPWARD = - ` +export const ARROW_UPWARD = ` `; -export const ARROW_DOWNWARD = - ` +export const ARROW_DOWNWARD = ` `; diff --git a/projects/mat-datatable-demo/src/app/services/demo-table-datastore.class.ts b/projects/mat-datatable-demo/src/app/services/demo-table-datastore.class.ts index 22a9290..53490c7 100644 --- a/projects/mat-datatable-demo/src/app/services/demo-table-datastore.class.ts +++ b/projects/mat-datatable-demo/src/app/services/demo-table-datastore.class.ts @@ -69,8 +69,11 @@ export class DemoTableDataStore implements DataStoreProvider this.trackBy(0, row) === this.trackBy(0, currentRow))) - .pipe(delay(simulatedResponseTime)); + return of( + selectedDataset.findIndex( + (currentRow) => this.trackBy(0, row) === this.trackBy(0, currentRow) + ) + ).pipe(delay(simulatedResponseTime)); } /** @@ -124,26 +127,31 @@ export class DemoTableDataStore implements DataStoreProvider 0)) { + if (filters !== undefined && Array.isArray(filters) && filters.length > 0) { selectedDataset = selectedDataset.filter((row: DatatableItem) => { - return filters.reduce((isSelected: boolean, currentFilter: FieldFilterDefinition) => { - if (currentFilter.value !== undefined) { - isSelected ||= row[currentFilter.fieldName] === currentFilter.value; - } else if ((currentFilter.valueFrom !== undefined) && (currentFilter.valueTo !== undefined)) { - isSelected ||= ( - (row[currentFilter.fieldName] >= currentFilter.valueFrom) && - (row[currentFilter.fieldName] <= currentFilter.valueTo) - ); - } - return isSelected; - }, false); + return filters.reduce( + (isSelected: boolean, currentFilter: FieldFilterDefinition) => { + if (currentFilter.value !== undefined) { + isSelected ||= row[currentFilter.fieldName] === currentFilter.value; + } else if ( + currentFilter.valueFrom !== undefined && + currentFilter.valueTo !== undefined + ) { + isSelected ||= + row[currentFilter.fieldName] >= currentFilter.valueFrom && + row[currentFilter.fieldName] <= currentFilter.valueTo; + } + return isSelected; + }, + false + ); }); } // Sort data - if ((sorts !== undefined) && Array.isArray(sorts)) { + if (sorts !== undefined && Array.isArray(sorts)) { this.currentSortingDefinitions = sorts; - if ((sorts.length > 0)) { + if (sorts.length > 0) { selectedDataset.sort(this.compareFn); } } @@ -161,7 +169,7 @@ export class DemoTableDataStore implements DataStoreProvider implements DataStoreProviderb) */ private compare(a: string | number, b: string | number, isAsc: boolean): number { - return (a === b ? 0 : (a < b ? -1 : 1)) * (isAsc ? 1 : -1); + return (a === b ? 0 : a < b ? -1 : 1) * (isAsc ? 1 : -1); } } diff --git a/projects/mat-datatable-demo/src/app/services/demo-table.mock.data.ts b/projects/mat-datatable-demo/src/app/services/demo-table.mock.data.ts index 37444dd..0279839 100644 --- a/projects/mat-datatable-demo/src/app/services/demo-table.mock.data.ts +++ b/projects/mat-datatable-demo/src/app/services/demo-table.mock.data.ts @@ -1,827 +1,919 @@ // created by https://www.mockaroo.com/; birthday is changed to a Date afterwards. export default [ { - 'userId': 1, - 'firstName': 'Rashawn', - 'lastName': 'Goyette', - 'email': 'Alexa1@gmail.com', - 'birthday': new Date('2000-11-21T06:55:38.655Z'), - 'description': 'Provident numquam possimus harum maxime repellat repellat optio quas.' - }, - { - 'userId': 103, - 'firstName': 'Rashawn', - 'lastName': 'Irvin', - 'email': 'IrvinR@gmail.com', - 'birthday': new Date('2000-11-21T16:55:38.655Z'), - 'description': 'Provident numquam possimus harum maxime repellat repellat optio quas.' - }, - { - 'userId': 2, - 'firstName': 'Jesse', - 'lastName': 'Littel', - 'email': 'Lura_Predovic@hotmail.com', - 'birthday': new Date('1980-07-18T09:14:13.937Z'), - 'description': 'Odit quibusdam tempora sapiente.' - }, - { - 'userId': 3, - 'firstName': 'Mazie', - 'lastName': 'Bradtke', - 'email': 'Concepcion25@yahoo.com', - 'birthday': new Date('1996-12-25T12:38:46.992Z'), - 'description': 'Ipsam temporibus exercitationem ipsum architecto.' - }, - { - 'userId': 4, - 'firstName': 'Gabrielle', - 'lastName': 'Predovic', - 'email': 'Emory.Schamberger@yahoo.com', - 'birthday': new Date('1975-01-08T09:45:13.052Z'), - 'description': 'Excepturi hic sint velit molestias enim aliquid voluptas cumque consequatur.' - }, - { - 'userId': 5, - 'firstName': 'Lily', - 'lastName': 'Rolfson', - 'email': 'Webster.Quigley67@yahoo.com', - 'birthday': new Date('1948-07-04T02:09:40.601Z'), - 'description': 'Nihil aliquam qui libero error modi pariatur quis voluptate possimus.' - }, - { - 'userId': 6, - 'firstName': 'Adah', - 'lastName': 'Hauck', - 'email': 'Vincenzo_Runte14@yahoo.com', - 'birthday': new Date('1966-02-11T06:49:26.101Z'), - 'description': 'Vero autem blanditiis omnis.' - }, - { - 'userId': 7, - 'firstName': 'Jessie', - 'lastName': 'Kuvalis', - 'email': 'Madelyn5@gmail.com', - 'birthday': new Date('1976-03-15T21:41:16.606Z'), - 'description': 'Veritatis beatae maxime molestias accusantium beatae.' - }, - { - 'userId': 8, - 'firstName': 'Alene', - 'lastName': 'D\'Amore', - 'email': 'Lura.Stokes@yahoo.com', - 'birthday': new Date('1992-10-15T00:54:04.161Z'), - 'description': 'Saepe quo doloribus minus quia error.' - }, - { - 'userId': 9, - 'firstName': 'Lea', - 'lastName': 'Keeling', - 'email': 'Dallin_OConner@gmail.com', - 'birthday': new Date('1980-09-09T12:17:11.494Z'), - 'description': 'Similique veniam debitis voluptate laudantium doloribus.' - }, - { - 'userId': 10, - 'firstName': 'Orrin', - 'lastName': 'Orn', - 'email': 'Dan_Torp23@yahoo.com', - 'birthday': new Date('1953-01-25T02:11:42.523Z'), - 'description': 'Totam inventore placeat magnam doloremque.' - }, - { - 'userId': 11, - 'firstName': 'Cleveland', - 'lastName': 'Koelpin', - 'email': 'Barrett69@hotmail.com', - 'birthday': new Date('1995-02-21T19:45:18.994Z'), - 'description': 'Facilis eligendi sed animi iusto repellendus quas ab quos facilis.' - }, - { - 'userId': 12, - 'firstName': 'Carol', - 'lastName': 'Braun', - 'email': 'Krystal_Upton@gmail.com', - 'birthday': new Date('1962-05-04T00:45:00.078Z'), - 'description': 'Accusamus vero repellat quae modi officiis molestias harum dignissimos distinctio.' - }, - { - 'userId': 13, - 'firstName': 'Dena', - 'lastName': 'Goodwin', - 'email': 'Rico34@yahoo.com', - 'birthday': new Date('1979-11-03T11:50:10.263Z'), - 'description': 'Necessitatibus eum dolores alias maiores est.' - }, - { - 'userId': 14, - 'firstName': 'Shad', - 'lastName': 'Kerluke', - 'email': 'Julien.Spencer37@hotmail.com', - 'birthday': new Date('1990-09-15T13:32:19.544Z'), - 'description': 'Numquam accusantium reiciendis cumque unde.' - }, - { - 'userId': 15, - 'firstName': 'Tina', - 'lastName': 'Lowe', - 'email': 'Miller_Kling@hotmail.com', - 'birthday': new Date('1968-11-25T17:32:53.658Z'), - 'description': 'Eum sit excepturi nesciunt doloribus doloribus neque tempora eum molestias.' - }, - { - 'userId': 16, - 'firstName': 'Novella', - 'lastName': 'Schmidt', - 'email': 'Josiane78@yahoo.com', - 'birthday': new Date('1983-03-06T20:36:10.796Z'), - 'description': 'Quod maiores vel.' - }, - { - 'userId': 17, - 'firstName': 'Reid', - 'lastName': 'Rolfson', - 'email': 'Berry_Sanford72@hotmail.com', - 'birthday': new Date('1953-03-17T01:23:24.335Z'), - 'description': 'Aut velit autem necessitatibus eaque.' - }, - { - 'userId': 18, - 'firstName': 'Cynthia', - 'lastName': 'Reichert', - 'email': 'Earnestine_Leannon98@gmail.com', - 'birthday': new Date('1971-11-26T00:38:55.102Z'), - 'description': 'Aut totam cupiditate illum.' - }, - { - 'userId': 19, - 'firstName': 'Jamar', - 'lastName': 'Hauck', - 'email': 'Mariela_Batz93@gmail.com', - 'birthday': new Date('1969-09-22T06:02:27.247Z'), - 'description': 'Perferendis quam officiis.' - }, - { - 'userId': 20, - 'firstName': 'Ada', - 'lastName': 'Ernser', - 'email': 'Rosendo_Breitenberg47@hotmail.com', - 'birthday': new Date('1984-02-27T07:45:35.737Z'), - 'description': 'Facilis debitis animi assumenda eveniet error.' - }, - { - 'userId': 21, - 'firstName': 'Tabitha', - 'lastName': 'Wiza', - 'email': 'Chasity.Jast@gmail.com', - 'birthday': new Date('1958-09-21T04:57:27.912Z'), - 'description': 'Doloribus maxime culpa aliquam rem excepturi officiis aliquid ratione alias.' - }, - { - 'userId': 22, - 'firstName': 'Frida', - 'lastName': 'Hermann', - 'email': 'Britney45@yahoo.com', - 'birthday': new Date('1999-04-20T18:18:30.593Z'), - 'description': 'Quasi esse ipsam totam fugit mollitia vero eum.' - }, - { - 'userId': 23, - 'firstName': 'Tavares', - 'lastName': 'Gottlieb', - 'email': 'Nils84@gmail.com', - 'birthday': new Date('1961-11-18T12:45:58.248Z'), - 'description': 'Illo consequatur eligendi exercitationem qui aspernatur sequi repudiandae.' - }, - { - 'userId': 24, - 'firstName': 'Otha', - 'lastName': 'Witting', - 'email': 'Watson.Smith80@gmail.com', - 'birthday': new Date('1946-07-01T13:44:28.632Z'), - 'description': 'At exercitationem tenetur accusamus nemo ad et.' - }, - { - 'userId': 25, - 'firstName': 'Ted', - 'lastName': 'Quigley', - 'email': 'Brenden.Hilll@hotmail.com', - 'birthday': new Date('1943-09-27T10:12:58.472Z'), - 'description': 'Atque modi illo.' - }, - { - 'userId': 26, - 'firstName': 'Curt', - 'lastName': 'Welch', - 'email': 'Ruthie.Blick39@hotmail.com', - 'birthday': new Date('1971-05-10T11:35:57.099Z'), - 'description': 'Ad modi ad aut itaque nisi.' - }, - { - 'userId': 27, - 'firstName': 'Madeline', - 'lastName': 'Kassulke', - 'email': 'Palma_Halvorson10@yahoo.com', - 'birthday': new Date('1994-11-06T10:27:13.921Z'), - 'description': 'Voluptatibus repudiandae vero quasi quaerat quisquam officiis.' - }, - { - 'userId': 28, - 'firstName': 'Aletha', - 'lastName': 'Dooley', - 'email': 'Lilly51@yahoo.com', - 'birthday': new Date('1998-03-16T07:43:19.509Z'), - 'description': 'Cumque quis itaque debitis labore quaerat reprehenderit dolorem.' - }, - { - 'userId': 29, - 'firstName': 'Lila', - 'lastName': 'Dickinson', - 'email': 'Frieda.Konopelski95@hotmail.com', - 'birthday': new Date('1963-10-11T08:48:51.425Z'), - 'description': 'Fugit perspiciatis dolorem inventore ad voluptas libero.' - }, - { - 'userId': 30, - 'firstName': 'Bessie', - 'lastName': 'Larson', - 'email': 'Janet72@yahoo.com', - 'birthday': new Date('1972-10-28T00:51:58.473Z'), - 'description': 'Consequatur iusto quas eum labore.' - }, - { - 'userId': 31, - 'firstName': 'Mac', - 'lastName': 'Hickle', - 'email': 'Nia_Johnston85@hotmail.com', - 'birthday': new Date('1956-06-03T16:11:53.374Z'), - 'description': 'Doloremque illo sint modi minus cum.' - }, - { - 'userId': 32, - 'firstName': 'Nya', - 'lastName': 'Bahringer', - 'email': 'Yasmeen_Daugherty63@gmail.com', - 'birthday': new Date('1993-04-01T19:03:06.411Z'), - 'description': 'Earum odio odit dolores sapiente illo cumque fugiat ad.' - }, - { - 'userId': 33, - 'firstName': 'Dakota', - 'lastName': 'Keebler', - 'email': 'Lesley.Sauer19@yahoo.com', - 'birthday': new Date('1969-11-07T06:40:21.732Z'), - 'description': 'Voluptas incidunt tempora architecto quae a blanditiis sapiente vel.' - }, - { - 'userId': 34, - 'firstName': 'Murphy', - 'lastName': 'Roberts', - 'email': 'Dan.Effertz@hotmail.com', - 'birthday': new Date('2000-01-18T08:02:53.660Z'), - 'description': 'Magni alias praesentium quae quibusdam qui.' - }, - { - 'userId': 35, - 'firstName': 'Johnpaul', - 'lastName': 'O\'Conner', - 'email': 'Bernardo15@gmail.com', - 'birthday': new Date('1988-05-15T21:23:16.217Z'), - 'description': 'Deleniti quos laudantium enim reiciendis doloremque fugiat culpa.' - }, - { - 'userId': 36, - 'firstName': 'Meggie', - 'lastName': 'Williamson', - 'email': 'Kassandra_Stanton@yahoo.com', - 'birthday': new Date('1944-02-22T16:22:41.901Z'), - 'description': 'Ullam repellendus nisi sit.' - }, - { - 'userId': 37, - 'firstName': 'Deborah', - 'lastName': 'Abbott', - 'email': 'Deborah.Abbott@gmail.com', - 'birthday': new Date('2003-06-18T22:38:30.281Z'), - 'description': 'Modi ullam assumenda magni quisquam consequuntur quis perferendis culpa.' - }, - { - 'userId': 101, - 'firstName': 'Anna', - 'lastName': 'Abbott', - 'email': 'Anna.Abbott1@gmail.com', - 'birthday': new Date('2013-04-23T22:38:30.281Z'), - 'description': 'Modi ullam assumenda magni quisquam consequuntur quis perferendis culpa.' - }, - { - 'userId': 102, - 'firstName': 'Anna', - 'lastName': 'Abbott', - 'email': 'Anna.Abbott2@gmail.com', - 'birthday': new Date('2002-11-02T22:38:30.281Z'), - 'description': 'Modi ullam assumenda magni quisquam consequuntur quis perferendis culpa.' - }, - { - 'userId': 38, - 'firstName': 'Aaliyah', - 'lastName': 'Sporer', - 'email': 'Floyd59@yahoo.com', - 'birthday': new Date('1958-06-27T08:58:58.561Z'), - 'description': 'Exercitationem nemo minima aut soluta suscipit voluptatem voluptatum vitae.' - }, - { - 'userId': 39, - 'firstName': 'Emerson', - 'lastName': 'O\'Keefe', - 'email': 'Evan.Turner@hotmail.com', - 'birthday': new Date('1988-12-30T13:07:12.997Z'), - 'description': 'Corporis laudantium dolor error voluptate laborum ut.' - }, - { - 'userId': 40, - 'firstName': 'Gregory', - 'lastName': 'Crist', - 'email': 'Letitia.Barton81@gmail.com', - 'birthday': new Date('1981-02-14T19:00:47.359Z'), - 'description': 'Veritatis assumenda mollitia libero.' - }, - { - 'userId': 41, - 'firstName': 'Macie', - 'lastName': 'Howell', - 'email': 'Zachary37@hotmail.com', - 'birthday': new Date('1979-01-22T11:50:56.998Z'), - 'description': 'Voluptatum laborum ab aut nam.' - }, - { - 'userId': 42, - 'firstName': 'Garry', - 'lastName': 'Miller', - 'email': 'Wallace78@yahoo.com', - 'birthday': new Date('1967-12-24T10:06:07.922Z'), - 'description': 'Ducimus sapiente iusto maxime voluptas amet laboriosam optio veniam ea.' - }, - { - 'userId': 43, - 'firstName': 'Kristian', - 'lastName': 'Mills', - 'email': 'Shana_Hane@yahoo.com', - 'birthday': new Date('1998-05-25T19:37:35.197Z'), - 'description': 'Eos laudantium blanditiis in.' - }, - { - 'userId': 44, - 'firstName': 'Annamarie', - 'lastName': 'Lowe', - 'email': 'Reyes38@hotmail.com', - 'birthday': new Date('1957-01-17T22:16:36.274Z'), - 'description': 'Consequuntur quaerat at nihil.' - }, - { - 'userId': 45, - 'firstName': 'Nicola', - 'lastName': 'Lakin', - 'email': 'Olin_Cruickshank67@gmail.com', - 'birthday': new Date('1950-01-21T11:34:29.968Z'), - 'description': 'Non corporis quidem iusto asperiores corporis officiis assumenda incidunt.' - }, - { - 'userId': 46, - 'firstName': 'Kennedi', - 'lastName': 'Renner', - 'email': 'Johnpaul.Metz3@yahoo.com', - 'birthday': new Date('1997-06-10T13:13:35.766Z'), - 'description': 'Asperiores tempora eveniet quaerat voluptatum rem iste.' - }, - { - 'userId': 47, - 'firstName': 'Wallace', - 'lastName': 'Hegmann', - 'email': 'Beau_Maggio7@yahoo.com', - 'birthday': new Date('1975-11-02T01:52:46.711Z'), - 'description': 'Doloremque odit praesentium temporibus.' - }, - { - 'userId': 48, - 'firstName': 'Kaia', - 'lastName': 'Frami', - 'email': 'Kaci29@gmail.com', - 'birthday': new Date('1953-11-25T07:41:59.208Z'), - 'description': 'Eligendi consequuntur tempore commodi nostrum illo quibusdam.' - }, - { - 'userId': 49, - 'firstName': 'Vallie', - 'lastName': 'Kilback', - 'email': 'Ben_OConner51@yahoo.com', - 'birthday': new Date('1990-12-08T14:00:37.514Z'), - 'description': 'Ducimus necessitatibus dolore dolores.' - }, - { - 'userId': 50, - 'firstName': 'Giovanna', - 'lastName': 'Ernser', - 'email': 'Micaela_Morissette@gmail.com', - 'birthday': new Date('1959-04-24T17:42:14.319Z'), - 'description': 'Doloremque minus temporibus blanditiis architecto occaecati alias quis.' - }, - { - 'userId': 51, - 'firstName': 'Kian', - 'lastName': 'Hirthe', - 'email': 'Gunnar50@hotmail.com', - 'birthday': new Date('1965-09-28T11:51:48.083Z'), - 'description': 'Consectetur beatae error necessitatibus officia accusamus.' - }, - { - 'userId': 52, - 'firstName': 'Lenny', - 'lastName': 'O\'Kon', - 'email': 'Koby_Wintheiser0@gmail.com', - 'birthday': new Date('1965-03-07T02:57:47.692Z'), - 'description': 'Fuga tenetur cumque vel.' - }, - { - 'userId': 53, - 'firstName': 'Vergie', - 'lastName': 'Jerde', - 'email': 'Terence.Denesik18@yahoo.com', - 'birthday': new Date('1994-11-16T15:03:26.539Z'), - 'description': 'Inventore doloribus facere earum perferendis amet quibusdam facere expedita ex.' - }, - { - 'userId': 54, - 'firstName': 'Myrl', - 'lastName': 'Mills', - 'email': 'Melisa.Hirthe@yahoo.com', - 'birthday': new Date('1944-11-27T21:33:20.408Z'), - 'description': 'Nulla fugit illum dolorem incidunt.' - }, - { - 'userId': 55, - 'firstName': 'Leanne', - 'lastName': 'Hills', - 'email': 'Darwin_Renner@yahoo.com', - 'birthday': new Date('1984-03-10T20:01:43.662Z'), - 'description': 'Optio magnam accusantium eligendi.' - }, - { - 'userId': 56, - 'firstName': 'Stephen', - 'lastName': 'Stanton', - 'email': 'Brycen_Reichel@hotmail.com', - 'birthday': new Date('1987-02-22T17:04:31.075Z'), - 'description': 'Eum adipisci tempore temporibus ducimus quod amet.' - }, - { - 'userId': 57, - 'firstName': 'Hettie', - 'lastName': 'Kreiger', - 'email': 'Jamaal.Mertz5@yahoo.com', - 'birthday': new Date('1983-05-13T22:29:06.357Z'), - 'description': 'Ab dolor magnam esse.' - }, - { - 'userId': 58, - 'firstName': 'Iliana', - 'lastName': 'Jacobi', - 'email': 'Jarod47@gmail.com', - 'birthday': new Date('2005-12-07T06:58:16.179Z'), - 'description': 'Labore aliquid ducimus repellat non facilis vitae.' - }, - { - 'userId': 59, - 'firstName': 'Lucile', - 'lastName': 'Swaniawski', - 'email': 'Marguerite_Goyette@yahoo.com', - 'birthday': new Date('1943-09-02T23:28:12.608Z'), - 'description': 'Minus praesentium nemo explicabo reiciendis incidunt.' - }, - { - 'userId': 60, - 'firstName': 'Paige', - 'lastName': 'Frami', - 'email': 'Lucas_Spencer@yahoo.com', - 'birthday': new Date('1990-12-05T11:05:27.917Z'), - 'description': 'Ipsa explicabo doloremque odio at tenetur.' - }, - { - 'userId': 61, - 'firstName': 'Sylvia', - 'lastName': 'O\'Kon', - 'email': 'Brown.Zieme94@gmail.com', - 'birthday': new Date('1960-03-06T04:10:03.122Z'), - 'description': 'Laboriosam quaerat quas corrupti necessitatibus quam officia est optio.' - }, - { - 'userId': 62, - 'firstName': 'Johnathan', - 'lastName': 'Lueilwitz', - 'email': 'Ruben.Anderson87@gmail.com', - 'birthday': new Date('1994-12-30T01:07:07.400Z'), - 'description': 'Aliquid magnam assumenda vel rem tempore suscipit voluptas tempore ducimus.' - }, - { - 'userId': 63, - 'firstName': 'Ian', - 'lastName': 'D\'Amore', - 'email': 'Josie_Konopelski31@hotmail.com', - 'birthday': new Date('1945-05-06T01:22:27.815Z'), - 'description': 'Quos doloremque quo provident velit rem.' - }, - { - 'userId': 64, - 'firstName': 'Pat', - 'lastName': 'Simonis', - 'email': 'Dax.Marks33@hotmail.com', - 'birthday': new Date('1982-01-30T16:32:55.326Z'), - 'description': 'Beatae dolore laborum aliquid tempore doloremque repellat facere.' - }, - { - 'userId': 65, - 'firstName': 'Mathias', - 'lastName': 'Friesen', - 'email': 'Davion12@yahoo.com', - 'birthday': new Date('1944-11-21T18:43:37.270Z'), - 'description': 'Neque voluptas inventore alias enim numquam totam doloremque nobis.' - }, - { - 'userId': 66, - 'firstName': 'Cydney', - 'lastName': 'Kirlin', - 'email': 'Kaycee.Bradtke32@gmail.com', - 'birthday': new Date('1977-08-21T08:31:22.456Z'), - 'description': 'Ex quod quae eveniet quos placeat temporibus.' - }, - { - 'userId': 67, - 'firstName': 'Prince', - 'lastName': 'Schumm', - 'email': 'Vernon_Waters@gmail.com', - 'birthday': new Date('1946-04-20T07:01:53.871Z'), - 'description': 'Harum quis ipsum magnam facilis.' - }, - { - 'userId': 68, - 'firstName': 'Shirley', - 'lastName': 'Ferry', - 'email': 'Alec.Kunze40@hotmail.com', - 'birthday': new Date('1977-03-27T13:19:52.048Z'), - 'description': 'Modi dolorum ea excepturi non nihil a culpa pariatur.' - }, - { - 'userId': 69, - 'firstName': 'Coty', - 'lastName': 'Erdman', - 'email': 'Virgie_Dickinson@gmail.com', - 'birthday': new Date('2003-06-24T15:50:42.290Z'), - 'description': 'Illo non dolorum quaerat dolorem asperiores neque corrupti ex.' - }, - { - 'userId': 70, - 'firstName': 'Anita', - 'lastName': 'Strosin', - 'email': 'Makenzie_Mayer36@gmail.com', - 'birthday': new Date('2000-08-02T14:41:41.000Z'), - 'description': 'Dicta natus autem ipsum reprehenderit.' - }, - { - 'userId': 71, - 'firstName': 'Clementine', - 'lastName': 'Luettgen', - 'email': 'Darby90@yahoo.com', - 'birthday': new Date('1965-05-24T03:33:42.337Z'), - 'description': 'Praesentium fugit sint nostrum qui iusto voluptates voluptates.' - }, - { - 'userId': 72, - 'firstName': 'Haven', - 'lastName': 'Mante', - 'email': 'Marques40@hotmail.com', - 'birthday': new Date('1950-10-02T15:35:37.099Z'), - 'description': 'Illo necessitatibus inventore libero qui cumque qui recusandae.' - }, - { - 'userId': 73, - 'firstName': 'Minnie', - 'lastName': 'Kulas', - 'email': 'Lorenz_Beier@yahoo.com', - 'birthday': new Date('1962-09-01T10:21:54.891Z'), - 'description': 'At consequatur maxime reiciendis pariatur.' - }, - { - 'userId': 74, - 'firstName': 'Alf', - 'lastName': 'Wuckert', - 'email': 'Stanton4@yahoo.com', - 'birthday': new Date('1987-12-26T07:45:14.517Z'), - 'description': 'Quidem natus eveniet at eum impedit neque natus deserunt.' - }, - { - 'userId': 75, - 'firstName': 'Brandy', - 'lastName': 'Rowe', - 'email': 'Agustin.Brakus@yahoo.com', - 'birthday': new Date('1981-05-10T03:30:19.265Z'), - 'description': 'Aliquid possimus laborum doloremque modi non maxime.' - }, - { - 'userId': 76, - 'firstName': 'Margaretta', - 'lastName': 'Torp', - 'email': 'Antonetta36@hotmail.com', - 'birthday': new Date('1952-11-19T22:50:36.420Z'), - 'description': 'Alias laborum ducimus earum at libero.' - }, - { - 'userId': 77, - 'firstName': 'Garland', - 'lastName': 'Shields', - 'email': 'Quentin_Daniel@hotmail.com', - 'birthday': new Date('1997-10-22T22:55:40.264Z'), - 'description': 'Repellendus hic perferendis tempora nisi voluptates beatae.' - }, - { - 'userId': 78, - 'firstName': 'Elwin', - 'lastName': 'Skiles', - 'email': 'Laura_Klein64@gmail.com', - 'birthday': new Date('1995-06-12T15:05:30.117Z'), - 'description': 'Provident libero praesentium quia harum exercitationem perferendis sunt autem earum.' - }, - { - 'userId': 79, - 'firstName': 'Dewitt', - 'lastName': 'Zulauf', - 'email': 'Orpha.Schinner74@gmail.com', - 'birthday': new Date('1951-08-25T02:09:43.216Z'), - 'description': 'Ut deleniti eum laborum.' - }, - { - 'userId': 80, - 'firstName': 'Moses', - 'lastName': 'Hoppe', - 'email': 'Marilie_Simonis92@gmail.com', - 'birthday': new Date('1957-01-07T08:56:35.297Z'), - 'description': 'Distinctio mollitia rem voluptatum itaque amet ad non natus rerum.' - }, - { - 'userId': 81, - 'firstName': 'Jeffrey', - 'lastName': 'Mertz', - 'email': 'Georgianna_Miller9@gmail.com', - 'birthday': new Date('1985-04-28T09:53:07.998Z'), - 'description': 'Aliquid autem facere odio soluta dolores eos dolore.' - }, - { - 'userId': 82, - 'firstName': 'Daniella', - 'lastName': 'Halvorson', - 'email': 'Arvilla23@hotmail.com', - 'birthday': new Date('1969-08-01T14:03:32.619Z'), - 'description': 'Optio accusamus eius.' - }, - { - 'userId': 83, - 'firstName': 'Kavon', - 'lastName': 'Boyle', - 'email': 'Monserrat.Hagenes58@hotmail.com', - 'birthday': new Date('1946-04-12T04:40:02.776Z'), - 'description': 'Necessitatibus earum tempore fugiat rerum placeat vitae deserunt facere explicabo.' - }, - { - 'userId': 84, - 'firstName': 'Lydia', - 'lastName': 'Hills', - 'email': 'Casper_Dicki51@hotmail.com', - 'birthday': new Date('1980-06-14T01:02:49.737Z'), - 'description': 'Tempora ipsum sequi natus accusamus expedita ullam dolores incidunt.' - }, - { - 'userId': 85, - 'firstName': 'Jess', - 'lastName': 'Hagenes', - 'email': 'Johnnie.Maggio26@gmail.com', - 'birthday': new Date('2000-09-14T13:50:57.124Z'), - 'description': 'Quidem odit doloremque blanditiis aspernatur quod temporibus cum.' - }, - { - 'userId': 86, - 'firstName': 'Paige', - 'lastName': 'Gutkowski', - 'email': 'Donna52@hotmail.com', - 'birthday': new Date('1973-04-12T00:46:29.258Z'), - 'description': 'Reiciendis veritatis sed magnam vitae est deserunt.' - }, - { - 'userId': 87, - 'firstName': 'Quinton', - 'lastName': 'Skiles', - 'email': 'Colt49@hotmail.com', - 'birthday': new Date('1981-11-22T20:06:11.861Z'), - 'description': 'Dolore pariatur minus tenetur aliquid quisquam.' - }, - { - 'userId': 88, - 'firstName': 'Percival', - 'lastName': 'Shanahan', - 'email': 'Kelsi94@hotmail.com', - 'birthday': new Date('1949-10-20T10:52:01.298Z'), - 'description': 'Laborum magni laborum libero nesciunt.' - }, - { - 'userId': 89, - 'firstName': 'Jean', - 'lastName': 'Lynch', - 'email': 'Krystal.Lakin@hotmail.com', - 'birthday': new Date('1951-08-31T13:47:41.310Z'), - 'description': 'Optio esse dolore cupiditate tempore vel ad tenetur.' - }, - { - 'userId': 90, - 'firstName': 'Zachariah', - 'lastName': 'Goldner', - 'email': 'Blaise_Dietrich20@gmail.com', - 'birthday': new Date('1985-08-02T10:42:40.939Z'), - 'description': 'Totam vero earum vel earum.' - }, - { - 'userId': 91, - 'firstName': 'Pansy', - 'lastName': 'Roberts', - 'email': 'Francesco.Feeney@hotmail.com', - 'birthday': new Date('1955-04-06T18:59:43.073Z'), - 'description': 'Harum minima animi quia nemo.' - }, - { - 'userId': 92, - 'firstName': 'Marshall', - 'lastName': 'Hane', - 'email': 'Joe_Greenholt51@yahoo.com', - 'birthday': new Date('1997-07-18T07:53:21.993Z'), - 'description': 'Fuga velit laudantium quo velit eveniet harum cumque nobis porro.' - }, - { - 'userId': 93, - 'firstName': 'Autumn', - 'lastName': 'Weimann', - 'email': 'Shanelle.Crooks46@hotmail.com', - 'birthday': new Date('1992-06-16T03:41:27.018Z'), - 'description': 'Quidem maiores voluptatum vel quidem temporibus eaque facilis esse.' - }, - { - 'userId': 94, - 'firstName': 'Danny', - 'lastName': 'Beahan', - 'email': 'Tremayne.Stiedemann28@yahoo.com', - 'birthday': new Date('1990-04-17T02:41:37.715Z'), - 'description': 'Aperiam quas non perspiciatis doloribus saepe placeat.' - }, - { - 'userId': 95, - 'firstName': 'Bulah', - 'lastName': 'Labadie', - 'email': 'Lorena43@hotmail.com', - 'birthday': new Date('1997-03-30T12:51:44.571Z'), - 'description': 'Tempora odit ab cupiditate ab dignissimos.' - }, - { - 'userId': 96, - 'firstName': 'Tremaine', - 'lastName': 'Fahey', - 'email': 'Rickie_Paucek31@yahoo.com', - 'birthday': new Date('1958-09-02T02:12:49.382Z'), - 'description': 'Enim corrupti voluptates nihil eveniet reprehenderit odit.' - }, - { - 'userId': 97, - 'firstName': 'Jany', - 'lastName': 'Kiehn', - 'email': 'Carmel41@yahoo.com', - 'birthday': new Date('1945-05-08T20:59:27.987Z'), - 'description': 'Voluptatibus unde iste quam nisi adipisci excepturi nostrum ratione saepe.' - }, - { - 'userId': 98, - 'firstName': 'Elyssa', - 'lastName': 'Gerhold', - 'email': 'Vivienne.Funk@yahoo.com', - 'birthday': new Date('1959-10-29T11:42:42.446Z'), - 'description': 'Praesentium quidem quibusdam consequuntur ipsam voluptates.' - }, - { - 'userId': 99, - 'firstName': 'Raina', - 'lastName': 'Champlin', - 'email': 'Annalise.Champlin74@hotmail.com', - 'birthday': new Date('1988-10-25T22:20:34.228Z'), - 'description': 'Pariatur at natus consequuntur ullam laboriosam aspernatur dolore.' - }, - { - 'userId': 100, - 'firstName': 'Declan', - 'lastName': 'Dickens', - 'email': 'Kaitlyn67@gmail.com', - 'birthday': new Date('1964-02-28T23:40:06.318Z'), - 'description': 'Iusto dolorum illum reprehenderit quasi facilis earum at.' + userId: 1, + firstName: 'Rashawn', + lastName: 'Goyette', + email: 'Alexa1@gmail.com', + birthday: new Date('2000-11-21T06:55:38.655Z'), + description: 'Provident numquam possimus harum maxime repellat repellat optio quas.', + remoteAccess: false + }, + { + userId: 103, + firstName: 'Rashawn', + lastName: 'Irvin', + email: 'IrvinR@gmail.com', + birthday: new Date('2000-11-21T16:55:38.655Z'), + description: 'Provident numquam possimus harum maxime repellat repellat optio quas.', + remoteAccess: true + }, + { + userId: 2, + firstName: 'Jesse', + lastName: 'Littel', + email: 'Lura_Predovic@hotmail.com', + birthday: new Date('1980-07-18T09:14:13.937Z'), + description: 'Odit quibusdam tempora sapiente.', + remoteAccess: true + }, + { + userId: 3, + firstName: 'Mazie', + lastName: 'Bradtke', + email: 'Concepcion25@yahoo.com', + birthday: new Date('1996-12-25T12:38:46.992Z'), + description: 'Ipsam temporibus exercitationem ipsum architecto.', + remoteAccess: false + }, + { + userId: 4, + firstName: 'Gabrielle', + lastName: 'Predovic', + email: 'Emory.Schamberger@yahoo.com', + birthday: new Date('1975-01-08T09:45:13.052Z'), + description: 'Excepturi hic sint velit molestias enim aliquid voluptas cumque consequatur.', + remoteAccess: false + }, + { + userId: 5, + firstName: 'Lily', + lastName: 'Rolfson', + email: 'Webster.Quigley67@yahoo.com', + birthday: new Date('1948-07-04T02:09:40.601Z'), + description: 'Nihil aliquam qui libero error modi pariatur quis voluptate possimus.', + remoteAccess: false + }, + { + userId: 6, + firstName: 'Adah', + lastName: 'Hauck', + email: 'Vincenzo_Runte14@yahoo.com', + birthday: new Date('1966-02-11T06:49:26.101Z'), + description: 'Vero autem blanditiis omnis.', + remoteAccess: undefined + }, + { + userId: 7, + firstName: 'Jessie', + lastName: 'Kuvalis', + email: 'Madelyn5@gmail.com', + birthday: new Date('1976-03-15T21:41:16.606Z'), + description: 'Veritatis beatae maxime molestias accusantium beatae.' + }, + { + userId: 8, + firstName: 'Alene', + lastName: "D'Amore", + email: 'Lura.Stokes@yahoo.com', + birthday: new Date('1992-10-15T00:54:04.161Z'), + description: 'Saepe quo doloribus minus quia error.', + remoteAccess: true + }, + { + userId: 9, + firstName: 'Lea', + lastName: 'Keeling', + email: 'Dallin_OConner@gmail.com', + birthday: new Date('1980-09-09T12:17:11.494Z'), + description: 'Similique veniam debitis voluptate laudantium doloribus.', + remoteAccess: undefined + }, + { + userId: 10, + firstName: 'Orrin', + lastName: 'Orn', + email: 'Dan_Torp23@yahoo.com', + birthday: new Date('1953-01-25T02:11:42.523Z'), + description: 'Totam inventore placeat magnam doloremque.', + remoteAccess: false + }, + { + userId: 11, + firstName: 'Cleveland', + lastName: 'Koelpin', + email: 'Barrett69@hotmail.com', + birthday: new Date('1995-02-21T19:45:18.994Z'), + description: 'Facilis eligendi sed animi iusto repellendus quas ab quos facilis.', + remoteAccess: undefined + }, + { + userId: 12, + firstName: 'Carol', + lastName: 'Braun', + email: 'Krystal_Upton@gmail.com', + birthday: new Date('1962-05-04T00:45:00.078Z'), + description: + 'Accusamus vero repellat quae modi officiis molestias harum dignissimos distinctio.', + remoteAccess: false + }, + { + userId: 13, + firstName: 'Dena', + lastName: 'Goodwin', + email: 'Rico34@yahoo.com', + birthday: new Date('1979-11-03T11:50:10.263Z'), + description: 'Necessitatibus eum dolores alias maiores est.', + remoteAccess: true + }, + { + userId: 14, + firstName: 'Shad', + lastName: 'Kerluke', + email: 'Julien.Spencer37@hotmail.com', + birthday: new Date('1990-09-15T13:32:19.544Z'), + description: 'Numquam accusantium reiciendis cumque unde.', + remoteAccess: false + }, + { + userId: 15, + firstName: 'Tina', + lastName: 'Lowe', + email: 'Miller_Kling@hotmail.com', + birthday: new Date('1968-11-25T17:32:53.658Z'), + description: 'Eum sit excepturi nesciunt doloribus doloribus neque tempora eum molestias.', + remoteAccess: undefined + }, + { + userId: 16, + firstName: 'Novella', + lastName: 'Schmidt', + email: 'Josiane78@yahoo.com', + birthday: new Date('1983-03-06T20:36:10.796Z'), + description: 'Quod maiores vel.', + remoteAccess: true + }, + { + userId: 17, + firstName: 'Reid', + lastName: 'Rolfson', + email: 'Berry_Sanford72@hotmail.com', + birthday: new Date('1953-03-17T01:23:24.335Z'), + description: 'Aut velit autem necessitatibus eaque.', + remoteAccess: false + }, + { + userId: 18, + firstName: 'Cynthia', + lastName: 'Reichert', + email: 'Earnestine_Leannon98@gmail.com', + birthday: new Date('1971-11-26T00:38:55.102Z'), + description: 'Aut totam cupiditate illum.', + remoteAccess: false + }, + { + userId: 19, + firstName: 'Jamar', + lastName: 'Hauck', + email: 'Mariela_Batz93@gmail.com', + birthday: new Date('1969-09-22T06:02:27.247Z'), + description: 'Perferendis quam officiis.' + }, + { + userId: 20, + firstName: 'Ada', + lastName: 'Ernser', + email: 'Rosendo_Breitenberg47@hotmail.com', + birthday: new Date('1984-02-27T07:45:35.737Z'), + description: 'Facilis debitis animi assumenda eveniet error.', + remoteAccess: undefined + }, + { + userId: 21, + firstName: 'Tabitha', + lastName: 'Wiza', + email: 'Chasity.Jast@gmail.com', + birthday: new Date('1958-09-21T04:57:27.912Z'), + description: 'Doloribus maxime culpa aliquam rem excepturi officiis aliquid ratione alias.', + remoteAccess: true + }, + { + userId: 22, + firstName: 'Frida', + lastName: 'Hermann', + email: 'Britney45@yahoo.com', + birthday: new Date('1999-04-20T18:18:30.593Z'), + description: 'Quasi esse ipsam totam fugit mollitia vero eum.', + remoteAccess: true + }, + { + userId: 23, + firstName: 'Tavares', + lastName: 'Gottlieb', + email: 'Nils84@gmail.com', + birthday: new Date('1961-11-18T12:45:58.248Z'), + description: 'Illo consequatur eligendi exercitationem qui aspernatur sequi repudiandae.', + remoteAccess: false + }, + { + userId: 24, + firstName: 'Otha', + lastName: 'Witting', + email: 'Watson.Smith80@gmail.com', + birthday: new Date('1946-07-01T13:44:28.632Z'), + description: 'At exercitationem tenetur accusamus nemo ad et.', + remoteAccess: true + }, + { + userId: 25, + firstName: 'Ted', + lastName: 'Quigley', + email: 'Brenden.Hilll@hotmail.com', + birthday: new Date('1943-09-27T10:12:58.472Z'), + description: 'Atque modi illo.', + remoteAccess: undefined + }, + { + userId: 26, + firstName: 'Curt', + lastName: 'Welch', + email: 'Ruthie.Blick39@hotmail.com', + birthday: new Date('1971-05-10T11:35:57.099Z'), + description: 'Ad modi ad aut itaque nisi.', + remoteAccess: false + }, + { + userId: 27, + firstName: 'Madeline', + lastName: 'Kassulke', + email: 'Palma_Halvorson10@yahoo.com', + birthday: new Date('1994-11-06T10:27:13.921Z'), + description: 'Voluptatibus repudiandae vero quasi quaerat quisquam officiis.', + remoteAccess: false + }, + { + userId: 28, + firstName: 'Aletha', + lastName: 'Dooley', + email: 'Lilly51@yahoo.com', + birthday: new Date('1998-03-16T07:43:19.509Z'), + description: 'Cumque quis itaque debitis labore quaerat reprehenderit dolorem.', + remoteAccess: false + }, + { + userId: 29, + firstName: 'Lila', + lastName: 'Dickinson', + email: 'Frieda.Konopelski95@hotmail.com', + birthday: new Date('1963-10-11T08:48:51.425Z'), + description: 'Fugit perspiciatis dolorem inventore ad voluptas libero.' + }, + { + userId: 30, + firstName: 'Bessie', + lastName: 'Larson', + email: 'Janet72@yahoo.com', + birthday: new Date('1972-10-28T00:51:58.473Z'), + description: 'Consequatur iusto quas eum labore.', + remoteAccess: true + }, + { + userId: 31, + firstName: 'Mac', + lastName: 'Hickle', + email: 'Nia_Johnston85@hotmail.com', + birthday: new Date('1956-06-03T16:11:53.374Z'), + description: 'Doloremque illo sint modi minus cum.', + remoteAccess: true + }, + { + userId: 32, + firstName: 'Nya', + lastName: 'Bahringer', + email: 'Yasmeen_Daugherty63@gmail.com', + birthday: new Date('1993-04-01T19:03:06.411Z'), + description: 'Earum odio odit dolores sapiente illo cumque fugiat ad.', + remoteAccess: true + }, + { + userId: 33, + firstName: 'Dakota', + lastName: 'Keebler', + email: 'Lesley.Sauer19@yahoo.com', + birthday: new Date('1969-11-07T06:40:21.732Z'), + description: 'Voluptas incidunt tempora architecto quae a blanditiis sapiente vel.', + remoteAccess: undefined + }, + { + userId: 34, + firstName: 'Murphy', + lastName: 'Roberts', + email: 'Dan.Effertz@hotmail.com', + birthday: new Date('2000-01-18T08:02:53.660Z'), + description: 'Magni alias praesentium quae quibusdam qui.' + }, + { + userId: 35, + firstName: 'Johnpaul', + lastName: "O'Conner", + email: 'Bernardo15@gmail.com', + birthday: new Date('1988-05-15T21:23:16.217Z'), + description: 'Deleniti quos laudantium enim reiciendis doloremque fugiat culpa.', + remoteAccess: true + }, + { + userId: 36, + firstName: 'Meggie', + lastName: 'Williamson', + email: 'Kassandra_Stanton@yahoo.com', + birthday: new Date('1944-02-22T16:22:41.901Z'), + description: 'Ullam repellendus nisi sit.', + remoteAccess: true + }, + { + userId: 37, + firstName: 'Deborah', + lastName: 'Abbott', + email: 'Deborah.Abbott@gmail.com', + birthday: new Date('2003-06-18T22:38:30.281Z'), + description: 'Modi ullam assumenda magni quisquam consequuntur quis perferendis culpa.', + remoteAccess: undefined + }, + { + userId: 101, + firstName: 'Anna', + lastName: 'Abbott', + email: 'Anna.Abbott1@gmail.com', + birthday: new Date('2013-04-23T22:38:30.281Z'), + description: 'Modi ullam assumenda magni quisquam consequuntur quis perferendis culpa.', + remoteAccess: false + }, + { + userId: 102, + firstName: 'Anna', + lastName: 'Abbott', + email: 'Anna.Abbott2@gmail.com', + birthday: new Date('2002-11-02T22:38:30.281Z'), + description: 'Modi ullam assumenda magni quisquam consequuntur quis perferendis culpa.', + remoteAccess: false + }, + { + userId: 38, + firstName: 'Aaliyah', + lastName: 'Sporer', + email: 'Floyd59@yahoo.com', + birthday: new Date('1958-06-27T08:58:58.561Z'), + description: 'Exercitationem nemo minima aut soluta suscipit voluptatem voluptatum vitae.', + remoteAccess: false + }, + { + userId: 39, + firstName: 'Emerson', + lastName: "O'Keefe", + email: 'Evan.Turner@hotmail.com', + birthday: new Date('1988-12-30T13:07:12.997Z'), + description: 'Corporis laudantium dolor error voluptate laborum ut.', + remoteAccess: true + }, + { + userId: 40, + firstName: 'Gregory', + lastName: 'Crist', + email: 'Letitia.Barton81@gmail.com', + birthday: new Date('1981-02-14T19:00:47.359Z'), + description: 'Veritatis assumenda mollitia libero.', + remoteAccess: true + }, + { + userId: 41, + firstName: 'Macie', + lastName: 'Howell', + email: 'Zachary37@hotmail.com', + birthday: new Date('1979-01-22T11:50:56.998Z'), + description: 'Voluptatum laborum ab aut nam.' + }, + { + userId: 42, + firstName: 'Garry', + lastName: 'Miller', + email: 'Wallace78@yahoo.com', + birthday: new Date('1967-12-24T10:06:07.922Z'), + description: 'Ducimus sapiente iusto maxime voluptas amet laboriosam optio veniam ea.', + remoteAccess: false + }, + { + userId: 43, + firstName: 'Kristian', + lastName: 'Mills', + email: 'Shana_Hane@yahoo.com', + birthday: new Date('1998-05-25T19:37:35.197Z'), + description: 'Eos laudantium blanditiis in.', + remoteAccess: false + }, + { + userId: 44, + firstName: 'Annamarie', + lastName: 'Lowe', + email: 'Reyes38@hotmail.com', + birthday: new Date('1957-01-17T22:16:36.274Z'), + description: 'Consequuntur quaerat at nihil.', + remoteAccess: undefined + }, + { + userId: 45, + firstName: 'Nicola', + lastName: 'Lakin', + email: 'Olin_Cruickshank67@gmail.com', + birthday: new Date('1950-01-21T11:34:29.968Z'), + description: 'Non corporis quidem iusto asperiores corporis officiis assumenda incidunt.', + remoteAccess: false + }, + { + userId: 46, + firstName: 'Kennedi', + lastName: 'Renner', + email: 'Johnpaul.Metz3@yahoo.com', + birthday: new Date('1997-06-10T13:13:35.766Z'), + description: 'Asperiores tempora eveniet quaerat voluptatum rem iste.' + }, + { + userId: 47, + firstName: 'Wallace', + lastName: 'Hegmann', + email: 'Beau_Maggio7@yahoo.com', + birthday: new Date('1975-11-02T01:52:46.711Z'), + description: 'Doloremque odit praesentium temporibus.', + remoteAccess: false + }, + { + userId: 48, + firstName: 'Kaia', + lastName: 'Frami', + email: 'Kaci29@gmail.com', + birthday: new Date('1953-11-25T07:41:59.208Z'), + description: 'Eligendi consequuntur tempore commodi nostrum illo quibusdam.', + remoteAccess: false + }, + { + userId: 49, + firstName: 'Vallie', + lastName: 'Kilback', + email: 'Ben_OConner51@yahoo.com', + birthday: new Date('1990-12-08T14:00:37.514Z'), + description: 'Ducimus necessitatibus dolore dolores.', + remoteAccess: false + }, + { + userId: 50, + firstName: 'Giovanna', + lastName: 'Ernser', + email: 'Micaela_Morissette@gmail.com', + birthday: new Date('1959-04-24T17:42:14.319Z'), + description: 'Doloremque minus temporibus blanditiis architecto occaecati alias quis.', + remoteAccess: false + }, + { + userId: 51, + firstName: 'Kian', + lastName: 'Hirthe', + email: 'Gunnar50@hotmail.com', + birthday: new Date('1965-09-28T11:51:48.083Z'), + description: 'Consectetur beatae error necessitatibus officia accusamus.', + remoteAccess: false + }, + { + userId: 52, + firstName: 'Lenny', + lastName: "O'Kon", + email: 'Koby_Wintheiser0@gmail.com', + birthday: new Date('1965-03-07T02:57:47.692Z'), + description: 'Fuga tenetur cumque vel.', + remoteAccess: undefined + }, + { + userId: 53, + firstName: 'Vergie', + lastName: 'Jerde', + email: 'Terence.Denesik18@yahoo.com', + birthday: new Date('1994-11-16T15:03:26.539Z'), + description: 'Inventore doloribus facere earum perferendis amet quibusdam facere expedita ex.' + }, + { + userId: 54, + firstName: 'Myrl', + lastName: 'Mills', + email: 'Melisa.Hirthe@yahoo.com', + birthday: new Date('1944-11-27T21:33:20.408Z'), + description: 'Nulla fugit illum dolorem incidunt.', + remoteAccess: undefined + }, + { + userId: 55, + firstName: 'Leanne', + lastName: 'Hills', + email: 'Darwin_Renner@yahoo.com', + birthday: new Date('1984-03-10T20:01:43.662Z'), + description: 'Optio magnam accusantium eligendi.', + remoteAccess: false + }, + { + userId: 56, + firstName: 'Stephen', + lastName: 'Stanton', + email: 'Brycen_Reichel@hotmail.com', + birthday: new Date('1987-02-22T17:04:31.075Z'), + description: 'Eum adipisci tempore temporibus ducimus quod amet.', + remoteAccess: undefined + }, + { + userId: 57, + firstName: 'Hettie', + lastName: 'Kreiger', + email: 'Jamaal.Mertz5@yahoo.com', + birthday: new Date('1983-05-13T22:29:06.357Z'), + description: 'Ab dolor magnam esse.', + remoteAccess: true + }, + { + userId: 58, + firstName: 'Iliana', + lastName: 'Jacobi', + email: 'Jarod47@gmail.com', + birthday: new Date('2005-12-07T06:58:16.179Z'), + description: 'Labore aliquid ducimus repellat non facilis vitae.', + remoteAccess: false + }, + { + userId: 59, + firstName: 'Lucile', + lastName: 'Swaniawski', + email: 'Marguerite_Goyette@yahoo.com', + birthday: new Date('1943-09-02T23:28:12.608Z'), + description: 'Minus praesentium nemo explicabo reiciendis incidunt.', + remoteAccess: undefined + }, + { + userId: 60, + firstName: 'Paige', + lastName: 'Frami', + email: 'Lucas_Spencer@yahoo.com', + birthday: new Date('1990-12-05T11:05:27.917Z'), + description: 'Ipsa explicabo doloremque odio at tenetur.' + }, + { + userId: 61, + firstName: 'Sylvia', + lastName: "O'Kon", + email: 'Brown.Zieme94@gmail.com', + birthday: new Date('1960-03-06T04:10:03.122Z'), + description: 'Laboriosam quaerat quas corrupti necessitatibus quam officia est optio.', + remoteAccess: false + }, + { + userId: 62, + firstName: 'Johnathan', + lastName: 'Lueilwitz', + email: 'Ruben.Anderson87@gmail.com', + birthday: new Date('1994-12-30T01:07:07.400Z'), + description: 'Aliquid magnam assumenda vel rem tempore suscipit voluptas tempore ducimus.', + remoteAccess: undefined + }, + { + userId: 63, + firstName: 'Ian', + lastName: "D'Amore", + email: 'Josie_Konopelski31@hotmail.com', + birthday: new Date('1945-05-06T01:22:27.815Z'), + description: 'Quos doloremque quo provident velit rem.', + remoteAccess: true + }, + { + userId: 64, + firstName: 'Pat', + lastName: 'Simonis', + email: 'Dax.Marks33@hotmail.com', + birthday: new Date('1982-01-30T16:32:55.326Z'), + description: 'Beatae dolore laborum aliquid tempore doloremque repellat facere.', + remoteAccess: true + }, + { + userId: 65, + firstName: 'Mathias', + lastName: 'Friesen', + email: 'Davion12@yahoo.com', + birthday: new Date('1944-11-21T18:43:37.270Z'), + description: 'Neque voluptas inventore alias enim numquam totam doloremque nobis.', + remoteAccess: undefined + }, + { + userId: 66, + firstName: 'Cydney', + lastName: 'Kirlin', + email: 'Kaycee.Bradtke32@gmail.com', + birthday: new Date('1977-08-21T08:31:22.456Z'), + description: 'Ex quod quae eveniet quos placeat temporibus.', + remoteAccess: false + }, + { + userId: 67, + firstName: 'Prince', + lastName: 'Schumm', + email: 'Vernon_Waters@gmail.com', + birthday: new Date('1946-04-20T07:01:53.871Z'), + description: 'Harum quis ipsum magnam facilis.' + }, + { + userId: 68, + firstName: 'Shirley', + lastName: 'Ferry', + email: 'Alec.Kunze40@hotmail.com', + birthday: new Date('1977-03-27T13:19:52.048Z'), + description: 'Modi dolorum ea excepturi non nihil a culpa pariatur.', + remoteAccess: false + }, + { + userId: 69, + firstName: 'Coty', + lastName: 'Erdman', + email: 'Virgie_Dickinson@gmail.com', + birthday: new Date('2003-06-24T15:50:42.290Z'), + description: 'Illo non dolorum quaerat dolorem asperiores neque corrupti ex.', + remoteAccess: undefined + }, + { + userId: 70, + firstName: 'Anita', + lastName: 'Strosin', + email: 'Makenzie_Mayer36@gmail.com', + birthday: new Date('2000-08-02T14:41:41.000Z'), + description: 'Dicta natus autem ipsum reprehenderit.', + remoteAccess: true + }, + { + userId: 71, + firstName: 'Clementine', + lastName: 'Luettgen', + email: 'Darby90@yahoo.com', + birthday: new Date('1965-05-24T03:33:42.337Z'), + description: 'Praesentium fugit sint nostrum qui iusto voluptates voluptates.', + remoteAccess: false + }, + { + userId: 72, + firstName: 'Haven', + lastName: 'Mante', + email: 'Marques40@hotmail.com', + birthday: new Date('1950-10-02T15:35:37.099Z'), + description: 'Illo necessitatibus inventore libero qui cumque qui recusandae.', + remoteAccess: undefined + }, + { + userId: 73, + firstName: 'Minnie', + lastName: 'Kulas', + email: 'Lorenz_Beier@yahoo.com', + birthday: new Date('1962-09-01T10:21:54.891Z'), + description: 'At consequatur maxime reiciendis pariatur.' + }, + { + userId: 74, + firstName: 'Alf', + lastName: 'Wuckert', + email: 'Stanton4@yahoo.com', + birthday: new Date('1987-12-26T07:45:14.517Z'), + description: 'Quidem natus eveniet at eum impedit neque natus deserunt.' + }, + { + userId: 75, + firstName: 'Brandy', + lastName: 'Rowe', + email: 'Agustin.Brakus@yahoo.com', + birthday: new Date('1981-05-10T03:30:19.265Z'), + description: 'Aliquid possimus laborum doloremque modi non maxime.', + remoteAccess: undefined + }, + { + userId: 76, + firstName: 'Margaretta', + lastName: 'Torp', + email: 'Antonetta36@hotmail.com', + birthday: new Date('1952-11-19T22:50:36.420Z'), + description: 'Alias laborum ducimus earum at libero.', + remoteAccess: true + }, + { + userId: 77, + firstName: 'Garland', + lastName: 'Shields', + email: 'Quentin_Daniel@hotmail.com', + birthday: new Date('1997-10-22T22:55:40.264Z'), + description: 'Repellendus hic perferendis tempora nisi voluptates beatae.', + remoteAccess: false + }, + { + userId: 78, + firstName: 'Elwin', + lastName: 'Skiles', + email: 'Laura_Klein64@gmail.com', + birthday: new Date('1995-06-12T15:05:30.117Z'), + description: + 'Provident libero praesentium quia harum exercitationem perferendis sunt autem earum.', + remoteAccess: undefined + }, + { + userId: 79, + firstName: 'Dewitt', + lastName: 'Zulauf', + email: 'Orpha.Schinner74@gmail.com', + birthday: new Date('1951-08-25T02:09:43.216Z'), + description: 'Ut deleniti eum laborum.', + remoteAccess: true + }, + { + userId: 80, + firstName: 'Moses', + lastName: 'Hoppe', + email: 'Marilie_Simonis92@gmail.com', + birthday: new Date('1957-01-07T08:56:35.297Z'), + description: 'Distinctio mollitia rem voluptatum itaque amet ad non natus rerum.', + remoteAccess: false + }, + { + userId: 81, + firstName: 'Jeffrey', + lastName: 'Mertz', + email: 'Georgianna_Miller9@gmail.com', + birthday: new Date('1985-04-28T09:53:07.998Z'), + description: 'Aliquid autem facere odio soluta dolores eos dolore.', + remoteAccess: undefined + }, + { + userId: 82, + firstName: 'Daniella', + lastName: 'Halvorson', + email: 'Arvilla23@hotmail.com', + birthday: new Date('1969-08-01T14:03:32.619Z'), + description: 'Optio accusamus eius.', + remoteAccess: true + }, + { + userId: 83, + firstName: 'Kavon', + lastName: 'Boyle', + email: 'Monserrat.Hagenes58@hotmail.com', + birthday: new Date('1946-04-12T04:40:02.776Z'), + description: + 'Necessitatibus earum tempore fugiat rerum placeat vitae deserunt facere explicabo.' + }, + { + userId: 84, + firstName: 'Lydia', + lastName: 'Hills', + email: 'Casper_Dicki51@hotmail.com', + birthday: new Date('1980-06-14T01:02:49.737Z'), + description: 'Tempora ipsum sequi natus accusamus expedita ullam dolores incidunt.', + remoteAccess: true + }, + { + userId: 85, + firstName: 'Jess', + lastName: 'Hagenes', + email: 'Johnnie.Maggio26@gmail.com', + birthday: new Date('2000-09-14T13:50:57.124Z'), + description: 'Quidem odit doloremque blanditiis aspernatur quod temporibus cum.', + remoteAccess: undefined + }, + { + userId: 86, + firstName: 'Paige', + lastName: 'Gutkowski', + email: 'Donna52@hotmail.com', + birthday: new Date('1973-04-12T00:46:29.258Z'), + description: 'Reiciendis veritatis sed magnam vitae est deserunt.', + remoteAccess: true + }, + { + userId: 87, + firstName: 'Quinton', + lastName: 'Skiles', + email: 'Colt49@hotmail.com', + birthday: new Date('1981-11-22T20:06:11.861Z'), + description: 'Dolore pariatur minus tenetur aliquid quisquam.' + }, + { + userId: 88, + firstName: 'Percival', + lastName: 'Shanahan', + email: 'Kelsi94@hotmail.com', + birthday: new Date('1949-10-20T10:52:01.298Z'), + description: 'Laborum magni laborum libero nesciunt.', + remoteAccess: false + }, + { + userId: 89, + firstName: 'Jean', + lastName: 'Lynch', + email: 'Krystal.Lakin@hotmail.com', + birthday: new Date('1951-08-31T13:47:41.310Z'), + description: 'Optio esse dolore cupiditate tempore vel ad tenetur.', + remoteAccess: true + }, + { + userId: 90, + firstName: 'Zachariah', + lastName: 'Goldner', + email: 'Blaise_Dietrich20@gmail.com', + birthday: new Date('1985-08-02T10:42:40.939Z'), + description: 'Totam vero earum vel earum.', + remoteAccess: true + }, + { + userId: 91, + firstName: 'Pansy', + lastName: 'Roberts', + email: 'Francesco.Feeney@hotmail.com', + birthday: new Date('1955-04-06T18:59:43.073Z'), + description: 'Harum minima animi quia nemo.', + remoteAccess: undefined + }, + { + userId: 92, + firstName: 'Marshall', + lastName: 'Hane', + email: 'Joe_Greenholt51@yahoo.com', + birthday: new Date('1997-07-18T07:53:21.993Z'), + description: 'Fuga velit laudantium quo velit eveniet harum cumque nobis porro.', + remoteAccess: false + }, + { + userId: 93, + firstName: 'Autumn', + lastName: 'Weimann', + email: 'Shanelle.Crooks46@hotmail.com', + birthday: new Date('1992-06-16T03:41:27.018Z'), + description: 'Quidem maiores voluptatum vel quidem temporibus eaque facilis esse.', + remoteAccess: false + }, + { + userId: 94, + firstName: 'Danny', + lastName: 'Beahan', + email: 'Tremayne.Stiedemann28@yahoo.com', + birthday: new Date('1990-04-17T02:41:37.715Z'), + description: 'Aperiam quas non perspiciatis doloribus saepe placeat.', + remoteAccess: false + }, + { + userId: 95, + firstName: 'Bulah', + lastName: 'Labadie', + email: 'Lorena43@hotmail.com', + birthday: new Date('1997-03-30T12:51:44.571Z'), + description: 'Tempora odit ab cupiditate ab dignissimos.', + remoteAccess: false + }, + { + userId: 96, + firstName: 'Tremaine', + lastName: 'Fahey', + email: 'Rickie_Paucek31@yahoo.com', + birthday: new Date('1958-09-02T02:12:49.382Z'), + description: 'Enim corrupti voluptates nihil eveniet reprehenderit odit.' + }, + { + userId: 97, + firstName: 'Jany', + lastName: 'Kiehn', + email: 'Carmel41@yahoo.com', + birthday: new Date('1945-05-08T20:59:27.987Z'), + description: 'Voluptatibus unde iste quam nisi adipisci excepturi nostrum ratione saepe.', + remoteAccess: undefined + }, + { + userId: 98, + firstName: 'Elyssa', + lastName: 'Gerhold', + email: 'Vivienne.Funk@yahoo.com', + birthday: new Date('1959-10-29T11:42:42.446Z'), + description: 'Praesentium quidem quibusdam consequuntur ipsam voluptates.', + remoteAccess: false + }, + { + userId: 99, + firstName: 'Raina', + lastName: 'Champlin', + email: 'Annalise.Champlin74@hotmail.com', + birthday: new Date('1988-10-25T22:20:34.228Z'), + description: 'Pariatur at natus consequuntur ullam laboriosam aspernatur dolore.', + remoteAccess: false + }, + { + userId: 100, + firstName: 'Declan', + lastName: 'Dickens', + email: 'Kaitlyn67@gmail.com', + birthday: new Date('1964-02-28T23:40:06.318Z'), + description: 'Iusto dolorum illum reprehenderit quasi facilis earum at.', + remoteAccess: false } ]; diff --git a/projects/mat-datatable-demo/src/app/shared/demo-table-item.interface.ts b/projects/mat-datatable-demo/src/app/shared/demo-table-item.interface.ts index e5ffdf7..1cbb64a 100644 --- a/projects/mat-datatable-demo/src/app/shared/demo-table-item.interface.ts +++ b/projects/mat-datatable-demo/src/app/shared/demo-table-item.interface.ts @@ -8,4 +8,5 @@ export interface DemoTableItem { email: string; birthday: Date; description: string; + remoteAccess?: boolean; } diff --git a/projects/mat-datatable-demo/src/assets/images/circle-gray.png b/projects/mat-datatable-demo/src/assets/images/circle-gray.png new file mode 100644 index 0000000..fb024e1 Binary files /dev/null and b/projects/mat-datatable-demo/src/assets/images/circle-gray.png differ diff --git a/projects/mat-datatable-demo/src/assets/images/circle-green.png b/projects/mat-datatable-demo/src/assets/images/circle-green.png new file mode 100644 index 0000000..57af6ae Binary files /dev/null and b/projects/mat-datatable-demo/src/assets/images/circle-green.png differ diff --git a/projects/mat-datatable-demo/src/assets/images/circle-red.png b/projects/mat-datatable-demo/src/assets/images/circle-red.png new file mode 100644 index 0000000..fd4ab4c Binary files /dev/null and b/projects/mat-datatable-demo/src/assets/images/circle-red.png differ diff --git a/projects/mat-datatable-demo/src/index.html b/projects/mat-datatable-demo/src/index.html index 33d2029..b02bcde 100644 --- a/projects/mat-datatable-demo/src/index.html +++ b/projects/mat-datatable-demo/src/index.html @@ -1,12 +1,12 @@ - - - mat-datatable Demo - - - - - - + + + mat-datatable Demo + + + + + + diff --git a/projects/mat-datatable-demo/src/main.ts b/projects/mat-datatable-demo/src/main.ts index 5add906..f27ed89 100644 --- a/projects/mat-datatable-demo/src/main.ts +++ b/projects/mat-datatable-demo/src/main.ts @@ -27,8 +27,7 @@ bootstrapApplication(AppComponent, { provideAnimations(), provideHttpClient(withInterceptorsFromDi()) ] -}) - .catch(err => console.error(err)); +}).catch((err) => console.error(err)); // TODO 'withInterceptorsFromDi' see following comment /** diff --git a/projects/mat-datatable-demo/src/styles.scss b/projects/mat-datatable-demo/src/styles.scss index 02b81d9..a908a9e 100644 --- a/projects/mat-datatable-demo/src/styles.scss +++ b/projects/mat-datatable-demo/src/styles.scss @@ -18,7 +18,7 @@ $theme: mat.define-light-theme( ( color: ( primary: $primary, - accent: $accent, + accent: $accent ), typography: mat.define-typography-config(), density: 0 diff --git a/projects/mat-datatable-demo/tsconfig.app.json b/projects/mat-datatable-demo/tsconfig.app.json index 1793e86..b39fd55 100644 --- a/projects/mat-datatable-demo/tsconfig.app.json +++ b/projects/mat-datatable-demo/tsconfig.app.json @@ -5,15 +5,7 @@ "outDir": "../../out-tsc/app", "types": [] }, - "files": [ - "src/main.ts" - ], - "include": [ - "src/**/*.d.ts", - "cypress" - ], - "exclude": [ - "**/*.spec.ts", - "cypress" - ] + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts", "cypress"], + "exclude": ["**/*.spec.ts", "cypress"] } diff --git a/projects/mat-datatable-demo/tsconfig.spec.json b/projects/mat-datatable-demo/tsconfig.spec.json index a1a2420..f3e7066 100644 --- a/projects/mat-datatable-demo/tsconfig.spec.json +++ b/projects/mat-datatable-demo/tsconfig.spec.json @@ -3,13 +3,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "../../out-tsc/spec", - "types": [ - "jasmine", - "node" - ] + "types": ["jasmine", "node"] }, - "include": [ - "src/**/*.spec.ts", - "src/**/*.d.ts" - ] + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] } diff --git a/projects/mat-datatable-lib/.eslintrc.json b/projects/mat-datatable-lib/.eslintrc.json index a1a0411..fa93353 100644 --- a/projects/mat-datatable-lib/.eslintrc.json +++ b/projects/mat-datatable-lib/.eslintrc.json @@ -1,14 +1,9 @@ { "extends": "../../.eslintrc.json", - "ignorePatterns": [ - "!**/*", - "**/.angular/**" - ], + "ignorePatterns": ["!**/*", "**/.angular/**"], "overrides": [ { - "files": [ - "*.ts" - ], + "files": ["*.ts"], "rules": { "@angular-eslint/directive-selector": [ "error", diff --git a/projects/mat-datatable-lib/CHANGELOG.md b/projects/mat-datatable-lib/CHANGELOG.md index e1e9bd1..99f3523 100644 --- a/projects/mat-datatable-lib/CHANGELOG.md +++ b/projects/mat-datatable-lib/CHANGELOG.md @@ -4,76 +4,68 @@ All notable changes to this project will be documented in this file. See [commit ## [17.2.13](https://github.com/BePo65/mat-datatable/compare/v17.2.12...v17.2.13) (2026-04-03) - ### Bug Fixes -* update packages to fix github dependabot security warning ([fb6b1bb](https://github.com/BePo65/mat-datatable/commit/fb6b1bb50a57120e8c706a3d1e823f7c80a63e9d)) -* update packages to fix github dependabot security warning ([e59b4df](https://github.com/BePo65/mat-datatable/commit/e59b4df18ca09ef61ff86c591abfe4569f5d3d18)) -* update packages to fix github dependabot security warnings ([9980c84](https://github.com/BePo65/mat-datatable/commit/9980c840772838b52699abbaec90a8ef420febfc)) +- update packages to fix github dependabot security warning ([fb6b1bb](https://github.com/BePo65/mat-datatable/commit/fb6b1bb50a57120e8c706a3d1e823f7c80a63e9d)) +- update packages to fix github dependabot security warning ([e59b4df](https://github.com/BePo65/mat-datatable/commit/e59b4df18ca09ef61ff86c591abfe4569f5d3d18)) +- update packages to fix github dependabot security warnings ([9980c84](https://github.com/BePo65/mat-datatable/commit/9980c840772838b52699abbaec90a8ef420febfc)) ## [17.2.12](https://github.com/BePo65/mat-datatable/compare/v17.2.11...v17.2.12) (2025-10-25) - ### Bug Fixes -* update packages to latest versions ([3e82442](https://github.com/BePo65/mat-datatable/commit/3e82442debaee05f97af67906b2046872d2e5c25)) +- update packages to latest versions ([3e82442](https://github.com/BePo65/mat-datatable/commit/3e82442debaee05f97af67906b2046872d2e5c25)) ## [17.2.11](https://github.com/BePo65/mat-datatable/compare/v17.2.10...v17.2.11) (2025-10-04) ## [17.2.10](https://github.com/BePo65/mat-datatable/compare/v17.2.9...v17.2.10) (2025-10-03) - ### Bug Fixes -* update packages to fix github dependabot security warning ([17b0331](https://github.com/BePo65/mat-datatable/commit/17b0331f1fc7340356ba51bc087494c25330ecf5)) -* update packages to fix github dependabot security warning ([30e8433](https://github.com/BePo65/mat-datatable/commit/30e8433ee9ee5bcf6e7de29bb36bd43373d97917)) -* update packages to fix github dependabot security warning ([ae52d6c](https://github.com/BePo65/mat-datatable/commit/ae52d6c74ac53b4b215cd3e9f277eb0b240aec25)) -* update packages to fix github security warnings ([8ddc68f](https://github.com/BePo65/mat-datatable/commit/8ddc68fb65e72158569d64af8ae0188895ee1e24)) +- update packages to fix github dependabot security warning ([17b0331](https://github.com/BePo65/mat-datatable/commit/17b0331f1fc7340356ba51bc087494c25330ecf5)) +- update packages to fix github dependabot security warning ([30e8433](https://github.com/BePo65/mat-datatable/commit/30e8433ee9ee5bcf6e7de29bb36bd43373d97917)) +- update packages to fix github dependabot security warning ([ae52d6c](https://github.com/BePo65/mat-datatable/commit/ae52d6c74ac53b4b215cd3e9f277eb0b240aec25)) +- update packages to fix github security warnings ([8ddc68f](https://github.com/BePo65/mat-datatable/commit/8ddc68fb65e72158569d64af8ae0188895ee1e24)) ## [17.2.9](https://github.com/BePo65/mat-datatable/compare/v17.2.8...v17.2.9) (2025-07-02) - ### Bug Fixes -* update packages to security issues ([f792ee4](https://github.com/BePo65/mat-datatable/commit/f792ee492d58968b904c85862c68adbd954f882b)) +- update packages to security issues ([f792ee4](https://github.com/BePo65/mat-datatable/commit/f792ee492d58968b904c85862c68adbd954f882b)) ## [17.2.8](https://github.com/BePo65/mat-datatable/compare/v17.2.7...v17.2.8) (2025-05-03) - ### Bug Fixes -* update packages to fix github dependabot security warning ([4acbd0b](https://github.com/BePo65/mat-datatable/commit/4acbd0bee40a01e574b4b7f0e91bda70f14c4764)) -* update packages to fix github dependabot security warning ([dd52c3a](https://github.com/BePo65/mat-datatable/commit/dd52c3a04a675ae8f1d6911d01c4bc190b4d8824)) -* update packages to fix github dependabot security warning ([06d4934](https://github.com/BePo65/mat-datatable/commit/06d493450718ac79d266cd807af109e20f20dd8a)) -* update packages to fix github dependabot security warning ([df9facb](https://github.com/BePo65/mat-datatable/commit/df9facb7cae2f37731fe6c6aece51368c619767b)) -* update packages to fix github dependabot security warnings ([90160a6](https://github.com/BePo65/mat-datatable/commit/90160a691c0f80871a8a558b1cabff5244bac3b0)) -* update packages to fix github dependabot security warnings ([97e0d3e](https://github.com/BePo65/mat-datatable/commit/97e0d3e83de751d8720a396b72c920f0a27ebb96)) +- update packages to fix github dependabot security warning ([4acbd0b](https://github.com/BePo65/mat-datatable/commit/4acbd0bee40a01e574b4b7f0e91bda70f14c4764)) +- update packages to fix github dependabot security warning ([dd52c3a](https://github.com/BePo65/mat-datatable/commit/dd52c3a04a675ae8f1d6911d01c4bc190b4d8824)) +- update packages to fix github dependabot security warning ([06d4934](https://github.com/BePo65/mat-datatable/commit/06d493450718ac79d266cd807af109e20f20dd8a)) +- update packages to fix github dependabot security warning ([df9facb](https://github.com/BePo65/mat-datatable/commit/df9facb7cae2f37731fe6c6aece51368c619767b)) +- update packages to fix github dependabot security warnings ([90160a6](https://github.com/BePo65/mat-datatable/commit/90160a691c0f80871a8a558b1cabff5244bac3b0)) +- update packages to fix github dependabot security warnings ([97e0d3e](https://github.com/BePo65/mat-datatable/commit/97e0d3e83de751d8720a396b72c920f0a27ebb96)) ## [17.2.7](https://github.com/BePo65/mat-datatable/compare/v17.2.6...v17.2.7) (2024-12-10) - ### Bug Fixes -* update packages to fix github dependabot security warnings ([682c4fb](https://github.com/BePo65/mat-datatable/commit/682c4fb762a86b4805881a7be0a41bc8ff476a0f)) -* update packages to fix github dependabot security warnings ([aca6b75](https://github.com/BePo65/mat-datatable/commit/aca6b750e679e77e24958d78cee1466a043458e7)) -* update packages to fix github dependabot security warnings ([4c5f840](https://github.com/BePo65/mat-datatable/commit/4c5f84004fe0ffeada49ca268d28083e9a3fa094)) -* update packages to fix github dependabot security warnings ([a8d81e1](https://github.com/BePo65/mat-datatable/commit/a8d81e186eedca49af7c9980ad8e0fd1c5ad4df3)) +- update packages to fix github dependabot security warnings ([682c4fb](https://github.com/BePo65/mat-datatable/commit/682c4fb762a86b4805881a7be0a41bc8ff476a0f)) +- update packages to fix github dependabot security warnings ([aca6b75](https://github.com/BePo65/mat-datatable/commit/aca6b750e679e77e24958d78cee1466a043458e7)) +- update packages to fix github dependabot security warnings ([4c5f840](https://github.com/BePo65/mat-datatable/commit/4c5f84004fe0ffeada49ca268d28083e9a3fa094)) +- update packages to fix github dependabot security warnings ([a8d81e1](https://github.com/BePo65/mat-datatable/commit/a8d81e186eedca49af7c9980ad8e0fd1c5ad4df3)) ## [17.2.6](https://github.com/BePo65/mat-datatable/compare/v17.2.5...v17.2.6) (2024-09-26) - ### Bug Fixes -* update packages to fix github dependabot security warning ([6244b0f](https://github.com/BePo65/mat-datatable/commit/6244b0f7cbede2caedcd0391d9f44d5f7a779201)) -* update packages to fix github dependabot security warnings ([1fda0e1](https://github.com/BePo65/mat-datatable/commit/1fda0e18b12d87268297c38185a09f91f1e4ba6a)) +- update packages to fix github dependabot security warning ([6244b0f](https://github.com/BePo65/mat-datatable/commit/6244b0f7cbede2caedcd0391d9f44d5f7a779201)) +- update packages to fix github dependabot security warnings ([1fda0e1](https://github.com/BePo65/mat-datatable/commit/1fda0e18b12d87268297c38185a09f91f1e4ba6a)) ## [17.2.5](https://github.com/BePo65/mat-datatable/compare/v17.2.4...v17.2.5) (2024-08-14) - ### Bug Fixes -* fix linting errors ([6d824f8](https://github.com/BePo65/mat-datatable/commit/6d824f810d9cccc584db9df03aa5166e140fb2f0)) -* update packages to fix github dependabot security warning ([f8b9990](https://github.com/BePo65/mat-datatable/commit/f8b999023f1243c56e0dd92ea554226f57e88a5a)) +- fix linting errors ([6d824f8](https://github.com/BePo65/mat-datatable/commit/6d824f810d9cccc584db9df03aa5166e140fb2f0)) +- update packages to fix github dependabot security warning ([f8b9990](https://github.com/BePo65/mat-datatable/commit/f8b999023f1243c56e0dd92ea554226f57e88a5a)) ## [17.2.4](https://github.com/BePo65/mat-datatable/compare/v17.2.3...v17.2.4) (2024-07-02) @@ -85,63 +77,56 @@ All notable changes to this project will be documented in this file. See [commit ## [17.2.0](https://github.com/BePo65/mat-datatable/compare/v17.1.1...v17.2.0) (2024-03-06) - ### Features -* fix problems with using the mat-datatable theme ([81fbc7a](https://github.com/BePo65/mat-datatable/commit/81fbc7a00bf4def71a3bcaed37c584fecb42629e)) -* **lib:** make theme an export of library ([42ee128](https://github.com/BePo65/mat-datatable/commit/42ee128d016eaf8c568324d38e31c1c6923e8066)) - +- fix problems with using the mat-datatable theme ([81fbc7a](https://github.com/BePo65/mat-datatable/commit/81fbc7a00bf4def71a3bcaed37c584fecb42629e)) +- **lib:** make theme an export of library ([42ee128](https://github.com/BePo65/mat-datatable/commit/42ee128d016eaf8c568324d38e31c1c6923e8066)) ### Bug Fixes -* remove scope from library ([28b1b59](https://github.com/BePo65/mat-datatable/commit/28b1b59c3b5908088de8cded9ba7e7e06a39084f)) +- remove scope from library ([28b1b59](https://github.com/BePo65/mat-datatable/commit/28b1b59c3b5908088de8cded9ba7e7e06a39084f)) ## [17.1.1](https://github.com/BePo65/mat-datatable/compare/v17.1.0...v17.1.1) (2024-03-05) ## [17.1.0](https://github.com/BePo65/mat-datatable/compare/v17.0.1...v17.1.0) (2024-03-05) - ### Features -* update app and lib to standalone ([917fd2a](https://github.com/BePo65/mat-datatable/commit/917fd2ae46e94f8aaebf033cb1dd44153cad6de9)) +- update app and lib to standalone ([917fd2a](https://github.com/BePo65/mat-datatable/commit/917fd2ae46e94f8aaebf033cb1dd44153cad6de9)) ## [17.0.1](https://github.com/BePo65/mat-datatable/compare/v17.0.0...v17.0.1) (2024-03-02) ## [17.0.0](https://github.com/BePo65/mat-datatable/compare/v16.0.0...v17.0.0) (2024-03-02) - ### ⚠ BREAKING CHANGES -* tha project now requires angular v17 +- tha project now requires angular v17 ### Features -* update to angular v17 ([dbe1ae8](https://github.com/BePo65/mat-datatable/commit/dbe1ae8fe06a5335d65babe6a420c230f73d3baa)) +- update to angular v17 ([dbe1ae8](https://github.com/BePo65/mat-datatable/commit/dbe1ae8fe06a5335d65babe6a420c230f73d3baa)) ## [16.0.0](https://github.com/BePo65/mat-datatable/compare/v15.0.0...v16.0.0) (2024-03-02) - ### ⚠ BREAKING CHANGES -* tha project now requires angular v16 +- tha project now requires angular v16 ### Features -* update to angular v16 ([5ecb525](https://github.com/BePo65/mat-datatable/commit/5ecb5253eaa1f226bf4d97c01ad4be311d81a3c2)) +- update to angular v16 ([5ecb525](https://github.com/BePo65/mat-datatable/commit/5ecb5253eaa1f226bf4d97c01ad4be311d81a3c2)) ## [15.0.0](https://github.com/BePo65/mat-datatable/compare/v0.0.0...v15.0.0) (2024-03-01) - ### Features -* **demo:** add link to source on github to header of demo page ([a01ea22](https://github.com/BePo65/mat-datatable/commit/a01ea22aa116a973bdf8f3ec71a7ebe88c435b47)) - +- **demo:** add link to source on github to header of demo page ([a01ea22](https://github.com/BePo65/mat-datatable/commit/a01ea22aa116a973bdf8f3ec71a7ebe88c435b47)) ### Bug Fixes -* **demo:** remove base tag from index.html to fix error 404 ([eecdfa5](https://github.com/BePo65/mat-datatable/commit/eecdfa5b85ec061f8c8aa5ae77b9c2cb7b60e9bf)) -* **demo:** remove routing from demo (was not used anyway) ([91d8a09](https://github.com/BePo65/mat-datatable/commit/91d8a09ca71c4a10d9bd41cc8e2110900117743b)) -* make missing icon in show/hide button visible ([96dd315](https://github.com/BePo65/mat-datatable/commit/96dd3159024bc510fbeda0ea63118843cbcf362a)), closes [github.com/angular/components/issues/14959#issuecomment-481797135](https://github.com/github.com/angular/components/issues/14959/issues/issuecomment-481797135) +- **demo:** remove base tag from index.html to fix error 404 ([eecdfa5](https://github.com/BePo65/mat-datatable/commit/eecdfa5b85ec061f8c8aa5ae77b9c2cb7b60e9bf)) +- **demo:** remove routing from demo (was not used anyway) ([91d8a09](https://github.com/BePo65/mat-datatable/commit/91d8a09ca71c4a10d9bd41cc8e2110900117743b)) +- make missing icon in show/hide button visible ([96dd315](https://github.com/BePo65/mat-datatable/commit/96dd3159024bc510fbeda0ea63118843cbcf362a)), closes [github.com/angular/components/issues/14959#issuecomment-481797135](https://github.com/github.com/angular/components/issues/14959/issues/issuecomment-481797135) ## 0.0.0 (2024-02-26) diff --git a/projects/mat-datatable-lib/README.md b/projects/mat-datatable-lib/README.md index 23574e9..9b1e3a9 100644 --- a/projects/mat-datatable-lib/README.md +++ b/projects/mat-datatable-lib/README.md @@ -55,19 +55,21 @@ A simple data table with virtual scrolling using Angular Material. + ## About The Project This project extends 'angular material table' so that it can be used as a replacement for [ngx-datatable](https://github.com/swimlane/ngx-datatable) in one of my projects. Unluckily ngx-datatable seems to be dead as it is still on angular v12 and an update to a more recent angular version is not in sight. Nat-Datatable implements a table with virtual scrolling, sorting and filtering. Only a minimal set of the functionality of ngx-datatable is implemented. -![Screenshot](assets/screenshot.jpg "Screenshot of the demo page") +![Screenshot](assets/screenshot.jpg 'Screenshot of the demo page') Try out the [live demo](https://bepo65.github.io/mat-datatable/).

(back to top)

+ ## Getting Started To use this package in your project just follow these simple steps. @@ -79,13 +81,15 @@ The package can be used in Angular apps with Angular Material installed. ### Installation Install the package from npmjs - ```sh - npm install @bepo65/mat-datatable - ``` + +```sh +npm install @bepo65/mat-datatable +``` ### Embed Mat-Datatable In Your Project Configure your angular application module (e.g: app.module.ts): + ```ts ... import { MatDatatableModule } from 'mat-datatable'; @@ -100,19 +104,23 @@ import { MatDatatableModule } from 'mat-datatable'; ``` Add Mat-Datatable to your html file (e.g: app.component.html): + ```html
- + [trackBy]="trackBy" + > loading...
``` The height of the element containing the mat-datatable must be set explicitly (e.g: app.component.scss): + ```css .content-table { height: 400px; @@ -122,6 +130,7 @@ The height of the element containing the mat-datatable must be set explicitly (e ``` Some properties of mat-datatable must be configured in the component class (e,g, app.component.ts): + ```ts export class AppComponent { ... @@ -150,6 +159,7 @@ export class AppComponent {

(back to top)

+ ## Used Assets The component is based on Angular Material and uses [Google Fonts](https://fonts.google.com/specimen/Roboto) and the [Google Material Icons](https://google.github.io/material-design-icons/#icon-font-for-the-web) font. @@ -158,6 +168,7 @@ Both fonts are part of the project and not fetched via https.

(back to top)

+ ## Mat-Datatable Demo Demo project to show all features of Mat-Datatable. @@ -173,6 +184,7 @@ Navigate to http://localhost:4200

(back to top)

+ ## API Reference `import { MatDatatable } from '@bepo65/mat-datatable';` @@ -435,6 +447,7 @@ The direction of a sort. | | + ## API Testing Harnesses `import { MatDatatableHarness } from '@bepo65/mat-datatable/testing';` @@ -445,7 +458,7 @@ The direction of a sort. -#### **_MatRowCellHarnessBase** extends [ContentContainerComponentHarness\](https://material.angular.io/cdk/test-harnesses/api#ContentContainerComponentHarness) +#### **\_MatRowCellHarnessBase** extends [ContentContainerComponentHarness\](https://material.angular.io/cdk/test-harnesses/api#ContentContainerComponentHarness) ##### **Methods** @@ -482,7 +495,7 @@ The direction of a sort. -#### **_MatRowHarnessBase** extends [ComponentHarness](https://material.angular.io/cdk/test-harnesses/api#ComponentHarness) +#### **\_MatRowHarnessBase** extends [ComponentHarness](https://material.angular.io/cdk/test-harnesses/api#ComponentHarness) Abstract class used as base for harnesses that interact with a mat-datatable row. @@ -1018,6 +1031,7 @@ A set of criteria that can be used to filter a list of row harness instances.

(back to top)

+ ## Roadmap See the [open issues](https://github.com/BePo65/mat-datatable/issues) for a full list of proposed features (and known issues). @@ -1025,13 +1039,15 @@ See the [open issues](https://github.com/BePo65/mat-datatable/issues) for a full

(back to top)

+ ## Hints On Possible Extensions -+ to make footer turn on / off dynamically it is not sufficient to wrap the footer cell and row definitions in ng-container. Details see [stackoverflow](https://stackoverflow.com/questions/63644938/angular-material-mat-table-dynamic-footer-header-rowdef/63648914#63648914). The demo uses [ngx-rerender](https://www.npmjs.com/package/ngx-rerender). +- to make footer turn on / off dynamically it is not sufficient to wrap the footer cell and row definitions in ng-container. Details see [stackoverflow](https://stackoverflow.com/questions/63644938/angular-material-mat-table-dynamic-footer-header-rowdef/63648914#63648914). The demo uses [ngx-rerender](https://www.npmjs.com/package/ngx-rerender).

(back to top)

+ ## Contributing Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. @@ -1046,15 +1062,18 @@ Don't forget to give the project a star! Thanks again! 5. Open a Pull Request ### Changelog + The project uses 'standard-version' to create the changelog. To enable this system, commit messages are linted before commits are executed by git. To enable this system you have to run the following scripts in your local repository home directory: + ``` npx husky install npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1" ``` **The structure of commit messages is**: + ```
@@ -1064,30 +1083,33 @@ npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1" ``` **header** + ``` (): ``` - + type and scope - - build: Changes that affect the build system or external dependencies (example scope: npm) - - docs: Documentation only changes - - feat: A new feature - - fix: A bug fix - - perf: A code change that improves performance - - refactor: A code change that neither fixes a bug nor adds a feature - - test: Adding missing tests or correcting existing tests (example scopes: demo, lib, e2e) + +- build: Changes that affect the build system or external dependencies (example scope: npm) +- docs: Documentation only changes +- feat: A new feature +- fix: A bug fix +- perf: A code change that improves performance +- refactor: A code change that neither fixes a bug nor adds a feature +- test: Adding missing tests or correcting existing tests (example scopes: demo, lib, e2e) **footer** + ``` BREAKING CHANGE: ... (requires MAJOR in Semantic Versioning) ``` For details of the commit messages format see [Contributing to Angular](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit). -

(back to top)

+ ## License Copyright © 2025 [Bernhard Pottler](https://github.com/BePo65). @@ -1099,7 +1121,9 @@ This project uses the fonts '[Roboto](https://fonts.google.com/specimen/Roboto/a

(back to top)

+ ## Hints + As `eslint` V9 requires a fundamental change to the configuration files, the update will be done in a later version. As a consequence the package `eslint-plugin-cypress` cannot be updated to a version 4.x or 5.x (as this version has a peerDependency of eslint >= 9). diff --git a/projects/mat-datatable-lib/assets/screenshot.jpg b/projects/mat-datatable-lib/assets/screenshot.jpg index 321a63d..835b7f6 100644 Binary files a/projects/mat-datatable-lib/assets/screenshot.jpg and b/projects/mat-datatable-lib/assets/screenshot.jpg differ diff --git a/projects/mat-datatable-lib/karma.conf.js b/projects/mat-datatable-lib/karma.conf.js index 1fee035..4c4ec2a 100644 --- a/projects/mat-datatable-lib/karma.conf.js +++ b/projects/mat-datatable-lib/karma.conf.js @@ -21,10 +21,7 @@ module.exports = function (config) { coverageReporter: { dir: require('path').join(__dirname, '../../coverage/mat-datatable-lib'), subdir: '.', - reporters: [ - { type: 'html' }, - { type: 'text-summary' } - ] + reporters: [{ type: 'html' }, { type: 'text-summary' }] }, reporters: ['progress', 'kjhtml'], port: 9876, diff --git a/projects/mat-datatable-lib/ng-package.json b/projects/mat-datatable-lib/ng-package.json index 2f09425..2b63df7 100644 --- a/projects/mat-datatable-lib/ng-package.json +++ b/projects/mat-datatable-lib/ng-package.json @@ -7,7 +7,11 @@ "README.md", "LICENSE", { "input": "src/lib", "glob": "_mat-datatable-table-theme.scss", "output": "lib" }, - { "input": "src/directives/datatable-sort", "glob": "_mat-multi-sort-badge-theme.scss", "output": "directives/datatable-sort" }, + { + "input": "src/directives/datatable-sort", + "glob": "_mat-multi-sort-badge-theme.scss", + "output": "directives/datatable-sort" + }, { "input": "src/theming", "glob": "**/*.scss", "output": "theming" } ], "lib": { diff --git a/projects/mat-datatable-lib/src/components/data-source.class.spec.ts b/projects/mat-datatable-lib/src/components/data-source.class.spec.ts index 9bc311e..1b2a91b 100644 --- a/projects/mat-datatable-lib/src/components/data-source.class.spec.ts +++ b/projects/mat-datatable-lib/src/components/data-source.class.spec.ts @@ -15,13 +15,16 @@ import { DataStoreProvider } from '../interfaces/datastore-provider.interface'; -import { TableVirtualScrollDataSource, TableVirtualScrollDataStoreSizes } from './data-source.class'; +import { + TableVirtualScrollDataSource, + TableVirtualScrollDataStoreSizes +} from './data-source.class'; type pageRequest = { - rowsRange: RequestRowsRange, - sorts?: FieldSortDefinition[], - filters?: FieldFilterDefinition[] -} + rowsRange: RequestRowsRange; + sorts?: FieldSortDefinition[]; + filters?: FieldFilterDefinition[]; +}; interface User { id: number; @@ -52,9 +55,7 @@ describe('TableVirtualScrollDataSource', () => { let testScheduler: TestScheduler; beforeEach(() => { - testScheduler = new TestScheduler((actual, expected) => - expect(actual).toEqual(expected) - ); + testScheduler = new TestScheduler((actual, expected) => expect(actual).toEqual(expected)); }); it('should emit datastore sizes once on connect', () => { @@ -67,7 +68,7 @@ describe('TableVirtualScrollDataSource', () => { const allEndpointParameters: pageRequest[] = [ { - rowsRange: { startRowIndex:0, numberOfRows:0 }, + rowsRange: { startRowIndex: 0, numberOfRows: 0 }, sorts: [], filters: [] } @@ -76,11 +77,11 @@ describe('TableVirtualScrollDataSource', () => { const fakeDataStore = new FakeUserDataStore(undefined, 80); const spyOnEndpoint = spyOn(fakeDataStore, 'getPagedData').and.callThrough(); const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const rangeToDisplay = new Subject; + const rangeToDisplay = new Subject(); const dataStoreSizes = dataSource.attachVirtualScroller(rangeToDisplay); dataSource.connect(); - testScheduler.run(helpers => { + testScheduler.run((helpers) => { const { expectObservable, flush } = helpers; const expectedMarbles = 'a'; const expectedValues = { @@ -92,7 +93,11 @@ describe('TableVirtualScrollDataSource', () => { flush(); expect(spyOnEndpoint).toHaveBeenCalledTimes(1); - expect(spyOnEndpoint).toHaveBeenCalledWith(allEndpointParameters[0].rowsRange, allEndpointParameters[0].sorts, allEndpointParameters[0].filters); + expect(spyOnEndpoint).toHaveBeenCalledWith( + allEndpointParameters[0].rowsRange, + allEndpointParameters[0].sorts, + allEndpointParameters[0].filters + ); }); // complete all subscriptions @@ -101,26 +106,26 @@ describe('TableVirtualScrollDataSource', () => { it('should get datastore sizes for 3 pages from datasource', () => { const allEndpointParameters: pageRequest[] = [ - { rowsRange:{ startRowIndex:0, numberOfRows:0 }, sorts:[], filters:[] }, - { rowsRange:{ startRowIndex:1, numberOfRows:2 }, sorts:[], filters:[] }, - { rowsRange:{ startRowIndex:5, numberOfRows:1 }, sorts:[], filters:[] }, - { rowsRange:{ startRowIndex:27, numberOfRows:3 }, sorts:[], filters:[] } + { rowsRange: { startRowIndex: 0, numberOfRows: 0 }, sorts: [], filters: [] }, + { rowsRange: { startRowIndex: 1, numberOfRows: 2 }, sorts: [], filters: [] }, + { rowsRange: { startRowIndex: 5, numberOfRows: 1 }, sorts: [], filters: [] }, + { rowsRange: { startRowIndex: 27, numberOfRows: 3 }, sorts: [], filters: [] } ]; // The spy also gets called by virtual scroller for getting the size of the datastore: // 1x from the constructor and 1x für every setting of 'sorts' and 'filters'. const expectedSpyCallingParameters = [ - [{ 'startRowIndex':0, 'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0, 'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0, 'numberOfRows':0 }, [], []], - [{ 'startRowIndex':1, 'numberOfRows':2 }, [], []], - [{ 'startRowIndex':0, 'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0, 'numberOfRows':0 }, [], []], - [{ 'startRowIndex':5, 'numberOfRows':1 }, [], []], - [{ 'startRowIndex':0, 'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0, 'numberOfRows':0 }, [], []], - [{ 'startRowIndex':27, 'numberOfRows':3 }, [], []] - ] as [ RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[] ][]; + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 1, numberOfRows: 2 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 5, numberOfRows: 1 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 27, numberOfRows: 3 }, [], []] + ] as [RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[]][]; const defaultSorts: FieldSortDefinition[] = []; const defaultFilters: FieldFilterDefinition[] = []; @@ -128,20 +133,20 @@ describe('TableVirtualScrollDataSource', () => { const fakeDataStore = new FakeUserDataStore(undefined, 80); const spyOnEndpoint = spyOn(fakeDataStore, 'getPagedData').and.callThrough(); const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const rangeToDisplay = new Subject; + const rangeToDisplay = new Subject(); const dataStoreSizes = dataSource.attachVirtualScroller(rangeToDisplay); dataSource.connect(); - testScheduler.run(helpers => { + testScheduler.run((helpers) => { const { cold, expectObservable, flush } = helpers; const sourceMarbles = '1-2----3-|'; const expectedMarbles = 'a-(bc)-(de)'; const expectedValues = { - a: { 'totalElements':80,'totalFilteredElements':80 }, - b: { 'totalElements':80,'totalFilteredElements':80 }, - c: { 'totalElements':80,'totalFilteredElements':80 }, - d: { 'totalElements':80,'totalFilteredElements':80 }, - e: { 'totalElements':80,'totalFilteredElements':80 } + a: { totalElements: 80, totalFilteredElements: 80 }, + b: { totalElements: 80, totalFilteredElements: 80 }, + c: { totalElements: 80, totalFilteredElements: 80 }, + d: { totalElements: 80, totalFilteredElements: 80 }, + e: { totalElements: 80, totalFilteredElements: 80 } }; const source = cold(sourceMarbles, { @@ -151,15 +156,13 @@ describe('TableVirtualScrollDataSource', () => { }); // Use source to make dataSource emit values - source.subscribe( - request => { - if (request !== undefined) { - dataSource.sorts = request.sorts || defaultSorts; - dataSource.filters = request.filters || defaultFilters; - rangeToDisplay.next(request.rowsRange); - } + source.subscribe((request) => { + if (request !== undefined) { + dataSource.sorts = request.sorts || defaultSorts; + dataSource.filters = request.filters || defaultFilters; + rangeToDisplay.next(request.rowsRange); } - ); + }); expectObservable(dataStoreSizes).toBe(expectedMarbles, expectedValues); @@ -179,11 +182,11 @@ describe('TableVirtualScrollDataSource', () => { it('should get rendered data and preliminary data for 1 page from datasource', () => { // The spy also gets called by virtual scroller for getting the size of the datastore const expectedSpyCallingParameters = [ - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':55,'numberOfRows':1 }, [], []] - ] as [ RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[] ][]; + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 55, numberOfRows: 1 }, [], []] + ] as [RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[]][]; const defaultSorts: FieldSortDefinition[] = []; const defaultFilters: FieldFilterDefinition[] = []; @@ -191,11 +194,11 @@ describe('TableVirtualScrollDataSource', () => { const fakeDataStore = new FakeUserDataStore(undefined, 80); const spyOnEndpoint = spyOn(fakeDataStore, 'getPagedData').and.callThrough(); const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const rangeToDisplay = new Subject; + const rangeToDisplay = new Subject(); dataSource.attachVirtualScroller(rangeToDisplay); const renderData = dataSource.connect(); - testScheduler.run(helpers => { + testScheduler.run((helpers) => { const { cold, expectObservable, flush } = helpers; const sourceMarbles = '-1-|'; const expectedMarbles = 'a (bc)'; @@ -206,19 +209,17 @@ describe('TableVirtualScrollDataSource', () => { }; const source = cold(sourceMarbles, { - 1: { rowsRange:{ startRowIndex:55, numberOfRows:1 }, sorts:[], filters:[] } + 1: { rowsRange: { startRowIndex: 55, numberOfRows: 1 }, sorts: [], filters: [] } }); // Use source to make dataSource emit values - source.subscribe( - request => { - if (request !== undefined) { - dataSource.sorts = request.sorts || defaultSorts; - dataSource.filters = request.filters || defaultFilters; - rangeToDisplay.next(request.rowsRange); - } + source.subscribe((request) => { + if (request !== undefined) { + dataSource.sorts = request.sorts || defaultSorts; + dataSource.filters = request.filters || defaultFilters; + rangeToDisplay.next(request.rowsRange); } - ); + }); expectObservable(renderData).toBe(expectedMarbles, expectedValues); @@ -243,25 +244,25 @@ describe('TableVirtualScrollDataSource', () => { it('should get rendered data and preliminary data for 3 pages from datasource', () => { const allEndpointParameters: pageRequest[] = [ - { rowsRange:{ startRowIndex:0, numberOfRows:0 }, sorts:[], filters:[] }, - { rowsRange:{ startRowIndex:1, numberOfRows:2 }, sorts:[], filters:[] }, - { rowsRange:{ startRowIndex:5, numberOfRows:1 }, sorts:[], filters:[] }, - { rowsRange:{ startRowIndex:27, numberOfRows:3 }, sorts:[], filters:[] } + { rowsRange: { startRowIndex: 0, numberOfRows: 0 }, sorts: [], filters: [] }, + { rowsRange: { startRowIndex: 1, numberOfRows: 2 }, sorts: [], filters: [] }, + { rowsRange: { startRowIndex: 5, numberOfRows: 1 }, sorts: [], filters: [] }, + { rowsRange: { startRowIndex: 27, numberOfRows: 3 }, sorts: [], filters: [] } ]; // The spy also gets called by virtual scroller for getting the size of the datastore const expectedSpyCallingParameters = [ - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':1,'numberOfRows':2 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':5,'numberOfRows':1 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':27,'numberOfRows':3 }, [], []] - ] as [ RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[] ][]; + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 1, numberOfRows: 2 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 5, numberOfRows: 1 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 27, numberOfRows: 3 }, [], []] + ] as [RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[]][]; const defaultSorts: FieldSortDefinition[] = []; const defaultFilters: FieldFilterDefinition[] = []; @@ -269,20 +270,27 @@ describe('TableVirtualScrollDataSource', () => { const fakeDataStore = new FakeUserDataStore(undefined, 80); const spyOnEndpoint = spyOn(fakeDataStore, 'getPagedData').and.callThrough(); const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const rangeToDisplay = new Subject; + const rangeToDisplay = new Subject(); dataSource.attachVirtualScroller(rangeToDisplay); const renderData = dataSource.connect(); - testScheduler.run(helpers => { + testScheduler.run((helpers) => { const { cold, expectObservable, flush } = helpers; const sourceMarbles = '1-2----3-|'; const expectedMarbles = 'a-(bc)-(de)'; const expectedValues = { - a: [{ id: 1, name: 'User0001' }, { id: 2, name: 'User0002' }], + a: [ + { id: 1, name: 'User0001' }, + { id: 2, name: 'User0002' } + ], b: Array(1), c: [{ id: 5, name: 'User0005' }], d: Array(3), - e: [{ id: 27, name: 'User0027' }, { id: 28, name: 'User0028' }, { id: 29, name: 'User0029' }] + e: [ + { id: 27, name: 'User0027' }, + { id: 28, name: 'User0028' }, + { id: 29, name: 'User0029' } + ] }; const source = cold(sourceMarbles, { @@ -292,15 +300,13 @@ describe('TableVirtualScrollDataSource', () => { }); // Use source to make dataSource emit values - source.subscribe( - request => { - if (request !== undefined) { - dataSource.sorts = request.sorts || defaultSorts; - dataSource.filters = request.filters || defaultFilters; - rangeToDisplay.next(request.rowsRange); - } + source.subscribe((request) => { + if (request !== undefined) { + dataSource.sorts = request.sorts || defaultSorts; + dataSource.filters = request.filters || defaultFilters; + rangeToDisplay.next(request.rowsRange); } - ); + }); expectObservable(renderData).toBe(expectedMarbles, expectedValues); @@ -319,25 +325,33 @@ describe('TableVirtualScrollDataSource', () => { it('should get 3 pages from datasource with sorting', () => { const allEndpointParameters: pageRequest[] = [ - { rowsRange:{ startRowIndex:0, numberOfRows:0 }, sorts:[], filters:[] }, - { rowsRange:{ startRowIndex:1, numberOfRows:2 }, sorts:[{ fieldName:'name', sortDirection:'desc' }], filters:[] }, - { rowsRange:{ startRowIndex:5, numberOfRows:1 }, sorts:[], filters:[] }, - { rowsRange:{ startRowIndex:27, numberOfRows:3 }, sorts:[{ fieldName:'id', sortDirection:'desc' }], filters:[] } + { rowsRange: { startRowIndex: 0, numberOfRows: 0 }, sorts: [], filters: [] }, + { + rowsRange: { startRowIndex: 1, numberOfRows: 2 }, + sorts: [{ fieldName: 'name', sortDirection: 'desc' }], + filters: [] + }, + { rowsRange: { startRowIndex: 5, numberOfRows: 1 }, sorts: [], filters: [] }, + { + rowsRange: { startRowIndex: 27, numberOfRows: 3 }, + sorts: [{ fieldName: 'id', sortDirection: 'desc' }], + filters: [] + } ]; // The spy also gets called by virtual scroller for getting the size of the datastore const expectedSpyCallingParameters = [ - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [{ fieldName:'name', sortDirection:'desc' }], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [{ fieldName:'name', sortDirection:'desc' }], []], - [{ 'startRowIndex':1,'numberOfRows':2 }, [{ fieldName:'name', sortDirection:'desc' }], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':5,'numberOfRows':1 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [{ fieldName:'id', sortDirection:'desc' }], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [{ fieldName:'id', sortDirection:'desc' }], []], - [{ 'startRowIndex':27,'numberOfRows':3 }, [{ fieldName:'id', sortDirection:'desc' }], []] - ] as [ RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[] ][]; + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [{ fieldName: 'name', sortDirection: 'desc' }], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [{ fieldName: 'name', sortDirection: 'desc' }], []], + [{ startRowIndex: 1, numberOfRows: 2 }, [{ fieldName: 'name', sortDirection: 'desc' }], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 5, numberOfRows: 1 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [{ fieldName: 'id', sortDirection: 'desc' }], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [{ fieldName: 'id', sortDirection: 'desc' }], []], + [{ startRowIndex: 27, numberOfRows: 3 }, [{ fieldName: 'id', sortDirection: 'desc' }], []] + ] as [RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[]][]; const defaultSorts: FieldSortDefinition[] = []; const defaultFilters: FieldFilterDefinition[] = []; @@ -345,20 +359,27 @@ describe('TableVirtualScrollDataSource', () => { const fakeDataStore = new FakeUserDataStore(undefined, 80); const spyOnEndpoint = spyOn(fakeDataStore, 'getPagedData').and.callThrough(); const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const rangeToDisplay = new Subject; + const rangeToDisplay = new Subject(); dataSource.attachVirtualScroller(rangeToDisplay); const renderData = dataSource.connect(); - testScheduler.run(helpers => { + testScheduler.run((helpers) => { const { cold, expectObservable, flush } = helpers; const sourceMarbles = '1-2----3-|'; const expectedMarbles = 'a-(bc)-(de)'; const expectedValues = { - a: [{ id: 78, name: 'User0078' }, { id: 77, name: 'User0077' }], + a: [ + { id: 78, name: 'User0078' }, + { id: 77, name: 'User0077' } + ], b: Array(1), c: [{ id: 5, name: 'User0005' }], d: Array(3), - e: [{ id: 52, name: 'User0052' }, { id: 51, name: 'User0051' }, { id: 50, name: 'User0050' }] + e: [ + { id: 52, name: 'User0052' }, + { id: 51, name: 'User0051' }, + { id: 50, name: 'User0050' } + ] }; const source = cold(sourceMarbles, { @@ -368,15 +389,13 @@ describe('TableVirtualScrollDataSource', () => { }); // Use source to make dataSource emit values - source.subscribe( - request => { - if (request !== undefined) { - dataSource.sorts = request.sorts || defaultSorts; - dataSource.filters = request.filters || defaultFilters; - rangeToDisplay.next(request.rowsRange); - } + source.subscribe((request) => { + if (request !== undefined) { + dataSource.sorts = request.sorts || defaultSorts; + dataSource.filters = request.filters || defaultFilters; + rangeToDisplay.next(request.rowsRange); } - ); + }); expectObservable(renderData).toBe(expectedMarbles, expectedValues); @@ -395,25 +414,47 @@ describe('TableVirtualScrollDataSource', () => { it('should get 3 pages from datasource with filtering', () => { const allEndpointParameters: pageRequest[] = [ - { rowsRange:{ startRowIndex:0, numberOfRows:0 }, sorts:[], filters:[] }, - { rowsRange:{ startRowIndex:0, numberOfRows:1 }, sorts:[], filters:[{ fieldName:'name', value:'User0037' }] as FieldFilterDefinition[] }, - { rowsRange:{ startRowIndex:0, numberOfRows:1 }, sorts:[], filters:[{ fieldName:'name', value:'User0028' }] as FieldFilterDefinition[] }, - { rowsRange:{ startRowIndex:20, numberOfRows:3 }, sorts:[], filters:[{ fieldName:'name', valueFrom:'User0003', valueTo:'User0030' }] as FieldFilterDefinition[] } + { rowsRange: { startRowIndex: 0, numberOfRows: 0 }, sorts: [], filters: [] }, + { + rowsRange: { startRowIndex: 0, numberOfRows: 1 }, + sorts: [], + filters: [{ fieldName: 'name', value: 'User0037' }] as FieldFilterDefinition[] + }, + { + rowsRange: { startRowIndex: 0, numberOfRows: 1 }, + sorts: [], + filters: [{ fieldName: 'name', value: 'User0028' }] as FieldFilterDefinition[] + }, + { + rowsRange: { startRowIndex: 20, numberOfRows: 3 }, + sorts: [], + filters: [ + { fieldName: 'name', valueFrom: 'User0003', valueTo: 'User0030' } + ] as FieldFilterDefinition[] + } ]; // The spy also gets called by virtual scroller for getting the size of the datastore const expectedSpyCallingParameters = [ - [{ 'startRowIndex':0,'numberOfRows':0 }, [], [] as FieldFilterDefinition[]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], [{ fieldName:'name', value:'User0037' }]], - [{ 'startRowIndex':0,'numberOfRows':1 }, [], [{ fieldName:'name', value:'User0037' }]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], [{ fieldName:'name', value:'User0037' }]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], [{ fieldName:'name', value:'User0028' }]], - [{ 'startRowIndex':0,'numberOfRows':1 }, [], [{ fieldName:'name', value:'User0028' }]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], [{ fieldName:'name', value:'User0028' }]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], [{ fieldName:'name', valueFrom:'User0003', valueTo:'User0030' }]], - [{ 'startRowIndex':20,'numberOfRows':3 }, [], [{ fieldName:'name', valueFrom:'User0003', valueTo:'User0030' }]] - ] as [ RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[] ][]; + [{ startRowIndex: 0, numberOfRows: 0 }, [], [] as FieldFilterDefinition[]], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], [{ fieldName: 'name', value: 'User0037' }]], + [{ startRowIndex: 0, numberOfRows: 1 }, [], [{ fieldName: 'name', value: 'User0037' }]], + [{ startRowIndex: 0, numberOfRows: 0 }, [], [{ fieldName: 'name', value: 'User0037' }]], + [{ startRowIndex: 0, numberOfRows: 0 }, [], [{ fieldName: 'name', value: 'User0028' }]], + [{ startRowIndex: 0, numberOfRows: 1 }, [], [{ fieldName: 'name', value: 'User0028' }]], + [{ startRowIndex: 0, numberOfRows: 0 }, [], [{ fieldName: 'name', value: 'User0028' }]], + [ + { startRowIndex: 0, numberOfRows: 0 }, + [], + [{ fieldName: 'name', valueFrom: 'User0003', valueTo: 'User0030' }] + ], + [ + { startRowIndex: 20, numberOfRows: 3 }, + [], + [{ fieldName: 'name', valueFrom: 'User0003', valueTo: 'User0030' }] + ] + ] as [RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[]][]; const defaultSorts: FieldSortDefinition[] = []; const defaultFilters: FieldFilterDefinition[] = []; @@ -421,11 +462,11 @@ describe('TableVirtualScrollDataSource', () => { const fakeDataStore = new FakeUserDataStore(undefined, 80); const spyOnEndpoint = spyOn(fakeDataStore, 'getPagedData').and.callThrough(); const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const rangeToDisplay = new Subject; + const rangeToDisplay = new Subject(); dataSource.attachVirtualScroller(rangeToDisplay); const renderData = dataSource.connect(); - testScheduler.run(helpers => { + testScheduler.run((helpers) => { const { cold, expectObservable, flush } = helpers; const sourceMarbles = '1-2----3-|'; const expectedMarbles = 'a-(bc)-(de)'; @@ -434,7 +475,11 @@ describe('TableVirtualScrollDataSource', () => { b: [{ id: 37, name: 'User0037' }], c: [{ id: 28, name: 'User0028' }], d: Array(3), - e: [{ id: 23, name: 'User0023' }, { id: 24, name: 'User0024' }, { id: 25, name: 'User0025' }] + e: [ + { id: 23, name: 'User0023' }, + { id: 24, name: 'User0024' }, + { id: 25, name: 'User0025' } + ] }; const source = cold(sourceMarbles, { @@ -444,15 +489,13 @@ describe('TableVirtualScrollDataSource', () => { }); // Use source to make dataSource emit values - source.subscribe( - request => { - if (request !== undefined) { - dataSource.sorts = request.sorts || defaultSorts; - dataSource.filters = request.filters || defaultFilters; - rangeToDisplay.next(request.rowsRange); - } + source.subscribe((request) => { + if (request !== undefined) { + dataSource.sorts = request.sorts || defaultSorts; + dataSource.filters = request.filters || defaultFilters; + rangeToDisplay.next(request.rowsRange); } - ); + }); expectObservable(renderData).toBe(expectedMarbles, expectedValues); @@ -471,25 +514,69 @@ describe('TableVirtualScrollDataSource', () => { it('should get 3 pages from datasource with sorting and filtering', () => { const allEndpointParameters: pageRequest[] = [ - { rowsRange:{ startRowIndex:0, numberOfRows:0 }, sorts:[], filters:[] }, - { rowsRange:{ startRowIndex:0, numberOfRows:1 }, sorts:[{ fieldName:'name', sortDirection:'desc' }], filters:[{ fieldName:'name', value:'User0037' }] as FieldFilterDefinitionSimple[] }, - { rowsRange:{ startRowIndex:0, numberOfRows:2 }, sorts:[], filters:[{ fieldName:'name', valueFrom:'User0068', valueTo:'User0079' }] as FieldFilterDefinitionRange[] }, - { rowsRange:{ startRowIndex:20, numberOfRows:3 }, sorts:[{ fieldName:'id', sortDirection:'desc' }], filters:[{ fieldName:'name', valueFrom:'User0003', valueTo:'User0030' }] as FieldFilterDefinitionRange[] } + { rowsRange: { startRowIndex: 0, numberOfRows: 0 }, sorts: [], filters: [] }, + { + rowsRange: { startRowIndex: 0, numberOfRows: 1 }, + sorts: [{ fieldName: 'name', sortDirection: 'desc' }], + filters: [{ fieldName: 'name', value: 'User0037' }] as FieldFilterDefinitionSimple[] + }, + { + rowsRange: { startRowIndex: 0, numberOfRows: 2 }, + sorts: [], + filters: [ + { fieldName: 'name', valueFrom: 'User0068', valueTo: 'User0079' } + ] as FieldFilterDefinitionRange[] + }, + { + rowsRange: { startRowIndex: 20, numberOfRows: 3 }, + sorts: [{ fieldName: 'id', sortDirection: 'desc' }], + filters: [ + { fieldName: 'name', valueFrom: 'User0003', valueTo: 'User0030' } + ] as FieldFilterDefinitionRange[] + } ]; // The spy also gets called by virtual scroller for getting the size of the datastore const expectedSpyCallingParameters = [ - [{ 'startRowIndex':0,'numberOfRows':0 }, [], [] as FieldFilterDefinition[]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [{ fieldName: 'name', sortDirection: 'desc' }], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [{ fieldName: 'name', sortDirection: 'desc' }], [{ fieldName:'name', value:'User0037' }]], - [{ 'startRowIndex':0,'numberOfRows':1 }, [{ fieldName: 'name', sortDirection: 'desc' }], [{ fieldName:'name', value:'User0037' }]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], [{ fieldName:'name', value:'User0037' }]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], [{ fieldName:'name', valueFrom:'User0068', valueTo: 'User0079' }]], - [{ 'startRowIndex':0,'numberOfRows':2 }, [], [{ fieldName:'name', valueFrom:'User0068', valueTo: 'User0079' }]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [{ fieldName: 'id', sortDirection: 'desc' }], [{ fieldName:'name', valueFrom:'User0068', valueTo: 'User0079' }]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [{ fieldName: 'id', sortDirection: 'desc' }], [{ fieldName:'name', valueFrom:'User0003', valueTo:'User0030' }]], - [{ 'startRowIndex':20,'numberOfRows':3 }, [{ fieldName: 'id', sortDirection: 'desc' }], [{ fieldName:'name', valueFrom:'User0003', valueTo:'User0030' }]] - ] as [ RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[] ][]; + [{ startRowIndex: 0, numberOfRows: 0 }, [], [] as FieldFilterDefinition[]], + [{ startRowIndex: 0, numberOfRows: 0 }, [{ fieldName: 'name', sortDirection: 'desc' }], []], + [ + { startRowIndex: 0, numberOfRows: 0 }, + [{ fieldName: 'name', sortDirection: 'desc' }], + [{ fieldName: 'name', value: 'User0037' }] + ], + [ + { startRowIndex: 0, numberOfRows: 1 }, + [{ fieldName: 'name', sortDirection: 'desc' }], + [{ fieldName: 'name', value: 'User0037' }] + ], + [{ startRowIndex: 0, numberOfRows: 0 }, [], [{ fieldName: 'name', value: 'User0037' }]], + [ + { startRowIndex: 0, numberOfRows: 0 }, + [], + [{ fieldName: 'name', valueFrom: 'User0068', valueTo: 'User0079' }] + ], + [ + { startRowIndex: 0, numberOfRows: 2 }, + [], + [{ fieldName: 'name', valueFrom: 'User0068', valueTo: 'User0079' }] + ], + [ + { startRowIndex: 0, numberOfRows: 0 }, + [{ fieldName: 'id', sortDirection: 'desc' }], + [{ fieldName: 'name', valueFrom: 'User0068', valueTo: 'User0079' }] + ], + [ + { startRowIndex: 0, numberOfRows: 0 }, + [{ fieldName: 'id', sortDirection: 'desc' }], + [{ fieldName: 'name', valueFrom: 'User0003', valueTo: 'User0030' }] + ], + [ + { startRowIndex: 20, numberOfRows: 3 }, + [{ fieldName: 'id', sortDirection: 'desc' }], + [{ fieldName: 'name', valueFrom: 'User0003', valueTo: 'User0030' }] + ] + ] as [RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[]][]; const defaultSorts: FieldSortDefinition[] = []; const defaultFilters: FieldFilterDefinition[] = []; @@ -497,20 +584,27 @@ describe('TableVirtualScrollDataSource', () => { const fakeDataStore = new FakeUserDataStore(undefined, 80); const spyOnEndpoint = spyOn(fakeDataStore, 'getPagedData').and.callThrough(); const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const rangeToDisplay = new Subject; + const rangeToDisplay = new Subject(); dataSource.attachVirtualScroller(rangeToDisplay); const renderData = dataSource.connect(); - testScheduler.run(helpers => { + testScheduler.run((helpers) => { const { cold, expectObservable, flush } = helpers; const sourceMarbles = '1-2----3-|'; const expectedMarbles = 'a-(bc)-(de)'; const expectedValues = { a: [{ id: 37, name: 'User0037' }], b: [{ id: 37, name: 'User0037' }, undefined], - c: [{ id: 68, name: 'User0068' }, { id: 69, name: 'User0069' }], + c: [ + { id: 68, name: 'User0068' }, + { id: 69, name: 'User0069' } + ], d: Array(3), - e: [{ id: 10, name: 'User0010' }, { id: 9, name: 'User0009' }, { id: 8, name: 'User0008' }] + e: [ + { id: 10, name: 'User0010' }, + { id: 9, name: 'User0009' }, + { id: 8, name: 'User0008' } + ] }; const source = cold(sourceMarbles, { @@ -520,15 +614,13 @@ describe('TableVirtualScrollDataSource', () => { }); // Use source to make dataSource emit values - source.subscribe( - request => { - if (request !== undefined) { - dataSource.sorts = request.sorts || defaultSorts; - dataSource.filters = request.filters || defaultFilters; - rangeToDisplay.next(request.rowsRange); - } + source.subscribe((request) => { + if (request !== undefined) { + dataSource.sorts = request.sorts || defaultSorts; + dataSource.filters = request.filters || defaultFilters; + rangeToDisplay.next(request.rowsRange); } - ); + }); expectObservable(renderData).toBe(expectedMarbles, expectedValues); @@ -547,47 +639,98 @@ describe('TableVirtualScrollDataSource', () => { it('should get 3 pages from datasource with sorting, filtering and trackBy', () => { const allEndpointParameters: pageRequest[] = [ - { rowsRange:{ startRowIndex:0, numberOfRows:0 }, sorts:[], filters:[] }, - { rowsRange:{ startRowIndex:0, numberOfRows:1 }, sorts:[{ fieldName:'name', sortDirection:'desc' }], filters:[{ fieldName:'name', value:'User0037' }] as FieldFilterDefinitionSimple[] }, - { rowsRange:{ startRowIndex:0, numberOfRows:2 }, sorts:[], filters:[{ fieldName:'name', valueFrom:'User0068', valueTo:'User0079' }] as FieldFilterDefinitionRange[] }, - { rowsRange:{ startRowIndex:20, numberOfRows:3 }, sorts:[{ fieldName:'id', sortDirection:'desc' }], filters:[{ fieldName:'name', valueFrom:'User0003', valueTo:'User0030' }] as FieldFilterDefinitionRange[] } + { rowsRange: { startRowIndex: 0, numberOfRows: 0 }, sorts: [], filters: [] }, + { + rowsRange: { startRowIndex: 0, numberOfRows: 1 }, + sorts: [{ fieldName: 'name', sortDirection: 'desc' }], + filters: [{ fieldName: 'name', value: 'User0037' }] as FieldFilterDefinitionSimple[] + }, + { + rowsRange: { startRowIndex: 0, numberOfRows: 2 }, + sorts: [], + filters: [ + { fieldName: 'name', valueFrom: 'User0068', valueTo: 'User0079' } + ] as FieldFilterDefinitionRange[] + }, + { + rowsRange: { startRowIndex: 20, numberOfRows: 3 }, + sorts: [{ fieldName: 'id', sortDirection: 'desc' }], + filters: [ + { fieldName: 'name', valueFrom: 'User0003', valueTo: 'User0030' } + ] as FieldFilterDefinitionRange[] + } ]; // The spy also gets called by virtual scroller for getting the size of the datastore const expectedSpyCallingParameters = [ - [{ 'startRowIndex':0,'numberOfRows':0 }, [], [] as FieldFilterDefinition[]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [{ fieldName: 'name', sortDirection: 'desc' }], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [{ fieldName: 'name', sortDirection: 'desc' }], [{ fieldName:'name', value:'User0037' }]], - [{ 'startRowIndex':0,'numberOfRows':1 }, [{ fieldName: 'name', sortDirection: 'desc' }], [{ fieldName:'name', value:'User0037' }]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], [{ fieldName:'name', value:'User0037' }]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], [{ fieldName:'name', valueFrom:'User0068', valueTo: 'User0079' }]], - [{ 'startRowIndex':0,'numberOfRows':2 }, [], [{ fieldName:'name', valueFrom:'User0068', valueTo: 'User0079' }]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [{ fieldName: 'id', sortDirection: 'desc' }], [{ fieldName:'name', valueFrom:'User0068', valueTo: 'User0079' }]], - [{ 'startRowIndex':0,'numberOfRows':0 }, [{ fieldName: 'id', sortDirection: 'desc' }], [{ fieldName:'name', valueFrom:'User0003', valueTo:'User0030' }]], - [{ 'startRowIndex':20,'numberOfRows':3 }, [{ fieldName: 'id', sortDirection: 'desc' }], [{ fieldName:'name', valueFrom:'User0003', valueTo:'User0030' }]] - ] as [ RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[] ][]; + [{ startRowIndex: 0, numberOfRows: 0 }, [], [] as FieldFilterDefinition[]], + [{ startRowIndex: 0, numberOfRows: 0 }, [{ fieldName: 'name', sortDirection: 'desc' }], []], + [ + { startRowIndex: 0, numberOfRows: 0 }, + [{ fieldName: 'name', sortDirection: 'desc' }], + [{ fieldName: 'name', value: 'User0037' }] + ], + [ + { startRowIndex: 0, numberOfRows: 1 }, + [{ fieldName: 'name', sortDirection: 'desc' }], + [{ fieldName: 'name', value: 'User0037' }] + ], + [{ startRowIndex: 0, numberOfRows: 0 }, [], [{ fieldName: 'name', value: 'User0037' }]], + [ + { startRowIndex: 0, numberOfRows: 0 }, + [], + [{ fieldName: 'name', valueFrom: 'User0068', valueTo: 'User0079' }] + ], + [ + { startRowIndex: 0, numberOfRows: 2 }, + [], + [{ fieldName: 'name', valueFrom: 'User0068', valueTo: 'User0079' }] + ], + [ + { startRowIndex: 0, numberOfRows: 0 }, + [{ fieldName: 'id', sortDirection: 'desc' }], + [{ fieldName: 'name', valueFrom: 'User0068', valueTo: 'User0079' }] + ], + [ + { startRowIndex: 0, numberOfRows: 0 }, + [{ fieldName: 'id', sortDirection: 'desc' }], + [{ fieldName: 'name', valueFrom: 'User0003', valueTo: 'User0030' }] + ], + [ + { startRowIndex: 20, numberOfRows: 3 }, + [{ fieldName: 'id', sortDirection: 'desc' }], + [{ fieldName: 'name', valueFrom: 'User0003', valueTo: 'User0030' }] + ] + ] as [RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[]][]; const defaultSorts: FieldSortDefinition[] = []; const defaultFilters: FieldFilterDefinition[] = []; - const trackByUserId: TrackByFunction = (index: number, item: User) => item.id; + const trackByUserId: TrackByFunction = (index: number, item: User) => item.id; const fakeDataStore = new FakeUserDataStore(trackByUserId, 80); const spyOnEndpoint = spyOn(fakeDataStore, 'getPagedData').and.callThrough(); const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const rangeToDisplay = new Subject; + const rangeToDisplay = new Subject(); dataSource.attachVirtualScroller(rangeToDisplay); const renderData = dataSource.connect(); - testScheduler.run(helpers => { + testScheduler.run((helpers) => { const { cold, expectObservable, flush } = helpers; const sourceMarbles = '1-2----3-|'; const expectedMarbles = 'a-(bc)-(de)'; const expectedValues = { a: [{ id: 37, name: 'User0037' }], b: [{ id: 37, name: 'User0037' }, undefined], - c: [{ id: 68, name: 'User0068' }, { id: 69, name: 'User0069' }], + c: [ + { id: 68, name: 'User0068' }, + { id: 69, name: 'User0069' } + ], d: Array(3), - e: [{ id: 10, name: 'User0010' }, { id: 9, name: 'User0009' }, { id: 8, name: 'User0008' }] + e: [ + { id: 10, name: 'User0010' }, + { id: 9, name: 'User0009' }, + { id: 8, name: 'User0008' } + ] }; const source = cold(sourceMarbles, { @@ -597,15 +740,13 @@ describe('TableVirtualScrollDataSource', () => { }); // Use source to make dataSource emit values - source.subscribe( - request => { - if (request !== undefined) { - dataSource.sorts = request.sorts || defaultSorts; - dataSource.filters = request.filters || defaultFilters; - rangeToDisplay.next(request.rowsRange); - } + source.subscribe((request) => { + if (request !== undefined) { + dataSource.sorts = request.sorts || defaultSorts; + dataSource.filters = request.filters || defaultFilters; + rangeToDisplay.next(request.rowsRange); } - ); + }); expectObservable(renderData).toBe(expectedMarbles, expectedValues); @@ -625,11 +766,11 @@ describe('TableVirtualScrollDataSource', () => { it('should indicate loading for 1 page', () => { // The spy also gets called by virtual scroller for getting the size of the datastore const expectedSpyCallingParameters = [ - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':11 }, [], []] - ] as [ RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[] ][]; + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 11 }, [], []] + ] as [RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[]][]; const defaultSorts: FieldSortDefinition[] = []; const defaultFilters: FieldFilterDefinition[] = []; @@ -637,11 +778,11 @@ describe('TableVirtualScrollDataSource', () => { const fakeDataStore = new FakeUserDataStore(undefined, 80); const spyOnEndpoint = spyOn(fakeDataStore, 'getPagedData').and.callThrough(); const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const rangeToDisplay = new Subject; + const rangeToDisplay = new Subject(); dataSource.attachVirtualScroller(rangeToDisplay); dataSource.connect(); - testScheduler.run(helpers => { + testScheduler.run((helpers) => { const { cold, expectObservable, flush } = helpers; const sourceMarbles = '-1-|'; const expectedMarbles = 'ab'; @@ -655,19 +796,17 @@ describe('TableVirtualScrollDataSource', () => { }; const source = cold(sourceMarbles, { - 1: { rowsRange:{ startRowIndex:0, numberOfRows:11 }, sorts:[], filters:[] } + 1: { rowsRange: { startRowIndex: 0, numberOfRows: 11 }, sorts: [], filters: [] } }); // Use source to make dataSource emit values - source.subscribe( - request => { - if (request !== undefined) { - dataSource.sorts = request.sorts || defaultSorts; - dataSource.filters = request.filters || defaultFilters; - rangeToDisplay.next(request.rowsRange); - } + source.subscribe((request) => { + if (request !== undefined) { + dataSource.sorts = request.sorts || defaultSorts; + dataSource.filters = request.filters || defaultFilters; + rangeToDisplay.next(request.rowsRange); } - ); + }); expectObservable(dataSource.loading$).toBe(expectedMarbles, expectedValues); @@ -687,17 +826,17 @@ describe('TableVirtualScrollDataSource', () => { it('should indicate loading for 3 pages', () => { // The spy also gets called by virtual scroller for getting the size of the datastore const expectedSpyCallingParameters = [ - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':11 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':1,'numberOfRows':12 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':0,'numberOfRows':0 }, [], []], - [{ 'startRowIndex':2,'numberOfRows':5 }, [], []] - ] as [ RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[] ][]; + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 11 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 1, numberOfRows: 12 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 0, numberOfRows: 0 }, [], []], + [{ startRowIndex: 2, numberOfRows: 5 }, [], []] + ] as [RequestRowsRange, FieldSortDefinition[], FieldFilterDefinition[]][]; const defaultSorts: FieldSortDefinition[] = []; const defaultFilters: FieldFilterDefinition[] = []; @@ -705,11 +844,11 @@ describe('TableVirtualScrollDataSource', () => { const fakeDataStore = new FakeUserDataStore(undefined, 80); const spyOnEndpoint = spyOn(fakeDataStore, 'getPagedData').and.callThrough(); const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const rangeToDisplay = new Subject; + const rangeToDisplay = new Subject(); dataSource.attachVirtualScroller(rangeToDisplay); dataSource.connect(); - testScheduler.run(helpers => { + testScheduler.run((helpers) => { const { cold, expectObservable, flush } = helpers; const sourceMarbles = '-1-2-3-|'; const expectedMarbles = 'ab-c-d'; @@ -725,21 +864,19 @@ describe('TableVirtualScrollDataSource', () => { }; const source = cold(sourceMarbles, { - 1: { rowsRange:{ startRowIndex:0, numberOfRows:11 }, sorts:[], filters:[] }, - 2: { rowsRange:{ startRowIndex:1, numberOfRows:12 }, sorts:[], filters:[] }, - 3: { rowsRange:{ startRowIndex:2, numberOfRows:5 }, sorts:[], filters:[] } + 1: { rowsRange: { startRowIndex: 0, numberOfRows: 11 }, sorts: [], filters: [] }, + 2: { rowsRange: { startRowIndex: 1, numberOfRows: 12 }, sorts: [], filters: [] }, + 3: { rowsRange: { startRowIndex: 2, numberOfRows: 5 }, sorts: [], filters: [] } }); // Use source to make dataSource emit values - source.subscribe( - request => { - if (request !== undefined) { - dataSource.sorts = request.sorts || defaultSorts; - dataSource.filters = request.filters || defaultFilters; - rangeToDisplay.next(request.rowsRange); - } + source.subscribe((request) => { + if (request !== undefined) { + dataSource.sorts = request.sorts || defaultSorts; + dataSource.filters = request.filters || defaultFilters; + rangeToDisplay.next(request.rowsRange); } - ); + }); expectObservable(dataSource.loading$).toBe(expectedMarbles, expectedValues); @@ -756,92 +893,90 @@ describe('TableVirtualScrollDataSource', () => { dataSource.disconnect(); }); - it('should get index of row in datasource', done => { + it('should get index of row in datasource', (done) => { const fakeDataStore = new FakeUserDataStore(undefined, 80); const dataSource = new TableVirtualScrollDataSource(fakeDataStore); const rowToGetIndexFor = { id: 55, name: 'User0055' }; - dataSource.rowToIndex(rowToGetIndexFor) - .subscribe(index => { - expect(index).toBe(55); - done(); - }); + dataSource.rowToIndex(rowToGetIndexFor).subscribe((index) => { + expect(index).toBe(55); + done(); + }); }); - it('should get index of row in datasource with sorting', done => { + it('should get index of row in datasource with sorting', (done) => { const fakeDataStore = new FakeUserDataStore(); fakeDataStore.dataSet = sortableDataset; const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const sorts = [{ fieldName:'name', sortDirection:'desc' }] as FieldSortDefinition[]; + const sorts = [{ fieldName: 'name', sortDirection: 'desc' }] as FieldSortDefinition[]; dataSource.sorts = sorts; const rowToGetIndexFor = { id: 4, name: 'Lithium', weight: 6.941, symbol: 'Li' }; - dataSource.rowToIndex(rowToGetIndexFor) - .subscribe(index => { - expect(index).toBe(3); - done(); - }); + dataSource.rowToIndex(rowToGetIndexFor).subscribe((index) => { + expect(index).toBe(3); + done(); + }); }); - it('should get index of row in datastore with filtering', done => { + it('should get index of row in datastore with filtering', (done) => { const fakeDataStore = new FakeUserDataStore(); fakeDataStore.dataSet = sortableDataset; const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const filters = [{ fieldName:'name', value:'Helium' }] as FieldFilterDefinition[]; + const filters = [{ fieldName: 'name', value: 'Helium' }] as FieldFilterDefinition[]; dataSource.filters = filters; const rowToGetIndexFor = { id: 3, name: 'Helium', weight: 4.0026, symbol: 'He' }; - dataSource.rowToIndex(rowToGetIndexFor) - .subscribe(index => { - expect(index).toBe(1); - done(); - }); + dataSource.rowToIndex(rowToGetIndexFor).subscribe((index) => { + expect(index).toBe(1); + done(); + }); }); - it('should get index of row in datastore with sorting and filtering', done => { + it('should get index of row in datastore with sorting and filtering', (done) => { const fakeDataStore = new FakeUserDataStore(); fakeDataStore.dataSet = sortableDataset; const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const sorts = [{ fieldName:'name', sortDirection:'desc' }, { fieldName:'id', sortDirection:'desc' }] as FieldSortDefinition[]; + const sorts = [ + { fieldName: 'name', sortDirection: 'desc' }, + { fieldName: 'id', sortDirection: 'desc' } + ] as FieldSortDefinition[]; dataSource.sorts = sorts; - const filters = [{ fieldName:'name', value:'Helium' }] as FieldFilterDefinition[]; + const filters = [{ fieldName: 'name', value: 'Helium' }] as FieldFilterDefinition[]; dataSource.filters = filters; const rowToGetIndexFor = { id: 3, name: 'Helium', weight: 4.0026, symbol: 'He' }; - dataSource.rowToIndex(rowToGetIndexFor) - .subscribe(index => { - expect(index).toBe(0); - done(); - }); + dataSource.rowToIndex(rowToGetIndexFor).subscribe((index) => { + expect(index).toBe(0); + done(); + }); }); - it('should get index of not existing row in datastore with sorting and filtering', done => { + it('should get index of not existing row in datastore with sorting and filtering', (done) => { const fakeDataStore = new FakeUserDataStore(); fakeDataStore.dataSet = sortableDataset; const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const sorts = [{ fieldName:'name', sortDirection:'desc' }] as FieldSortDefinition[]; + const sorts = [{ fieldName: 'name', sortDirection: 'desc' }] as FieldSortDefinition[]; dataSource.sorts = sorts; - const filters = [{ fieldName:'name', value:'Helium' }] as FieldFilterDefinition[]; + const filters = [{ fieldName: 'name', value: 'Helium' }] as FieldFilterDefinition[]; dataSource.filters = filters; const rowToGetIndexFor = { id: 4, name: 'Lithium', weight: 6.941, symbol: 'Li' }; - dataSource.rowToIndex(rowToGetIndexFor) - .subscribe(index => { - expect(index).toBe(-1); - done(); - }); + dataSource.rowToIndex(rowToGetIndexFor).subscribe((index) => { + expect(index).toBe(-1); + done(); + }); }); it('should indicate loading when getting index of row in datastore', () => { const fakeDataStore = new FakeUserDataStore(undefined, 80); const dataSource = new TableVirtualScrollDataSource(fakeDataStore); - const rangeToDisplay = new Subject; + const rangeToDisplay = new Subject(); dataSource.attachVirtualScroller(rangeToDisplay); dataSource.connect(); let numberOfCalls = 0; - testScheduler.run(helpers => { + testScheduler.run((helpers) => { const { cold, expectObservable, flush } = helpers; const sourceMarbles = '-1-|'; const expectedMarbles = 'ab'; @@ -859,14 +994,11 @@ describe('TableVirtualScrollDataSource', () => { }); // Use source to make dataSource emit values - source.subscribe( - row => { - if (row !== undefined) { - dataSource.rowToIndex(row) - .subscribe(() => numberOfCalls++); - } + source.subscribe((row) => { + if (row !== undefined) { + dataSource.rowToIndex(row).subscribe(() => numberOfCalls++); } - ); + }); expectObservable(dataSource.loading$).toBe(expectedMarbles, expectedValues); @@ -881,7 +1013,7 @@ describe('TableVirtualScrollDataSource', () => { }); class FakeUserDataStore implements DataStoreProvider { - public get datastoreSize() : number { + public get datastoreSize(): number { return this._datastoreSize; } public set datastoreSize(value: number) { @@ -889,12 +1021,14 @@ class FakeUserDataStore implements DataStoreProvider implements DataStoreProvider[] ): Observable { const selectedDataset = this.getRawDataSortedFiltered(sorts, filters); - return of(selectedDataset.findIndex(currentRow => this.trackBy(0, row) === this.trackBy(0, currentRow))); + return of( + selectedDataset.findIndex( + (currentRow) => this.trackBy(0, row) === this.trackBy(0, currentRow) + ) + ); } - public set dataSet(newDataset : DatatableItem[]) { + public set dataSet(newDataset: DatatableItem[]) { if (newDataset && Array.isArray(newDataset)) { this.fakeDataset = newDataset; this._datastoreSize = newDataset.length; @@ -960,26 +1098,31 @@ class FakeUserDataStore implements DataStoreProvider 0)) { + if (filters !== undefined && Array.isArray(filters) && filters.length > 0) { selectedDataset = selectedDataset.filter((row: DatatableItem) => { - return filters.reduce((isSelected: boolean, currentFilter: FieldFilterDefinition) => { - if (currentFilter.value !== undefined) { - isSelected ||= row[currentFilter.fieldName] === currentFilter.value; - } else if ((currentFilter.valueFrom !== undefined) && (currentFilter.valueTo !== undefined)) { - isSelected ||= ( - (row[currentFilter.fieldName] >= currentFilter.valueFrom) && - (row[currentFilter.fieldName] <= currentFilter.valueTo) - ); - } - return isSelected; - }, false); + return filters.reduce( + (isSelected: boolean, currentFilter: FieldFilterDefinition) => { + if (currentFilter.value !== undefined) { + isSelected ||= row[currentFilter.fieldName] === currentFilter.value; + } else if ( + currentFilter.valueFrom !== undefined && + currentFilter.valueTo !== undefined + ) { + isSelected ||= + row[currentFilter.fieldName] >= currentFilter.valueFrom && + row[currentFilter.fieldName] <= currentFilter.valueTo; + } + return isSelected; + }, + false + ); }); } // Sort data - only the first entry of the definitions is used - if ((sorts !== undefined) && Array.isArray(sorts)) { + if (sorts !== undefined && Array.isArray(sorts)) { this.currentSortingDefinitions = sorts; - if ((sorts.length > 0)) { + if (sorts.length > 0) { selectedDataset.sort(this.compareFn); } } @@ -990,7 +1133,10 @@ class FakeUserDataStore implements DataStoreProvider { const index = k; - const newUser = { id: index, name: `User${index.toString().padStart(4, '0')}` } as DatatableItem; + const newUser = { + id: index, + name: `User${index.toString().padStart(4, '0')}` + } as DatatableItem; return newUser; }); } @@ -1018,7 +1164,7 @@ class FakeUserDataStore implements DataStoreProvider implements DataStoreProviderb) */ private compare(a: string | number, b: string | number, isAsc: boolean): number { - return (a === b ? 0 : (a < b ? -1 : 1)) * (isAsc ? 1 : -1); + return (a === b ? 0 : a < b ? -1 : 1) * (isAsc ? 1 : -1); } } diff --git a/projects/mat-datatable-lib/src/components/data-source.class.ts b/projects/mat-datatable-lib/src/components/data-source.class.ts index 9c73bb5..d04ae02 100644 --- a/projects/mat-datatable-lib/src/components/data-source.class.ts +++ b/projects/mat-datatable-lib/src/components/data-source.class.ts @@ -2,11 +2,17 @@ import { DataSource } from '@angular/cdk/collections'; import { BehaviorSubject, Observable, of, Subject, Subscription } from 'rxjs'; import { map, take, takeUntil, filter, switchMap, tap } from 'rxjs/operators'; -import { Page, RequestRowsRange, FieldSortDefinition, FieldFilterDefinition, DataStoreProvider } from '../interfaces/datastore-provider.interface'; +import { + Page, + RequestRowsRange, + FieldSortDefinition, + FieldFilterDefinition, + DataStoreProvider +} from '../interfaces/datastore-provider.interface'; export interface TableVirtualScrollDataStoreSizes { - totalElements: number - totalFilteredElements: number + totalElements: number; + totalFilteredElements: number; } export interface TVSDataSource { @@ -18,7 +24,9 @@ export interface TVSDataSource { * @param rangeToDisplay$ - Observable emitting objects defining the rows required by the TableItemSizeDirective * @returns Observable emitting the absolute and the filtered size of the content of the data store */ - attachVirtualScroller(rangeToDisplay$: Observable): Observable; + attachVirtualScroller( + rangeToDisplay$: Observable + ): Observable; } /** @@ -40,7 +48,10 @@ export class TableVirtualScrollDataSource extends DataSource implements TV private readonly rangeToDisplayChanges$ = new Subject(); /** Stream that emits when the number of the entries in the data source changes (e.g. by filtering or adding/deleting data). */ - private readonly dataStoreSizes$ = new BehaviorSubject({ totalElements: 0, totalFilteredElements: 0 }); + private readonly dataStoreSizes$ = new BehaviorSubject({ + totalElements: 0, + totalFilteredElements: 0 + }); /** Stream that emits to request the number of entries the data source. */ private readonly requestDataStoreSizes$ = new Subject(); @@ -97,9 +108,7 @@ export class TableVirtualScrollDataSource extends DataSource implements TV } private _dataStoreProvider!: DataStoreProvider; - constructor( - private datastoreProviderObject?: DataStoreProvider - ) { + constructor(private datastoreProviderObject?: DataStoreProvider) { super(); // Get the number of rows in the data store; @@ -107,31 +116,31 @@ export class TableVirtualScrollDataSource extends DataSource implements TV this.requestDataStoreSizes$ .pipe( takeUntil(this.unsubscribe$), - switchMap(() => this.dataStoreProvider.getPagedData(this.requestForSizesOnly, this.sorts, this.filters) - .pipe( - take(1), - map((page: Page) => { - return { - totalElements: page.totalElements, - totalFilteredElements: page.totalFilteredElements - }; - }) - ) + switchMap(() => + this.dataStoreProvider + .getPagedData(this.requestForSizesOnly, this.sorts, this.filters) + .pipe( + take(1), + map((page: Page) => { + return { + totalElements: page.totalElements, + totalFilteredElements: page.totalFilteredElements + }; + }) + ) ) ) - .subscribe( - sizes => { - this.filteredSizeOfDataStore = sizes.totalFilteredElements; - this.dataStoreSizes$.next(sizes); - } - ); + .subscribe((sizes) => { + this.filteredSizeOfDataStore = sizes.totalFilteredElements; + this.dataStoreSizes$.next(sizes); + }); // dataStoreProvider is set after 'this.requestDataStoreSizes$' because setter // triggers 'this.reloadSizeOfDatastore()' if (this.datastoreProviderObject !== undefined) { this.dataStoreProvider = this.datastoreProviderObject; } else { - this.dataStoreProvider = new EmptyDataStoreProvider; + this.dataStoreProvider = new EmptyDataStoreProvider(); } } @@ -159,7 +168,9 @@ export class TableVirtualScrollDataSource extends DataSource implements TV * @param rangeToDisplay - observable emitting range of rows to display * @returns observable emitting size of data store */ - attachVirtualScroller(rangeToDisplay: Subject): Observable { + attachVirtualScroller( + rangeToDisplay: Subject + ): Observable { this.rangeToDisplay$ = rangeToDisplay; this.rangeToDisplayChanges$.next(); this._updateRangeToDisplaySubscription(); @@ -184,11 +195,10 @@ export class TableVirtualScrollDataSource extends DataSource implements TV // used to avoid "ExpressionChangedAfterItHasBeenCheckedError" setTimeout(() => this.loadingSubject.next(true), 0); - return this.dataStoreProvider.indexOfRow(row, this.sorts, this.filters) - .pipe( - takeUntil(this.unsubscribe$), - tap(() => this.loadingSubject.next(false)) - ); + return this.dataStoreProvider.indexOfRow(row, this.sorts, this.filters).pipe( + takeUntil(this.unsubscribe$), + tap(() => this.loadingSubject.next(false)) + ); } /** @@ -207,37 +217,37 @@ export class TableVirtualScrollDataSource extends DataSource implements TV // Send preliminary data evaluated from last list of rows to table to // satisfy virtual scroller. this.rangeToDisplaySubscription.add( - this.rangeToDisplay$ - .pipe( - takeUntil(this.unsubscribe$), - takeUntil(this.rangeToDisplayChanges$), - filter(() => this.isConnectedToTable) - ) - .subscribe(range => { - const preliminaryData = this.preliminaryRowsToDisplay(range); - this.renderData.next(preliminaryData); - }) + this.rangeToDisplay$ + .pipe( + takeUntil(this.unsubscribe$), + takeUntil(this.rangeToDisplayChanges$), + filter(() => this.isConnectedToTable) + ) + .subscribe((range) => { + const preliminaryData = this.preliminaryRowsToDisplay(range); + this.renderData.next(preliminaryData); + }) ); // Emit rows from data source to table this.rangeToDisplaySubscription.add( - this.rangeToDisplay$ - .pipe( - takeUntil(this.unsubscribe$), - takeUntil(this.rangeToDisplayChanges$), - filter(() => this.isConnectedToTable), - tap(() => { - // used to avoid "ExpressionChangedAfterItHasBeenCheckedError" - setTimeout(() => this.loadingSubject.next(true), 0); - }), - switchMap(range => this.dataStoreProvider.getPagedData(range, this.sorts, this.filters) - .pipe( - take(1), - tap(() => this.loadingSubject.next(false)) + this.rangeToDisplay$ + .pipe( + takeUntil(this.unsubscribe$), + takeUntil(this.rangeToDisplayChanges$), + filter(() => this.isConnectedToTable), + tap(() => { + // used to avoid "ExpressionChangedAfterItHasBeenCheckedError" + setTimeout(() => this.loadingSubject.next(true), 0); + }), + switchMap((range) => + this.dataStoreProvider.getPagedData(range, this.sorts, this.filters).pipe( + take(1), + tap(() => this.loadingSubject.next(false)) + ) ) ) - ) - .subscribe(page => { + .subscribe((page) => { this.renderData.next(page.content); this.lastRangeToDisplay = { startRowIndex: page.startRowIndex, @@ -251,8 +261,7 @@ export class TableVirtualScrollDataSource extends DataSource implements TV totalFilteredElements: page.totalFilteredElements }); } - } - ) + }) ); } @@ -280,17 +289,26 @@ export class TableVirtualScrollDataSource extends DataSource implements TV const copyLength = Math.max(Math.min(oldContentLength - offset, range.numberOfRows), 0); const copyEndIndex = offset + copyLength; // handle case, when we request data beyond the end of the datastore - const filteredSizeOfDataStore= this.filteredSizeOfDataStore || 0; - const numberOfExcessRows = Math.max((range.startRowIndex + range.numberOfRows) - filteredSizeOfDataStore, 0); + const filteredSizeOfDataStore = this.filteredSizeOfDataStore || 0; + const numberOfExcessRows = Math.max( + range.startRowIndex + range.numberOfRows - filteredSizeOfDataStore, + 0 + ); const emptyRowsToAdd = Math.max(range.numberOfRows - copyLength - numberOfExcessRows, 0); - result = this.lastRowsContent.slice(offset, copyEndIndex) - .concat(Array(emptyRowsToAdd)); + result = this.lastRowsContent.slice(offset, copyEndIndex).concat(Array(emptyRowsToAdd)); } else { // we take only rows that overlap between the lastRange and the requested range - const copyLength = Math.max(Math.min(oldContentLength - offset, range.startRowIndex + range.numberOfRows - lastRange.startRowIndex), 0); + const copyLength = Math.max( + Math.min( + oldContentLength - offset, + range.startRowIndex + range.numberOfRows - lastRange.startRowIndex + ), + 0 + ); const copyEndIndex = copyLength; - result = Array(Math.min(offset, range.numberOfRows)) - .concat(this.lastRowsContent.slice(0, copyEndIndex)); + result = Array(Math.min(offset, range.numberOfRows)).concat( + this.lastRowsContent.slice(0, copyEndIndex) + ); } return result; } @@ -301,8 +319,10 @@ export class TableVirtualScrollDataSource extends DataSource implements TV * @returns true, if the totalElements or the totalFilteredElements changed */ private datastoreSizesChanged(page: Page): boolean { - return (page.totalElements !== this.dataStoreSizes$.value.totalElements) || - (page.totalFilteredElements !== this.dataStoreSizes$.value.totalFilteredElements); + return ( + page.totalElements !== this.dataStoreSizes$.value.totalElements || + page.totalFilteredElements !== this.dataStoreSizes$.value.totalFilteredElements + ); } } @@ -317,23 +337,20 @@ export class EmptyDataStoreProvider implements DataStoreProvider { getPagedData( rowsRange: RequestRowsRange, sorts?: FieldSortDefinition[], - filters?: FieldFilterDefinition[]) { - return of( - { - content: [] as T[], - startRowIndex: 0, - returnedElements: 0, - totalElements: 0, - totalFilteredElements: 0 - } as Page); - } + filters?: FieldFilterDefinition[] + ) { + return of({ + content: [] as T[], + startRowIndex: 0, + returnedElements: 0, + totalElements: 0, + totalFilteredElements: 0 + } as Page); + } - indexOfRow( - row: T, - sorts?: FieldSortDefinition[], - filters?: FieldFilterDefinition[]) { - return of(-1); - } + indexOfRow(row: T, sorts?: FieldSortDefinition[], filters?: FieldFilterDefinition[]) { + return of(-1); + } } /* eslint-enable @typescript-eslint/no-unused-vars */ diff --git a/projects/mat-datatable-lib/src/directives/datatable-header-align.directive.spec.ts b/projects/mat-datatable-lib/src/directives/datatable-header-align.directive.spec.ts index 59319c3..b3325e8 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-header-align.directive.spec.ts +++ b/projects/mat-datatable-lib/src/directives/datatable-header-align.directive.spec.ts @@ -6,20 +6,20 @@ import { MatDatatableHeaderAlignDirective } from './datatable-header-align.direc describe('MatDatatableHeaderAlignDirective', () => { let fixture: ComponentFixture; - let directiveElements: DebugElement[]; // the elements with the directive + let directiveElements: DebugElement[]; // the elements with the directive beforeEach(() => { fixture = TestBed.configureTestingModule({ - imports: [ - TestComponent - ] + imports: [TestComponent] }).createComponent(TestComponent); // initial binding fixture.detectChanges(); // all elements with an attached MatDatatableHeaderAlignDirective - directiveElements = fixture.debugElement.queryAll(By.directive(MatDatatableHeaderAlignDirective)); + directiveElements = fixture.debugElement.queryAll( + By.directive(MatDatatableHeaderAlignDirective) + ); }); it('should create an instance', () => { @@ -57,50 +57,57 @@ describe('MatDatatableHeaderAlignDirective', () => { @Component({ template: ` -
- - - - - - - - - - - - - - - - - -
-
- -
-
- -
-
- -
-
- -
- 1 - - Rashawn - - Goyette - - Alexa1@gmail.com -
-
+
+ + + + + + + + + + + + + + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+ 1 + + Rashawn + + Goyette + + Alexa1@gmail.com +
+
`, standalone: true, - imports: [ - MatDatatableHeaderAlignDirective - ] + imports: [MatDatatableHeaderAlignDirective] }) -class TestComponent { } +class TestComponent {} diff --git a/projects/mat-datatable-lib/src/directives/datatable-header-align.directive.ts b/projects/mat-datatable-lib/src/directives/datatable-header-align.directive.ts index 754dacd..3c16026 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-header-align.directive.ts +++ b/projects/mat-datatable-lib/src/directives/datatable-header-align.directive.ts @@ -3,13 +3,16 @@ import { AfterViewInit, Directive, ElementRef, Input, Renderer2 } from '@angular import { ColumnAlignmentType } from '../interfaces/datatable-column-definition.interface'; @Directive({ - selector: '[matHeaderAlignment]', - standalone: true + selector: '[matHeaderAlignment]', + standalone: true }) export class MatDatatableHeaderAlignDirective implements AfterViewInit { @Input() matHeaderAlignment: ColumnAlignmentType | undefined; - constructor(private el: ElementRef, private renderer: Renderer2) { } + constructor( + private el: ElementRef, + private renderer: Renderer2 + ) {} ngAfterViewInit(): void { const hostElem = this.el.nativeElement; @@ -37,7 +40,11 @@ export class MatDatatableHeaderAlignDirective implements AfterViewInit { this.renderer.setStyle(children[indexOfHeaderContainerChild], 'justify-content', 'left'); break; case 'center': - this.renderer.setStyle(children[indexOfHeaderContainerChild], 'justify-content', 'center'); + this.renderer.setStyle( + children[indexOfHeaderContainerChild], + 'justify-content', + 'center' + ); this.renderer.setStyle(children[indexOfHeaderContainerChild], 'margin-right', '-18px'); break; case 'right': diff --git a/projects/mat-datatable-lib/src/directives/datatable-resizable.directive.spec.ts b/projects/mat-datatable-lib/src/directives/datatable-resizable.directive.spec.ts index 70045b1..a8f98a5 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-resizable.directive.spec.ts +++ b/projects/mat-datatable-lib/src/directives/datatable-resizable.directive.spec.ts @@ -6,14 +6,12 @@ import { MatDatatableResizableDirective } from './datatable-resizable.directive' describe('MatResizableDirective', () => { let fixture: ComponentFixture; - let directiveElements: DebugElement[]; // the elements with the directive + let directiveElements: DebugElement[]; // the elements with the directive beforeEach(() => { fixture = TestBed.configureTestingModule({ - imports: [MatDatatableResizableDirective, - TestComponent] -}) - .createComponent(TestComponent); + imports: [MatDatatableResizableDirective, TestComponent] + }).createComponent(TestComponent); fixture.detectChanges(); // initial binding // all elements with an attached MatDatatableHeaderAlignDirective @@ -64,27 +62,25 @@ describe('MatResizableDirective', () => { @Component({ template: ` - - -
default header
- - -
resizable header
- - -
not resizable header
- - -
not resizable header
- - -
not resizable header
- - + + +
default header
+ + +
resizable header
+ + +
not resizable header
+ + +
not resizable header
+ + +
not resizable header
+ + `, - standalone: true, - imports: [ - MatDatatableResizableDirective - ] + standalone: true, + imports: [MatDatatableResizableDirective] }) -class TestComponent { } +class TestComponent {} diff --git a/projects/mat-datatable-lib/src/directives/datatable-resizable.directive.ts b/projects/mat-datatable-lib/src/directives/datatable-resizable.directive.ts index 75a53ef..719e8d2 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-resizable.directive.ts +++ b/projects/mat-datatable-lib/src/directives/datatable-resizable.directive.ts @@ -1,21 +1,15 @@ import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; -import { - Directive, - ElementRef, - Input, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; +import { Directive, ElementRef, Input, NgZone, OnDestroy, OnInit, Renderer2 } from '@angular/core'; @Directive({ - selector: '[matResizable]', - standalone: true + selector: '[matResizable]', + standalone: true }) export class MatDatatableResizableDirective implements OnInit, OnDestroy { @Input() - get matResizable() { return this._matResizable; } + get matResizable() { + return this._matResizable; + } set matResizable(value: BooleanInput) { this._matResizable = coerceBooleanProperty(value); } @@ -25,7 +19,7 @@ export class MatDatatableResizableDirective implements OnInit, OnDestroy { private startWidth!: number; private column: HTMLElement; private table!: HTMLElement; - private resizing= false; + private resizing = false; private unlistenMouseDown?: () => void; private unlistenMouseMove?: () => void; @@ -34,7 +28,8 @@ export class MatDatatableResizableDirective implements OnInit, OnDestroy { constructor( private renderer: Renderer2, private el: ElementRef, - private ngZone: NgZone) { + private ngZone: NgZone + ) { this.column = this.el.nativeElement; } @@ -47,7 +42,9 @@ export class MatDatatableResizableDirective implements OnInit, OnDestroy { const resizer = this.renderer.createElement('span') as HTMLElement; this.renderer.addClass(resizer, 'resize-holder'); this.renderer.appendChild(this.column, resizer); - this.unlistenMouseDown = this.renderer.listen(resizer, 'mousedown', ($event: MouseEvent) => this.onMouseDown($event)); + this.unlistenMouseDown = this.renderer.listen(resizer, 'mousedown', ($event: MouseEvent) => + this.onMouseDown($event) + ); } } @@ -62,13 +59,17 @@ export class MatDatatableResizableDirective implements OnInit, OnDestroy { this.startWidth = this.column.offsetWidth; this.ngZone.runOutsideAngular(() => { - this.unlistenMouseMove = this.renderer.listen('document', 'mousemove', ($event: MouseEvent) => { - // Resizing with primary mouse button pressed - if (this.resizing && $event.buttons & 1) { - const width = this.startWidth + ($event.pageX - this.startX); - this.renderer.setStyle(this.column, 'width', `${width}px`); + this.unlistenMouseMove = this.renderer.listen( + 'document', + 'mousemove', + ($event: MouseEvent) => { + // Resizing with primary mouse button pressed + if (this.resizing && $event.buttons & 1) { + const width = this.startWidth + ($event.pageX - this.startX); + this.renderer.setStyle(this.column, 'width', `${width}px`); + } } - }); + ); }); this.unlistenMouseUp = this.renderer.listen('document', 'mouseup', () => { diff --git a/projects/mat-datatable-lib/src/directives/datatable-sort/README.md b/projects/mat-datatable-lib/src/directives/datatable-sort/README.md index 34f4989..bd5af74 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-sort/README.md +++ b/projects/mat-datatable-lib/src/directives/datatable-sort/README.md @@ -4,8 +4,9 @@ This component is a clone of the [Angular Material Sort-Header](https://material A modified version of the [Angular Material Badge](https://material.angular.io/components/badge/api) directive is used to display the ordering of the sorts in the column header. ## Licenses + **Angular Material Sort-Header** and **Angular Material Badge** use the MIT License: - Copyright Google LLC All Rights Reserved. +Copyright Google LLC All Rights Reserved. - Use of this source code is governed by an MIT-style license that can be found in the LICENSE file at https://angular.io/license +Use of this source code is governed by an MIT-style license that can be found in the LICENSE file at https://angular.io/license diff --git a/projects/mat-datatable-lib/src/directives/datatable-sort/_mat-multi-sort-badge-theme.scss b/projects/mat-datatable-lib/src/directives/datatable-sort/_mat-multi-sort-badge-theme.scss index fa57202..4da94b7 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-sort/_mat-multi-sort-badge-theme.scss +++ b/projects/mat-datatable-lib/src/directives/datatable-sort/_mat-multi-sort-badge-theme.scss @@ -155,7 +155,8 @@ $_badge-structure-emitted: false !default; } } -@mixin density($config-or-theme) {} +@mixin density($config-or-theme) { +} @mixin theme($theme) { @include mat.private-check-duplicate-theme-styles($theme, 'mat-multi-sort-badge') { diff --git a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-badge.directive.spec.ts b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-badge.directive.spec.ts index a2966d5..7c82189 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-badge.directive.spec.ts +++ b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-badge.directive.spec.ts @@ -14,9 +14,7 @@ describe('MatMultiSortBadgeDirective', () => { beforeEach(() => { fixture = TestBed.configureTestingModule({ - imports: [ - BadgeTestApp - ] + imports: [BadgeTestApp] }).createComponent(BadgeTestApp); testComponent = fixture.debugElement.componentInstance as BadgeTestApp; fixture.detectChanges(); // initial binding @@ -26,7 +24,9 @@ describe('MatMultiSortBadgeDirective', () => { }); it('should update the badge based on attribute', () => { - const badgeElement = badgeHostNativeElement.querySelector('.mat-multi-sort-badge-content') ?? document.createElement('div'); + const badgeElement = + badgeHostNativeElement.querySelector('.mat-multi-sort-badge-content') ?? + document.createElement('div'); expect(badgeElement.textContent).toContain('1'); @@ -37,7 +37,9 @@ describe('MatMultiSortBadgeDirective', () => { }); it('should be able to pass in falsy values to the badge content', () => { - const badgeElement = badgeHostNativeElement.querySelector('.mat-multi-sort-badge-content') ?? document.createElement('div'); + const badgeElement = + badgeHostNativeElement.querySelector('.mat-multi-sort-badge-content') ?? + document.createElement('div'); expect(badgeElement.textContent).toContain('1'); @@ -48,7 +50,9 @@ describe('MatMultiSortBadgeDirective', () => { }); it('should treat null and undefined as empty strings in the badge content', () => { - const badgeElement = badgeHostNativeElement.querySelector('.mat-multi-sort-badge-content') ?? document.createElement('div'); + const badgeElement = + badgeHostNativeElement.querySelector('.mat-multi-sort-badge-content') ?? + document.createElement('div'); expect(badgeElement.textContent).toContain('1'); @@ -136,7 +140,9 @@ describe('MatMultiSortBadgeDirective', () => { }); it('should apply view encapsulation on create badge content', () => { - const badge = badgeHostNativeElement.querySelector('.mat-multi-sort-badge-content') ?? document.createElement('div'); + const badge = + badgeHostNativeElement.querySelector('.mat-multi-sort-badge-content') ?? + document.createElement('div'); let encapsulationAttr: Attr | undefined; for (let i = 0; i < badge.attributes.length; i++) { @@ -164,13 +170,19 @@ describe('MatMultiSortBadgeDirective', () => { const preExistingFixture = TestBed.createComponent(PreExistingBadge); preExistingFixture.detectChanges(); - expect((preExistingFixture.nativeElement as HTMLElement).querySelectorAll('.mat-multi-sort-badge-content').length).toBe(1); + expect( + (preExistingFixture.nativeElement as HTMLElement).querySelectorAll( + '.mat-multi-sort-badge-content' + ).length + ).toBe(1); }); it('should expose the badge element', () => { const badgeElement = badgeHostNativeElement.querySelector('.mat-multi-sort-badge-content'); - expect(fixture.componentInstance.badgeInstance.getBadgeElement()).toBe(badgeElement as HTMLElement); + expect(fixture.componentInstance.badgeInstance.getBadgeElement()).toBe( + badgeElement as HTMLElement + ); }); }); @@ -180,19 +192,19 @@ describe('MatMultiSortBadgeDirective', () => { encapsulation: ViewEncapsulation.Emulated, styles: ['span { color: hotpink; }'], template: ` - - home - + [matMultiSortBadgeDisabled]="badgeDisabled" + > + home + `, standalone: true, - imports: [ - MatMultiSortBadgeDirective - ] + imports: [MatMultiSortBadgeDirective] }) class BadgeTestApp { @ViewChild(MatMultiSortBadgeDirective) badgeInstance!: MatMultiSortBadgeDirective; @@ -206,10 +218,10 @@ class BadgeTestApp { @Component({ template: ` - - home -
Pre-existing badge
-
+ + home +
Pre-existing badge
+
` }) class PreExistingBadge {} diff --git a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-badge.directive.ts b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-badge.directive.ts index b3db524..c8f17bc 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-badge.directive.ts +++ b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-badge.directive.ts @@ -26,18 +26,18 @@ const BADGE_CONTENT_CLASS = 'mat-multi-sort-badge-content'; /** Directive to display a text badge. */ @Directive({ - selector: '[matMultiSortBadge]', - inputs: ['disabled: matMultiSortBadgeDisabled'], - host: { - 'class': 'mat-multi-sort-badge', - '[class.mat-multi-sort-badge-above]': '!isAscending()', - '[class.mat-multi-sort-badge-below]': 'isAscending()', - '[class.mat-multi-sort-badge-before]': '!isAfter()', - '[class.mat-multi-sort-badge-after]': 'isAfter()', - '[class.mat-multi-sort-badge-hidden]': 'hidden || !content', - '[class.mat-multi-sort-badge-disabled]': 'disabled' - }, - standalone: true + selector: '[matMultiSortBadge]', + inputs: ['disabled: matMultiSortBadgeDisabled'], + host: { + class: 'mat-multi-sort-badge', + '[class.mat-multi-sort-badge-above]': '!isAscending()', + '[class.mat-multi-sort-badge-below]': 'isAscending()', + '[class.mat-multi-sort-badge-before]': '!isAfter()', + '[class.mat-multi-sort-badge-after]': 'isAfter()', + '[class.mat-multi-sort-badge-hidden]': 'hidden || !content', + '[class.mat-multi-sort-badge-disabled]': 'disabled' + }, + standalone: true }) export class MatMultiSortBadgeDirective implements OnInit, OnDestroy { /** @@ -95,7 +95,7 @@ export class MatMultiSortBadgeDirective implements OnInit, OnDestroy { private _ariaDescriber: AriaDescriber, private _renderer: Renderer2, @Optional() @Inject(ANIMATION_MODULE_TYPE) private _animationMode?: string - ) { } + ) {} /** Whether the badge is for an ascending arrow or not */ isAscending(): boolean { diff --git a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-errors.ts b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-errors.ts index b688176..79e2e92 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-errors.ts +++ b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-errors.ts @@ -13,7 +13,9 @@ export function getMultiSortDuplicateSortableIdError(id: string): Error { /** Error - header column not contained in a mat-multi-sort element */ export function getMultiSortHeaderNotContainedWithinMultiSortError(): Error { - return Error('MatMultiSortHeader must be placed within a parent element with the MatMultiSort directive.'); + return Error( + 'MatMultiSortHeader must be placed within a parent element with the MatMultiSort directive.' + ); } /** Error - header column without id */ diff --git a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-header.component.html b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-header.component.html index 93a1612..b51eece 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-header.component.html +++ b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-header.component.html @@ -8,12 +8,13 @@ The approach is based off of: https://dequeuniversity.com/library/aria/tables/sf-sortable-grid --> -
- +
-
+ [matMultiSortBadgePosition]="arrowPosition" + >
diff --git a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-header.component.ts b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-header.component.ts index be3d5b0..cdc3ff2 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-header.component.ts +++ b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort-header.component.ts @@ -48,17 +48,14 @@ interface MatSortHeaderColumnDef { templateUrl: './mat-multi-sort-header.component.html', styleUrls: ['./mat-multi-sort-header.component.scss'], host: { - 'class': 'mat-multi-sort-header' + class: 'mat-multi-sort-header' }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property inputs: ['disabled'], standalone: true, - imports: [ - MatMultiSortBadgeDirective, - NgIf - ], + imports: [MatMultiSortBadgeDirective, NgIf], animations: [ matSortAnimations.indicator, matSortAnimations.leftPointer, @@ -68,7 +65,10 @@ interface MatSortHeaderColumnDef { matSortAnimations.allowChildren ] }) -export class MatMultiSortHeader extends MatSortHeader implements AfterViewInit, CanDisable, OnDestroy { +export class MatMultiSortHeader + extends MatSortHeader + implements AfterViewInit, CanDisable, OnDestroy +{ @Input('mat-multi-sort-header') override id!: string; sortingPosition = ''; @@ -80,17 +80,20 @@ export class MatMultiSortHeader extends MatSortHeader implements AfterViewInit, changeDetectorRef: ChangeDetectorRef, @Optional() public override _sort: MatMultiSort, @Inject('MAT_SORT_HEADER_COLUMN_DEF') - @Optional() public override _columnDef: MatSortHeaderColumnDef, + @Optional() + public override _columnDef: MatSortHeaderColumnDef, _focusMonitor: FocusMonitor, _elementRef: ElementRef, @Optional() _ariaDescriber?: AriaDescriber | null, - @Optional() @Inject(MAT_SORT_DEFAULT_OPTIONS) - defaultOptions?: MatSortDefaultOptions) { - if (!_sort && isDevMode()) { - throw getMultiSortHeaderNotContainedWithinMultiSortError(); - } + @Optional() + @Inject(MAT_SORT_DEFAULT_OPTIONS) + defaultOptions?: MatSortDefaultOptions + ) { + if (!_sort && isDevMode()) { + throw getMultiSortHeaderNotContainedWithinMultiSortError(); + } - super( + super( _intl, changeDetectorRef, _sort, @@ -104,18 +107,14 @@ export class MatMultiSortHeader extends MatSortHeader implements AfterViewInit, override ngAfterViewInit(): void { super.ngAfterViewInit(); - this._sort.multiSortChange - .pipe( - takeUntil(this.unsubscribe$) - ) - .subscribe((sorts: Sort[]) => { - const sortIndex = sorts.findIndex(sort => sort.active === this.id); - if (sortIndex >= 0) { - this.sortingPosition = (sortIndex + 1).toString(); - } else { - this.sortingPosition = ''; - } - }); + this._sort.multiSortChange.pipe(takeUntil(this.unsubscribe$)).subscribe((sorts: Sort[]) => { + const sortIndex = sorts.findIndex((sort) => sort.active === this.id); + if (sortIndex >= 0) { + this.sortingPosition = (sortIndex + 1).toString(); + } else { + this.sortingPosition = ''; + } + }); } override ngOnDestroy(): void { @@ -126,7 +125,7 @@ export class MatMultiSortHeader extends MatSortHeader implements AfterViewInit, // Whether this MatMultiSortHeader is currently sorted in either ascending or descending order. override _isSorted() { - return this._sort.sortDefinitions.findIndex(sort => sort.active === this.id) > -1; + return this._sort.sortDefinitions.findIndex((sort) => sort.active === this.id) > -1; } /** @@ -155,12 +154,14 @@ export class MatMultiSortHeader extends MatSortHeader implements AfterViewInit, return 'none'; } - const sortingDefinition = this._sort.sortDefinitions.find(sort => sort.active === this.id); + const sortingDefinition = this._sort.sortDefinitions.find((sort) => sort.active === this.id); return sortingDefinition?.direction == 'asc' ? 'ascending' : 'descending'; } _getSortDirection(): SortDirection { - const i = this._sort.sortDefinitions.findIndex(sort => sort.active === this.id); - return this._isSorted() ? this._sort.sortDefinitions[i].direction : (this.start || this._sort.start); + const i = this._sort.sortDefinitions.findIndex((sort) => sort.active === this.id); + return this._isSorted() + ? this._sort.sortDefinitions[i].direction + : this.start || this._sort.start; } } diff --git a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort.directive.spec.ts b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort.directive.spec.ts index f2e308d..b2a66ec 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort.directive.spec.ts +++ b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort.directive.spec.ts @@ -4,10 +4,7 @@ import { CollectionViewer } from '@angular/cdk/collections'; import { CdkTableModule, DataSource } from '@angular/cdk/table'; import { Component, ViewChild, ElementRef } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; -import { MAT_SORT_DEFAULT_OPTIONS , - Sort, - SortDirection -} from '@angular/material/sort'; +import { MAT_SORT_DEFAULT_OPTIONS, Sort, SortDirection } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; import { By } from '@angular/platform-browser'; import { provideNoopAnimations } from '@angular/platform-browser/animations'; @@ -26,18 +23,14 @@ import { createFakeEvent, createMouseEvent } from './testbed/fake-events/event-o import { wrappedErrorMessage } from './wrapped-error-message'; describe('MatMultiSorDirective', () => { - describe('without default options', () => { + describe('without default options', () => { let fixture: ComponentFixture; let component: SimpleMatMultiSortApp; beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - SimpleMatMultiSortApp - ], - providers: [ - provideNoopAnimations() - ] + imports: [SimpleMatMultiSortApp], + providers: [provideNoopAnimations()] }).compileComponents(); })); @@ -97,12 +90,12 @@ describe('MatMultiSorDirective', () => { }); describe('checking correct arrow direction and view state for its various states', () => { - let expectedStates: Map; + let expectedStates: Map; beforeEach(() => { // Starting state for the view and directions - note that overrideStart is reversed to be // desc - expectedStates = new Map([ + expectedStates = new Map([ ['defaultA', { viewState: 'asc', arrowDirection: 'asc' }], ['defaultB', { viewState: 'asc', arrowDirection: 'asc' }], ['overrideStart', { viewState: 'desc', arrowDirection: 'desc' }], @@ -147,7 +140,10 @@ describe('MatMultiSorDirective', () => { it('should be correct when cycling through a default sort header', () => { // Sort the header to set it to the active start state component.sort('defaultA'); - expectedStates.set('defaultA', { viewState: 'asc-to-active', arrowDirection: 'active-asc' }); + expectedStates.set('defaultA', { + viewState: 'asc-to-active', + arrowDirection: 'active-asc' + }); component.expectViewAndDirectionStates(expectedStates); // Sorting again will reverse its direction @@ -180,13 +176,19 @@ describe('MatMultiSorDirective', () => { it('should be correct when adding sort for second header', () => { // Sort the first header to set up component.sort('defaultA'); - expectedStates.set('defaultA', { viewState: 'asc-to-active', arrowDirection: 'active-asc' }); + expectedStates.set('defaultA', { + viewState: 'asc-to-active', + arrowDirection: 'active-asc' + }); component.expectViewAndDirectionStates(expectedStates); // Sort the second header and verify that the first header did not change component.dispatchMouseEvent('defaultB', 'click'); expectedStates.set('defaultA', { viewState: 'active', arrowDirection: 'active-asc' }); - expectedStates.set('defaultB', { viewState: 'asc-to-active', arrowDirection: 'active-asc' }); + expectedStates.set('defaultB', { + viewState: 'asc-to-active', + arrowDirection: 'active-asc' + }); component.expectViewAndDirectionStates(expectedStates); }); @@ -202,24 +204,28 @@ describe('MatMultiSorDirective', () => { // eslint-disable-next-line jasmine/missing-expect it('should be correct when sorting programmatically', () => { - const newSort1: Sort[] = [ - { active:'defaultB', direction:'desc' } - ]; + const newSort1: Sort[] = [{ active: 'defaultB', direction: 'desc' }]; component.matMultiSort.sortDefinitions = newSort1; fixture.detectChanges(); - expectedStates.set('defaultB', { viewState: 'desc-to-active', arrowDirection: 'active-desc' }); + expectedStates.set('defaultB', { + viewState: 'desc-to-active', + arrowDirection: 'active-desc' + }); component.expectViewAndDirectionStates(expectedStates); // Change sort definition const newSort2: Sort[] = [ - { active:'defaultA', direction:'desc' }, - { active:'defaultB', direction:'asc' } + { active: 'defaultA', direction: 'desc' }, + { active: 'defaultB', direction: 'asc' } ]; component.matMultiSort.sortDefinitions = newSort2; fixture.detectChanges(); - expectedStates.set('defaultA', { viewState: 'desc-to-active', arrowDirection: 'active-desc' }); + expectedStates.set('defaultA', { + viewState: 'desc-to-active', + arrowDirection: 'active-desc' + }); expectedStates.set('defaultB', { viewState: 'active', arrowDirection: 'active-asc' }); component.expectViewAndDirectionStates(expectedStates); }); @@ -227,14 +233,20 @@ describe('MatMultiSorDirective', () => { // eslint-disable-next-line jasmine/missing-expect it('should be correct when setting sort programmatically twice', () => { const newSort: Sort[] = [ - { active:'defaultA', direction:'desc' }, - { active:'defaultB', direction:'asc' } + { active: 'defaultA', direction: 'desc' }, + { active: 'defaultB', direction: 'asc' } ]; component.matMultiSort.sortDefinitions = newSort; fixture.detectChanges(); - expectedStates.set('defaultA', { viewState: 'desc-to-active', arrowDirection: 'active-desc' }); - expectedStates.set('defaultB', { viewState: 'asc-to-active', arrowDirection: 'active-asc' }); + expectedStates.set('defaultA', { + viewState: 'desc-to-active', + arrowDirection: 'active-desc' + }); + expectedStates.set('defaultB', { + viewState: 'asc-to-active', + arrowDirection: 'active-asc' + }); component.expectViewAndDirectionStates(expectedStates); // apply sorting definition again @@ -253,8 +265,8 @@ describe('MatMultiSorDirective', () => { expect(startSortDefinitions.length).toBe(0); const newSort: Sort[] = [ - { active:'defaultA', direction:'desc' }, - { active:'defaultB', direction:'asc' } + { active: 'defaultA', direction: 'desc' }, + { active: 'defaultB', direction: 'asc' } ]; component.matMultiSort.sortDefinitions = newSort; fixture.detectChanges(); @@ -290,7 +302,9 @@ describe('MatMultiSorDirective', () => { }); it('should allow for the cycling the sort direction to be disabled per column', () => { - const container = (fixture.nativeElement as HTMLElement).querySelector('#defaultA .mat-sort-header-container'); + const container = (fixture.nativeElement as HTMLElement).querySelector( + '#defaultA .mat-sort-header-container' + ); component.sort('defaultA'); @@ -313,7 +327,9 @@ describe('MatMultiSorDirective', () => { }); it('should allow for the cycling the sort direction to be disabled for all columns', () => { - const container = (fixture.nativeElement as HTMLElement).querySelector('#defaultA .mat-sort-header-container'); + const container = (fixture.nativeElement as HTMLElement).querySelector( + '#defaultA .mat-sort-header-container' + ); component.sort('defaultA'); @@ -361,11 +377,10 @@ describe('MatMultiSorDirective', () => { }); it('should throw an error if an MatMultiSortable is not contained within an MatMultiSort directive', () => { - expect(() => - TestBed.createComponent(MatMultiSortHeaderMissingMatMultiSortApp).detectChanges() - ).toThrowError(wrappedErrorMessage(getMultiSortHeaderNotContainedWithinMultiSortError())); - } - ); + expect(() => + TestBed.createComponent(MatMultiSortHeaderMissingMatMultiSortApp).detectChanges() + ).toThrowError(wrappedErrorMessage(getMultiSortHeaderNotContainedWithinMultiSortError())); + }); it('should throw an error if two MatSortables have the same id', () => { expect(() => @@ -396,7 +411,9 @@ describe('MatMultiSorDirective', () => { it('should toggle indicator hint on button focus/blur and hide on click', () => { const header = fixture.componentInstance.defaultA; - const container = (fixture.nativeElement as HTMLElement).querySelector('#defaultA .mat-sort-header-container'); + const container = (fixture.nativeElement as HTMLElement).querySelector( + '#defaultA .mat-sort-header-container' + ); const focusEvent = createFakeEvent('focus'); const blurEvent = createFakeEvent('blur'); @@ -497,16 +514,17 @@ describe('MatMultiSorDirective', () => { })); it('should have a focus indicator', () => { - const headerNativeElement = fixture.debugElement.query( - By.directive(MatMultiSortHeader) - ).nativeElement as HTMLElement; + const headerNativeElement = fixture.debugElement.query(By.directive(MatMultiSortHeader)) + .nativeElement as HTMLElement; const container = headerNativeElement.querySelector('.mat-sort-header-container'); expect(container?.classList.contains('mat-focus-indicator')).toBe(true); }); it('should add a default aria description to sort buttons', () => { - const sortButton = (fixture.nativeElement as HTMLElement).querySelector('.mat-sort-header-container'); + const sortButton = (fixture.nativeElement as HTMLElement).querySelector( + '.mat-sort-header-container' + ); const descriptionId = sortButton?.getAttribute('aria-describedby') || ''; expect(descriptionId).toBeDefined(); @@ -538,30 +556,38 @@ describe('MatMultiSorDirective', () => { it('should render arrows after sort header by default', () => { // create component with 'arrowPosition = undefined' - const matSortWithArrowPositionFixture = TestBed.createComponent(MatMultiSortWithArrowPositionApp); + const matSortWithArrowPositionFixture = TestBed.createComponent( + MatMultiSortWithArrowPositionApp + ); matSortWithArrowPositionFixture.detectChanges(); - const containerA = (matSortWithArrowPositionFixture.nativeElement as HTMLElement) - .querySelector('#defaultA .mat-sort-header-container'); - const containerB = (matSortWithArrowPositionFixture.nativeElement as HTMLElement) - .querySelector('#defaultB .mat-sort-header-container'); + const containerA = ( + matSortWithArrowPositionFixture.nativeElement as HTMLElement + ).querySelector('#defaultA .mat-sort-header-container'); + const containerB = ( + matSortWithArrowPositionFixture.nativeElement as HTMLElement + ).querySelector('#defaultB .mat-sort-header-container'); expect(containerA?.classList.contains('mat-sort-header-position-before')).toBe(false); expect(containerB?.classList.contains('mat-sort-header-position-before')).toBe(false); }); it('should render arrows before if appropriate parameter passed', () => { - const matSortWithArrowPositionFixture = TestBed.createComponent(MatMultiSortWithArrowPositionApp); + const matSortWithArrowPositionFixture = TestBed.createComponent( + MatMultiSortWithArrowPositionApp + ); const matSortWithArrowPositionComponent = matSortWithArrowPositionFixture.componentInstance; matSortWithArrowPositionComponent.arrowPosition = 'before'; matSortWithArrowPositionFixture.detectChanges(); - const containerA = (matSortWithArrowPositionFixture.nativeElement as HTMLElement) - .querySelector('#defaultA .mat-sort-header-container'); - const containerB = (matSortWithArrowPositionFixture.nativeElement as HTMLElement) - .querySelector('#defaultB .mat-sort-header-container'); + const containerA = ( + matSortWithArrowPositionFixture.nativeElement as HTMLElement + ).querySelector('#defaultA .mat-sort-header-container'); + const containerB = ( + matSortWithArrowPositionFixture.nativeElement as HTMLElement + ).querySelector('#defaultB .mat-sort-header-container'); expect(containerA?.classList.contains('mat-sort-header-position-before')).toBe(true); expect(containerB?.classList.contains('mat-sort-header-position-before')).toBe(true); @@ -569,15 +595,19 @@ describe('MatMultiSorDirective', () => { it('should render arrows in proper position based on arrowPosition parameter', () => { // create component with 'arrowPosition = undefined' - const matSortWithArrowPositionFixture = TestBed.createComponent(MatMultiSortWithArrowPositionApp); + const matSortWithArrowPositionFixture = TestBed.createComponent( + MatMultiSortWithArrowPositionApp + ); const matSortWithArrowPositionComponent = matSortWithArrowPositionFixture.componentInstance; matSortWithArrowPositionFixture.detectChanges(); - const containerA = (matSortWithArrowPositionFixture.nativeElement as HTMLElement) - .querySelector('#defaultA .mat-sort-header-container'); - const containerB = (matSortWithArrowPositionFixture.nativeElement as HTMLElement) - .querySelector('#defaultB .mat-sort-header-container'); + const containerA = ( + matSortWithArrowPositionFixture.nativeElement as HTMLElement + ).querySelector('#defaultA .mat-sort-header-container'); + const containerB = ( + matSortWithArrowPositionFixture.nativeElement as HTMLElement + ).querySelector('#defaultB .mat-sort-header-container'); expect(containerA?.classList.contains('mat-sort-header-position-before')).toBe(false); expect(containerB?.classList.contains('mat-sort-header-position-before')).toBe(false); @@ -592,30 +622,38 @@ describe('MatMultiSorDirective', () => { it('should render sorting position badge after sort header by default', () => { // create component with 'arrowPosition = undefined' - const matSortWithArrowPositionFixture = TestBed.createComponent(MatMultiSortWithArrowPositionApp); + const matSortWithArrowPositionFixture = TestBed.createComponent( + MatMultiSortWithArrowPositionApp + ); matSortWithArrowPositionFixture.detectChanges(); - const badgeA = (matSortWithArrowPositionFixture.nativeElement as HTMLElement) - .querySelector('#defaultA .mat-multi-sort-badge'); - const badgeB = (matSortWithArrowPositionFixture.nativeElement as HTMLElement) - .querySelector('#defaultB .mat-multi-sort-badge'); + const badgeA = (matSortWithArrowPositionFixture.nativeElement as HTMLElement).querySelector( + '#defaultA .mat-multi-sort-badge' + ); + const badgeB = (matSortWithArrowPositionFixture.nativeElement as HTMLElement).querySelector( + '#defaultB .mat-multi-sort-badge' + ); expect(badgeA?.classList.contains('mat-multi-sort-badge-after')).toBeTruthy(); expect(badgeB?.classList.contains('mat-multi-sort-badge-after')).toBeTruthy(); }); it('should render sorting position badge before if appropriate parameter passed', () => { - const matSortWithArrowPositionFixture = TestBed.createComponent(MatMultiSortWithArrowPositionApp); + const matSortWithArrowPositionFixture = TestBed.createComponent( + MatMultiSortWithArrowPositionApp + ); const matSortWithArrowPositionComponent = matSortWithArrowPositionFixture.componentInstance; matSortWithArrowPositionComponent.arrowPosition = 'before'; matSortWithArrowPositionFixture.detectChanges(); - const containerA = (matSortWithArrowPositionFixture.nativeElement as HTMLElement) - .querySelector('#defaultA .mat-multi-sort-badge'); - const containerB = (matSortWithArrowPositionFixture.nativeElement as HTMLElement) - .querySelector('#defaultB .mat-multi-sort-badge'); + const containerA = ( + matSortWithArrowPositionFixture.nativeElement as HTMLElement + ).querySelector('#defaultA .mat-multi-sort-badge'); + const containerB = ( + matSortWithArrowPositionFixture.nativeElement as HTMLElement + ).querySelector('#defaultB .mat-multi-sort-badge'); expect(containerA?.classList.contains('mat-multi-sort-badge-before')).toBeTruthy(); expect(containerB?.classList.contains('mat-multi-sort-badge-before')).toBeTruthy(); @@ -623,16 +661,20 @@ describe('MatMultiSorDirective', () => { it('should render sorting position badge in proper position based on arrowPosition parameter', () => { // create component with 'arrowPosition = undefined' - const matSortWithArrowPositionFixture = TestBed.createComponent(MatMultiSortWithArrowPositionApp); + const matSortWithArrowPositionFixture = TestBed.createComponent( + MatMultiSortWithArrowPositionApp + ); const matSortWithArrowPositionComponent = matSortWithArrowPositionFixture.componentInstance; matSortWithArrowPositionComponent.arrowPosition = 'after'; matSortWithArrowPositionFixture.detectChanges(); - const containerA = (matSortWithArrowPositionFixture.nativeElement as HTMLElement) - .querySelector('#defaultA .mat-multi-sort-badge'); - const containerB = (matSortWithArrowPositionFixture.nativeElement as HTMLElement) - .querySelector('#defaultB .mat-multi-sort-badge'); + const containerA = ( + matSortWithArrowPositionFixture.nativeElement as HTMLElement + ).querySelector('#defaultA .mat-multi-sort-badge'); + const containerB = ( + matSortWithArrowPositionFixture.nativeElement as HTMLElement + ).querySelector('#defaultB .mat-multi-sort-badge'); expect(containerA?.classList.contains('mat-multi-sort-badge-after')).toBeTruthy(); expect(containerB?.classList.contains('mat-multi-sort-badge-after')).toBeTruthy(); @@ -646,16 +688,18 @@ describe('MatMultiSorDirective', () => { }); it('should render sorting position badge in proper position based on arrow direction', () => { - const badgeB = (fixture.nativeElement as HTMLElement).querySelector('#defaultB .mat-multi-sort-badge'); + const badgeB = (fixture.nativeElement as HTMLElement).querySelector( + '#defaultB .mat-multi-sort-badge' + ); - // Sort the header to set it to the ascending state - component.matMultiSort.sortDefinitions = [{ active:'defaultB', direction:'asc' }]; + // Sort the header to set it to the ascending state + component.matMultiSort.sortDefinitions = [{ active: 'defaultB', direction: 'asc' }]; fixture.detectChanges(); expect(component.defaultB._arrowDirection).toBe('asc'); expect(badgeB?.classList.contains('mat-multi-sort-badge-below')).toBeTruthy(); - component.matMultiSort.sortDefinitions = [{ active:'defaultB', direction:'desc' }]; + component.matMultiSort.sortDefinitions = [{ active: 'defaultB', direction: 'desc' }]; fixture.detectChanges(); expect(component.defaultB._arrowDirection).toBe('desc'); @@ -669,9 +713,7 @@ describe('MatMultiSorDirective', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - MatMultiSortWithoutExplicitInputsApp - ], + imports: [MatMultiSortWithoutExplicitInputsApp], providers: [ { provide: MAT_SORT_DEFAULT_OPTIONS, @@ -706,9 +748,7 @@ describe('MatMultiSorDirective', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - MatSortWithoutInputsApp - ], + imports: [MatSortWithoutInputsApp], providers: [ { provide: MAT_SORT_DEFAULT_OPTIONS, @@ -728,10 +768,12 @@ describe('MatMultiSorDirective', () => { }); it('should render arrows in proper position', () => { - const containerA = (fixture.nativeElement as HTMLElement) - .querySelector('#defaultA .mat-sort-header-container'); - const containerB = (fixture.nativeElement as HTMLElement) - .querySelector('#defaultB .mat-sort-header-container'); + const containerA = (fixture.nativeElement as HTMLElement).querySelector( + '#defaultA .mat-sort-header-container' + ); + const containerB = (fixture.nativeElement as HTMLElement).querySelector( + '#defaultB .mat-sort-header-container' + ); expect(containerA?.classList.contains('mat-sort-header-position-before')).toBe(true); expect(containerB?.classList.contains('mat-sort-header-position-before')).toBe(true); @@ -759,11 +801,11 @@ function testSingleColumnSortDirectionSequence( component.matMultiSort.sortDefinitions = []; // Run through the sequence to confirm the order - const actualSequence = expectedSequence.map(entry => { + const actualSequence = expectedSequence.map((entry) => { component.sort(id); // Check that the sort event's active sort is consistent with the MatSort - if(entry.length !== 0) { + if (entry.length !== 0) { expect(component.matMultiSort.sortDefinitions.length).toBe(1); expect(component.matMultiSort.sortDefinitions[0].active).toBe(id); expect(component.matMultiSort.sortDefinitions[0].direction).toBe(entry); @@ -771,7 +813,9 @@ function testSingleColumnSortDirectionSequence( expect(component.latestSortEvent[0].active).toBe(id); // Check that the sort event's direction is consistent with the MatSort - expect(component.matMultiSort.sortDefinitions[0].direction).toBe(component.latestSortEvent[0].direction); + expect(component.matMultiSort.sortDefinitions[0].direction).toBe( + component.latestSortEvent[0].direction + ); } else { expect(component.matMultiSort.sortDefinitions.length).toBe(0); expect(component.latestSortEvent.length).toBe(0); @@ -790,43 +834,41 @@ type SimpleMatSortAppColumnIds = 'defaultA' | 'defaultB' | 'overrideStart' | 'ov @Component({ template: ` -
-
- A -
-
- B -
-
- D -
-
- E -
-
+
+
+ A +
+
+ B +
+
+ D +
+
+ E +
+
`, standalone: true, - imports: [ - MatMultiSort, - MatMultiSortHeader - ] + imports: [MatMultiSort, MatMultiSortHeader] }) class SimpleMatMultiSortApp { latestSortEvent!: Sort[]; @@ -853,7 +895,7 @@ class SimpleMatMultiSortApp { dispatchMouseEvent(id: SimpleMatSortAppColumnIds, event: string) { const sortElement = this.elementRef.nativeElement.querySelector(`#${id}`); - if ((sortElement !== undefined) && (sortElement !== null)) { + if (sortElement !== undefined && sortElement !== null) { dispatchMouseEvent(sortElement, event); } } @@ -865,7 +907,7 @@ class SimpleMatMultiSortApp { * @param viewStates - Map with ViewStates to be tested */ expectViewAndDirectionStates( - viewStates: Map + viewStates: Map ) { const sortHeaders = new Map([ ['defaultA', this.defaultA], @@ -893,32 +935,34 @@ class FakeDataSource extends DataSource { @Component({ template: ` - - - Column A - {{row.a}} - - - - Column B - {{row.b}} - - - - Column C - {{row.c}} - - - - - + + + + Column A + + {{ row.a }} + + + + + Column B + + {{ row.b }} + + + + + Column C + + {{ row.c }} + + + + + `, standalone: true, - imports: [ - CdkTableModule, - MatMultiSort, - MatMultiSortHeader - ] + imports: [CdkTableModule, MatMultiSort, MatMultiSortHeader] }) class CdkTableMatMultiSortApp { @ViewChild(MatMultiSort) matMultiSort: MatMultiSort | undefined; @@ -929,32 +973,34 @@ class CdkTableMatMultiSortApp { @Component({ template: ` - - - Column A - {{row.a}} - - - - Column B - {{row.b}} - - - - Column C - {{row.c}} - - - - - + + + + Column A + + {{ row.a }} + + + + + Column B + + {{ row.b }} + + + + + Column C + + {{ row.c }} + + + + + `, standalone: true, - imports: [ - MatMultiSort, - MatMultiSortHeader, - MatTableModule - ] + imports: [MatMultiSort, MatMultiSortHeader, MatTableModule] }) class MatTableMatMultiSortApp { @ViewChild(MatMultiSort) matMultiSort: MatMultiSort | undefined; @@ -966,24 +1012,19 @@ class MatTableMatMultiSortApp { @Component({ template: '
A
', standalone: true, - imports: [ - MatMultiSortHeader - ] + imports: [MatMultiSortHeader] }) class MatMultiSortHeaderMissingMatMultiSortApp {} @Component({ template: ` -
-
A
-
B
-
+
+
A
+
B
+
`, standalone: true, - imports: [ - MatMultiSort, - MatMultiSortHeader - ] + imports: [MatMultiSort, MatMultiSortHeader] }) class MatMultiSortDuplicateMatSortableIdsApp { @ViewChild(MatMultiSort) matMultiSort!: MatMultiSort; @@ -991,51 +1032,41 @@ class MatMultiSortDuplicateMatSortableIdsApp { @Component({ template: ` -
-
A
-
+
+
A
+
`, standalone: true, - imports: [ - MatMultiSort, - MatMultiSortHeader - ] + imports: [MatMultiSort, MatMultiSortHeader] }) class MatSortableMissingIdApp {} @Component({ template: ` -
-
A
-
+
+
A
+
`, standalone: true, - imports: [ - MatMultiSort, - MatMultiSortHeader - ] + imports: [MatMultiSort, MatMultiSortHeader] }) class MatMultiSortableInvalidDirectionApp {} @Component({ template: ` -
-
- A -
-
+
+
A
+
`, standalone: true, - imports: [ - MatMultiSort, - MatMultiSortHeader - ] + imports: [MatMultiSort, MatMultiSortHeader] }) class MatMultiSortWithoutExplicitInputsApp { - latestSortEvent!: Sort[]; active!: string; @@ -1052,7 +1083,7 @@ class MatMultiSortWithoutExplicitInputsApp { dispatchMouseEvent(id: SimpleMatSortAppColumnIds, event: string) { const sortElement = this.elementRef.nativeElement.querySelector(`#${id}`); - if ((sortElement !== undefined) && (sortElement !== null)) { + if (sortElement !== undefined && sortElement !== null) { dispatchMouseEvent(sortElement, event); } } @@ -1060,20 +1091,17 @@ class MatMultiSortWithoutExplicitInputsApp { @Component({ template: ` -
-
- A -
-
- B -
-
+
+
+ A +
+
+ B +
+
`, standalone: true, - imports: [ - MatMultiSort, - MatMultiSortHeader - ] + imports: [MatMultiSort, MatMultiSortHeader] }) class MatMultiSortWithArrowPositionApp { // component with default value of 'arrowPosition' = undefined @@ -1085,20 +1113,13 @@ class MatMultiSortWithArrowPositionApp { @Component({ template: ` -
-
- A -
-
- B -
-
+
+
A
+
B
+
`, standalone: true, - imports: [ - MatMultiSort, - MatMultiSortHeader - ] + imports: [MatMultiSort, MatMultiSortHeader] }) class MatSortWithoutInputsApp { @ViewChild(MatMultiSort) matMultiSort!: MatMultiSort; diff --git a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort.directive.ts b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort.directive.ts index ba2ed94..25d0358 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort.directive.ts +++ b/projects/mat-datatable-lib/src/directives/datatable-sort/mat-multi-sort.directive.ts @@ -2,15 +2,7 @@ /* eslint-disable @angular-eslint/no-host-metadata-property */ /* eslint-disable @angular-eslint/no-output-rename */ -import { - Directive, - EventEmitter, - Inject, - Input, - Optional, - Output, - isDevMode -} from '@angular/core'; +import { Directive, EventEmitter, Inject, Input, Optional, Output, isDevMode } from '@angular/core'; import { MatSort, MatSortable, @@ -27,12 +19,12 @@ import { // Container for MatSortables to manage the sort state and provide default sort parameters. @Directive({ - selector: '[matMultiSort]', - exportAs: 'matMultiSort', - host: { - 'class': 'mat-multi-sort' - }, - standalone: true + selector: '[matMultiSort]', + exportAs: 'matMultiSort', + host: { + class: 'mat-multi-sort' + }, + standalone: true }) export class MatMultiSort extends MatSort { // The currently active sort definition. @@ -50,7 +42,9 @@ export class MatMultiSort extends MatSort { private _sortDefinitions: Sort[] = []; // Event emitted when the user changes the sort definition. - @Output('matMultiSortChange') readonly multiSortChange: EventEmitter = new EventEmitter(); + @Output('matMultiSortChange') readonly multiSortChange: EventEmitter = new EventEmitter< + Sort[] + >(); constructor( @Optional() @Inject(MAT_SORT_DEFAULT_OPTIONS) private __defaultOptions?: MatSortDefaultOptions @@ -92,7 +86,7 @@ export class MatMultiSort extends MatSort { } updateSortDefinitions(sortable: MatSortable): void { - const sortIndex = this.sortDefinitions.findIndex(sort => sort.active === sortable.id); + const sortIndex = this.sortDefinitions.findIndex((sort) => sort.active === sortable.id); if (sortIndex >= 0) { const newSort: Sort = { @@ -120,12 +114,14 @@ export class MatMultiSort extends MatSort { } // Get the sort direction cycle with the potential sortable overrides. - const disableClear = sortable?.disableClear ?? this.disableClear ?? !!this.__defaultOptions?.disableClear; + const disableClear = + sortable?.disableClear ?? this.disableClear ?? !!this.__defaultOptions?.disableClear; const sortDirectionCycle = getSortDirectionCycle(sortable.start || this.start, disableClear); - const sortIndex = this.sortDefinitions.findIndex(sort => sort.active === sortable.id); + const sortIndex = this.sortDefinitions.findIndex((sort) => sort.active === sortable.id); // Get and return the next direction in the cycle - let nextDirectionIndex = sortDirectionCycle.indexOf(this.sortDefinitions[sortIndex].direction) + 1; + let nextDirectionIndex = + sortDirectionCycle.indexOf(this.sortDefinitions[sortIndex].direction) + 1; if (nextDirectionIndex >= sortDirectionCycle.length) { nextDirectionIndex = 0; } diff --git a/projects/mat-datatable-lib/src/directives/datatable-sort/public-api.ts b/projects/mat-datatable-lib/src/directives/datatable-sort/public-api.ts index caf2212..bd7492e 100644 --- a/projects/mat-datatable-lib/src/directives/datatable-sort/public-api.ts +++ b/projects/mat-datatable-lib/src/directives/datatable-sort/public-api.ts @@ -2,11 +2,7 @@ * Public API Surface of mat-multi-sort */ -export { - Sort, - SortDirection, - SortHeaderArrowPosition -} from '@angular/material/sort'; +export { Sort, SortDirection, SortHeaderArrowPosition } from '@angular/material/sort'; export { SortDirectionAscDesc } from './mat-multi-sort.interface'; @@ -17,4 +13,3 @@ export * from './mat-multi-sort.directive'; export * from './mat-multi-sort-header.component'; // MatMultiSortModule - diff --git a/projects/mat-datatable-lib/src/directives/virtual-scroll/fixed-size-table-virtual-scroll-strategy.ts b/projects/mat-datatable-lib/src/directives/virtual-scroll/fixed-size-table-virtual-scroll-strategy.ts index 1935467..d252aad 100644 --- a/projects/mat-datatable-lib/src/directives/virtual-scroll/fixed-size-table-virtual-scroll-strategy.ts +++ b/projects/mat-datatable-lib/src/directives/virtual-scroll/fixed-size-table-virtual-scroll-strategy.ts @@ -86,16 +86,16 @@ export class FixedSizeTableVirtualScrollStrategy implements VirtualScrollStrateg if (!this.viewport || !this.rowHeight) { return; } - this.viewport.scrollToOffset((index - 1 ) * this.rowHeight + this.headerHeight, behavior); + this.viewport.scrollToOffset((index - 1) * this.rowHeight + this.headerHeight, behavior); } public setConfig(configs: TableVirtualScrollStrategyConfigs) { const { rowHeight, headerHeight, footerHeight, bufferMultiplier } = configs; if ( - this.rowHeight === rowHeight - && this.headerHeight === headerHeight - && this.footerHeight === footerHeight - && this.bufferMultiplier === bufferMultiplier + this.rowHeight === rowHeight && + this.headerHeight === headerHeight && + this.footerHeight === footerHeight && + this.bufferMultiplier === bufferMultiplier ) { return; } @@ -112,7 +112,8 @@ export class FixedSizeTableVirtualScrollStrategy implements VirtualScrollStrateg } const renderedOffset = this.viewport.getOffsetToRenderedContentStart() || 0; - const viewportSizeForRows = this.viewport.getViewportSize() - this.headerHeight - this.footerHeight; + const viewportSizeForRows = + this.viewport.getViewportSize() - this.headerHeight - this.footerHeight; const itemsDisplayed = Math.ceil(viewportSizeForRows / this.rowHeight); const bufferItems = Math.ceil(itemsDisplayed * this.bufferMultiplier); const start = Math.floor(renderedOffset / this.rowHeight); diff --git a/projects/mat-datatable-lib/src/directives/virtual-scroll/table-item-size.directive.ts b/projects/mat-datatable-lib/src/directives/virtual-scroll/table-item-size.directive.ts index 6303d7c..4cd2265 100644 --- a/projects/mat-datatable-lib/src/directives/virtual-scroll/table-item-size.directive.ts +++ b/projects/mat-datatable-lib/src/directives/virtual-scroll/table-item-size.directive.ts @@ -48,7 +48,7 @@ type SwitchDataSource = (dataSource: TableVirtualScrollDataSource) => void * @returns combined selectors */ const combineSelectors = (...pairs: string[][]): string => - pairs.map(selectors => `${selectors.join(' ')}, ${selectors.join('')}`).join(', '); + pairs.map((selectors) => `${selectors.join(' ')}, ${selectors.join('')}`).join(', '); const stickyHeaderSelector = combineSelectors( ['.mat-mdc-header-row', '.mat-mdc-table-sticky'], @@ -78,14 +78,16 @@ const defaults = { }; @Directive({ - // eslint-disable-next-line @angular-eslint/directive-selector - selector: 'cdk-virtual-scroll-viewport[tvsItemSize]', - exportAs: 'table-virtual-scroll', - standalone: true, - providers: [{ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: 'cdk-virtual-scroll-viewport[tvsItemSize]', + exportAs: 'table-virtual-scroll', + standalone: true, + providers: [ + { provide: VIRTUAL_SCROLL_STRATEGY, useFactory: () => new FixedSizeTableVirtualScrollStrategy() - }] + } + ] }) export class TableItemSizeDirective implements OnChanges, AfterContentInit, OnDestroy { @HostListener('window:keyup', ['$event']) @@ -99,11 +101,15 @@ export class TableItemSizeDirective implements OnChanges, AfterCont break; case 'PageDown': // show the 'old' last line as 'new' first line - this.scrollToIndex((this.firstVisibleIndexChanged$.value + this.visibleRowsChanged$.value - 1)); + this.scrollToIndex( + this.firstVisibleIndexChanged$.value + this.visibleRowsChanged$.value - 1 + ); break; case 'PageUp': // show the 'old' first line as 'new' last line - this.scrollToIndex(Math.max(this.firstVisibleIndexChanged$.value - this.visibleRowsChanged$.value + 1, 0)); + this.scrollToIndex( + Math.max(this.firstVisibleIndexChanged$.value - this.visibleRowsChanged$.value + 1, 0) + ); break; case 'End': this.scrollToIndex(this.scrollStrategy.dataLength - 1); @@ -186,7 +192,7 @@ export class TableItemSizeDirective implements OnChanges, AfterCont // used to avoid "ExpressionChangedAfterItHasBeenCheckedError" delay(0) ) - .subscribe(index => + .subscribe((index) => /** * In order to avoid hitting change detection for every scroll event, all of the events * emitted from the scrolledIndexChange stream will be run outside the Angular zone. @@ -197,12 +203,8 @@ export class TableItemSizeDirective implements OnChanges, AfterCont ); virtualScrollStrategy.visibleRowsChange - .pipe( - takeUntil(this.unsubscribe$) - ) - .subscribe(visibleRows => - setTimeout(() => this.visibleRowsChanged$.next(visibleRows), 0) - ); + .pipe(takeUntil(this.unsubscribe$)) + .subscribe((visibleRows) => setTimeout(() => this.visibleRowsChanged$.next(visibleRows), 0)); } ngOnDestroy() { @@ -234,16 +236,13 @@ export class TableItemSizeDirective implements OnChanges, AfterCont // Handle changes to sticky rows combineLatest([ this.scrollStrategy.stickyChange, - this.resetStickyPositions - .pipe( - startWith(void 0), - delayWhen(() => this.getScheduleObservable()), - tap(() => this.stickyPositions = null) - ) - ]) - .pipe( - takeUntil(this.unsubscribe$) + this.resetStickyPositions.pipe( + startWith(void 0), + delayWhen(() => this.getScheduleObservable()), + tap(() => (this.stickyPositions = null)) ) + ]) + .pipe(takeUntil(this.unsubscribe$)) .subscribe(([stickyOffset]) => { if (!this.stickyPositions) { this.initStickyPositions(); @@ -290,7 +289,7 @@ export class TableItemSizeDirective implements OnChanges, AfterCont * @param index - index of entry to scroll to (zero based) */ scrollToIndexCentered(index: number) { - const rowsBefore = Math.floor(Math.max(((this.visibleRowsChanged$.value + 1) / 2) - 1, 0)); + const rowsBefore = Math.floor(Math.max((this.visibleRowsChanged$.value + 1) / 2 - 1, 0)); this.scrollStrategy.scrollToIndex(index - rowsBefore, 'smooth'); } @@ -300,7 +299,9 @@ export class TableItemSizeDirective implements OnChanges, AfterCont */ private connectDataSource(dataSource: TableVirtualScrollDataSource) { if (!isTVSDataSource(dataSource)) { - throw new Error('[tvsItemSize] requires TableVirtualScrollDataSource be set as [dataSource] of the table'); + throw new Error( + '[tvsItemSize] requires TableVirtualScrollDataSource be set as [dataSource] of the table' + ); } if (!isMatTable(this.table)) { throw new Error('[tvsItemSize] requires the table to be an angular material table'); @@ -310,18 +311,19 @@ export class TableItemSizeDirective implements OnChanges, AfterCont this.dataSourceChanges$.next(); // Emit new row range, when base data changes e.g. on sorting or filtering the data - dataSource.attachVirtualScroller(this.rangeToDisplay$) + dataSource + .attachVirtualScroller(this.rangeToDisplay$) .pipe( distinctUntilChanged(), takeUntil(this.dataSourceChanges$), takeUntil(this.unsubscribe$), - tap(sizes => { + tap((sizes) => { this.scrollStrategy.dataLength = sizes.totalFilteredElements; this.totalRowsChanged$.next(sizes.totalElements); this.filteredRowsChanged$.next(sizes.totalFilteredElements); }), - switchMap(sizes => this.scrollStrategy.renderedRangeStream - .pipe( + switchMap((sizes) => + this.scrollStrategy.renderedRangeStream.pipe( // Reduce data array to range defined by virtual scroll viewport map(({ start, end }) => { const result = { @@ -339,7 +341,7 @@ export class TableItemSizeDirective implements OnChanges, AfterCont ) ) ) - .subscribe(range => { + .subscribe((range) => { // Emit range of data requested by virtual scroll viewport this.ngZone.run(() => this.rangeToDisplay$.next(range)); }); @@ -354,9 +356,8 @@ export class TableItemSizeDirective implements OnChanges, AfterCont return; } - const isEnabled = (rowDefs: CanStick[]) => rowDefs - .map(def => def.sticky) - .reduce((prevState, state) => prevState && state, true); + const isEnabled = (rowDefs: CanStick[]) => + rowDefs.map((def) => def.sticky).reduce((prevState, state) => prevState && state, true); this.stickyEnabled = { header: isEnabled(this.table['_headerRowDefs'] as CanStick[]), @@ -365,11 +366,12 @@ export class TableItemSizeDirective implements OnChanges, AfterCont } private setStickyHeader(offset: number) { - this.scrollStrategy.viewport.elementRef.nativeElement.querySelectorAll(stickyHeaderSelector) + this.scrollStrategy.viewport.elementRef.nativeElement + .querySelectorAll(stickyHeaderSelector) .forEach((el: Element) => { const parent = el.parentElement; let baseOffset = 0; - if ((this.stickyPositions !== null) && (parent !== null) && this.stickyPositions.has(parent)) { + if (this.stickyPositions !== null && parent !== null && this.stickyPositions.has(parent)) { baseOffset = this.stickyPositions.get(parent) || 0; } (el as HTMLElement).style.top = `${baseOffset - offset}px`; @@ -377,11 +379,12 @@ export class TableItemSizeDirective implements OnChanges, AfterCont } private setStickyFooter(offset: number) { - this.scrollStrategy.viewport.elementRef.nativeElement.querySelectorAll(stickyFooterSelector) + this.scrollStrategy.viewport.elementRef.nativeElement + .querySelectorAll(stickyFooterSelector) .forEach((el: Element) => { const parent = el.parentElement; let baseOffset = 0; - if ((this.stickyPositions !== null) && (parent !== null) && this.stickyPositions.has(parent)) { + if (this.stickyPositions !== null && parent !== null && this.stickyPositions.has(parent)) { baseOffset = this.stickyPositions.get(parent) || 0; } (el as HTMLElement).style.bottom = `${-baseOffset + offset}px`; @@ -393,20 +396,30 @@ export class TableItemSizeDirective implements OnChanges, AfterCont this.setStickyEnabled(); if (this.stickyEnabled.header) { - this.scrollStrategy.viewport.elementRef.nativeElement.querySelectorAll(stickyHeaderSelector) + this.scrollStrategy.viewport.elementRef.nativeElement + .querySelectorAll(stickyHeaderSelector) .forEach((el: Element) => { const parent = el.parentElement; - if ((this.stickyPositions !== null) && (parent !== null) && !this.stickyPositions.has(parent)) { + if ( + this.stickyPositions !== null && + parent !== null && + !this.stickyPositions.has(parent) + ) { this.stickyPositions.set(parent, parent.offsetTop); } }); } if (this.stickyEnabled.footer) { - this.scrollStrategy.viewport.elementRef.nativeElement.querySelectorAll(stickyFooterSelector) + this.scrollStrategy.viewport.elementRef.nativeElement + .querySelectorAll(stickyFooterSelector) .forEach((el: Element) => { const parent = el.parentElement; - if ((this.stickyPositions !== null) && (parent !== null) && !this.stickyPositions.has(parent)) { + if ( + this.stickyPositions !== null && + parent !== null && + !this.stickyPositions.has(parent) + ) { this.stickyPositions.set(parent, -parent.offsetTop); } }); diff --git a/projects/mat-datatable-lib/src/interfaces/datastore-provider.interface.ts b/projects/mat-datatable-lib/src/interfaces/datastore-provider.interface.ts index ae8eb2a..47e2014 100644 --- a/projects/mat-datatable-lib/src/interfaces/datastore-provider.interface.ts +++ b/projects/mat-datatable-lib/src/interfaces/datastore-provider.interface.ts @@ -2,50 +2,54 @@ import { Observable } from 'rxjs'; import { SortDirectionAscDesc } from '../directives/datatable-sort/mat-multi-sort.interface'; -type UnionKeys = T extends T? keyof T : never; -type StrictUnionHelper = T extends T? T & Partial, keyof T>, undefined>> : never; -type StrictUnion = StrictUnionHelper +type UnionKeys = T extends T ? keyof T : never; +type StrictUnionHelper = T extends T + ? T & Partial, keyof T>, undefined>> + : never; +type StrictUnion = StrictUnionHelper; /** * Type defining the sorting a column. * @template T - type defining the data of a table row */ export type FieldSortDefinition = { - fieldName: keyof T - sortDirection: SortDirectionAscDesc -} + fieldName: keyof T; + sortDirection: SortDirectionAscDesc; +}; /** * Definition of a simple string filter for a table column. * @template T - type defining the data of a table row */ export type FieldFilterDefinitionSimple = { - fieldName: keyof T - value: string | number | Date -} + fieldName: keyof T; + value: string | number | Date; +}; /** * Definition of a range filter for a table column. * @template T - type defining the data of a table row */ export type FieldFilterDefinitionRange = { - fieldName: keyof T - valueFrom: string | number | Date - valueTo: string | number | Date -} + fieldName: keyof T; + valueFrom: string | number | Date; + valueTo: string | number | Date; +}; /** * Definition of a filter for a table column. * @template T - type defining the data of a table row */ -export type FieldFilterDefinition = StrictUnion<(FieldFilterDefinitionSimple | FieldFilterDefinitionRange)> +export type FieldFilterDefinition = StrictUnion< + FieldFilterDefinitionSimple | FieldFilterDefinitionRange +>; /** * Interface defining the properties of a requests for a range of rows. */ export interface RequestRowsRange { - startRowIndex: number - numberOfRows: number + startRowIndex: number; + numberOfRows: number; } /** @@ -53,11 +57,11 @@ export interface RequestRowsRange { * @template T - type defining the data of a table row */ export interface Page { - content: T[] - startRowIndex: number - returnedElements: number - totalElements: number - totalFilteredElements: number + content: T[]; + startRowIndex: number; + returnedElements: number; + totalElements: number; + totalFilteredElements: number; } /** @@ -76,7 +80,7 @@ export interface DataStoreProvider { rowsRange: RequestRowsRange, sorts?: FieldSortDefinition[], filters?: FieldFilterDefinition[] - ) => Observable> + ) => Observable>; /** * Get the relative index of a row in the datastore (0..n) respecting @@ -90,5 +94,5 @@ export interface DataStoreProvider { row: T, sorts?: FieldSortDefinition[], filters?: FieldFilterDefinition[] - ) => Observable + ) => Observable; } diff --git a/projects/mat-datatable-lib/src/interfaces/datatable-column-definition.interface.ts b/projects/mat-datatable-lib/src/interfaces/datatable-column-definition.interface.ts index 247c25b..d4d92cb 100644 --- a/projects/mat-datatable-lib/src/interfaces/datatable-column-definition.interface.ts +++ b/projects/mat-datatable-lib/src/interfaces/datatable-column-definition.interface.ts @@ -1,20 +1,59 @@ +import { Pipe, PipeTransform } from '@angular/core'; + export type ColumnAlignmentType = 'left' | 'center' | 'right'; /** * Interface for the definition of a single table column * @template TRowData - type / interface definition for data of a single row */ +export type ColumnDisplayType = 'string' | 'mailtoLink' | 'image'; +// Using CellValueBase as base type to enforce discriminated field +export interface CellValueBase { + type: ColumnDisplayType; +} +export interface CellValueString extends CellValueBase { + type: 'string'; + text: string; +} +export interface CellValueMailTo extends CellValueBase { + type: 'mailtoLink'; + url: string; + text: string; +} +export interface CellValueImage extends CellValueBase { + type: 'image'; + url: string; + altText: string; + title?: string; +} + +export type CellContentValue = CellValueString | CellValueMailTo | CellValueImage; +export type CellContentDefType = (element: TRowData) => CellContentValue; + +// TODO can be deleted for Angular > 17, when using @let + @switch +@Pipe({ + name: 'cast', + standalone: true, + pure: true +}) +export class CastPipe implements PipeTransform { + // Uses a generic type to coerce the type checking system + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars + transform(value: any, typeToken?: T): T { + return value as T; + } +} + export interface MatColumnDefinition { columnId: string; sortable?: boolean; resizable?: boolean; header: string; headerAlignment?: ColumnAlignmentType; - cell: (element: TRowData) => string; + cell: CellContentDefType; // type of cell with values for content cellAlignment?: ColumnAlignmentType; width?: string; // e.g. '8em' - tooltip?: ((element: TRowData) => string); - showAsMailtoLink?: boolean; + tooltip?: (element: TRowData) => string; showAsSingleLine?: boolean; footer?: string; footerAlignment?: ColumnAlignmentType; diff --git a/projects/mat-datatable-lib/src/lib/_mat-datatable-table-theme.scss b/projects/mat-datatable-lib/src/lib/_mat-datatable-table-theme.scss index 9b84233..111ea4b 100644 --- a/projects/mat-datatable-lib/src/lib/_mat-datatable-table-theme.scss +++ b/projects/mat-datatable-lib/src/lib/_mat-datatable-table-theme.scss @@ -1,11 +1,11 @@ /* stylelint-disable block-no-empty */ -@use "sass:map"; -@use "@angular/material" as mat; +@use 'sass:map'; +@use '@angular/material' as mat; @mixin color($config-or-theme) { $config: mat.get-color-config($config-or-theme); - $primary-palette: map.get($config, "primary"); + $primary-palette: map.get($config, 'primary'); $foreground: map.get($config, foreground); $background: map.get($config, background); @@ -44,9 +44,11 @@ } } -@mixin typography($config-or-theme) {} +@mixin typography($config-or-theme) { +} -@mixin density($config-or-theme) {} +@mixin density($config-or-theme) { +} @mixin theme($theme) { @include mat.private-check-duplicate-theme-styles($theme, 'mat-datatable') { diff --git a/projects/mat-datatable-lib/src/lib/mat-datatable.component.html b/projects/mat-datatable-lib/src/lib/mat-datatable.component.html index 4480be5..bfce3f7 100644 --- a/projects/mat-datatable-lib/src/lib/mat-datatable.component.html +++ b/projects/mat-datatable-lib/src/lib/mat-datatable.component.html @@ -1,21 +1,27 @@ + mode="indeterminate" +> - +
+ (matMultiSortChange)="onMultiSortChanged($event)" + > - - - - - + (click)="onRowClicked(row)" + > - +
- {{column.header}} + [matResizable]="column.resizable" + > + {{ column.header }} - {{column.cell(row)}} - {{column.cell(row)}} + + {{ + (column.cell(row) | cast: StringCell).text + }} + {{ (column.cell(row) | cast: MailToCell).text }} + +
diff --git a/projects/mat-datatable-lib/src/lib/mat-datatable.component.spec.ts b/projects/mat-datatable-lib/src/lib/mat-datatable.component.spec.ts index 12560d3..dcc4b0d 100644 --- a/projects/mat-datatable-lib/src/lib/mat-datatable.component.spec.ts +++ b/projects/mat-datatable-lib/src/lib/mat-datatable.component.spec.ts @@ -5,8 +5,19 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { provideNoopAnimations } from '@angular/platform-browser/animations'; import { BehaviorSubject, first, Observable, of } from 'rxjs'; -import { MatDatatableHarness, MatHeaderRowHarness, MatFooterRowHarness, MatMultiSortHarness } from '../../testing/src'; -import { Page, RequestRowsRange, FieldSortDefinition, FieldFilterDefinition, DataStoreProvider } from '../interfaces/datastore-provider.interface'; +import { + MatDatatableHarness, + MatHeaderRowHarness, + MatFooterRowHarness, + MatMultiSortHarness +} from '../../testing/src'; +import { + Page, + RequestRowsRange, + FieldSortDefinition, + FieldFilterDefinition, + DataStoreProvider +} from '../interfaces/datastore-provider.interface'; import { MatColumnDefinition } from '../interfaces/datatable-column-definition.interface'; import { MatSortDefinition } from '../interfaces/datatable-sort-definition.interface'; @@ -18,7 +29,7 @@ describe('MatDatatableComponent', () => { let fixture: ComponentFixture; let loader: HarnessLoader; let component: DatatableTestComponent; - const singlePageDataAsStrings = SINGLE_PAGE_DATA.map(entry => { + const singlePageDataAsStrings = SINGLE_PAGE_DATA.map((entry) => { const result: string[] = []; result.push(entry.position.toString()); result.push(entry.name); @@ -29,13 +40,8 @@ describe('MatDatatableComponent', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - DatatableEmptyTestComponent, - DatatableTestComponent - ], - providers: [ - provideNoopAnimations() - ] + imports: [DatatableEmptyTestComponent, DatatableTestComponent], + providers: [provideNoopAnimations()] }).compileComponents(); })); @@ -103,7 +109,7 @@ describe('MatDatatableComponent', () => { let fixture: ComponentFixture; let loader: HarnessLoader; let component: DatatableTestComponent; - const singlePageDataAsStrings = SINGLE_PAGE_DATA.map(entry => { + const singlePageDataAsStrings = SINGLE_PAGE_DATA.map((entry) => { const result: string[] = []; result.push(entry.position.toString()); result.push(entry.name); @@ -114,13 +120,8 @@ describe('MatDatatableComponent', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - DatatableEmptyTestComponent, - DatatableTestComponent - ], - providers: [ - provideNoopAnimations() - ] + imports: [DatatableEmptyTestComponent, DatatableTestComponent], + providers: [provideNoopAnimations()] }).compileComponents(); })); @@ -202,7 +203,7 @@ describe('MatDatatableComponent', () => { ['6', 'Carbon', '12.0107', 'C'], ['9', 'Fluorine', '18.9984', 'F'], ['3', 'Helium', '4.0026', 'He'], - ['2', 'Helium', '6.1234','He'], + ['2', 'Helium', '6.1234', 'He'], ['1', 'Hydrogen', '1.0079', 'H'], ['4', 'Lithium', '6.941', 'Li'], ['10', 'Neon', '20.1797', 'Ne'], @@ -248,7 +249,7 @@ describe('MatDatatableComponent', () => { ['4', 'Lithium', '6.941', 'Li'], ['1', 'Hydrogen', '1.0079', 'H'], ['3', 'Helium', '4.0026', 'He'], - ['2', 'Helium', '6.1234','He'], + ['2', 'Helium', '6.1234', 'He'], ['9', 'Fluorine', '18.9984', 'F'], ['6', 'Carbon', '12.0107', 'C'], ['5', 'Beryllium', '9.0122', 'Be'] @@ -286,7 +287,7 @@ describe('MatDatatableComponent', () => { ['4', 'Lithium', '6.941', 'Li'], ['1', 'Hydrogen', '1.0079', 'H'], ['3', 'Helium', '4.0026', 'He'], - ['2', 'Helium', '6.1234','He'], + ['2', 'Helium', '6.1234', 'He'], ['9', 'Fluorine', '18.9984', 'F'], ['6', 'Carbon', '12.0107', 'C'], ['5', 'Beryllium', '9.0122', 'Be'] @@ -329,10 +330,8 @@ describe('MatDatatableComponent', () => { it('should emit new sort order on change', () => { let currentSortDefinitions: MatSortDefinition[] | undefined; component.matDataTable.sortChange - .pipe( - first() - ) - .subscribe((value: MatSortDefinition[]) => currentSortDefinitions = value); + .pipe(first()) + .subscribe((value: MatSortDefinition[]) => (currentSortDefinitions = value)); // change sort definitions const newSortDefinitions: MatSortDefinition[] = [ @@ -350,7 +349,7 @@ describe('MatDatatableComponent', () => { let fixture: ComponentFixture; let loader: HarnessLoader; let component: DatatableTestComponent; - const singlePageDataAsStrings = SINGLE_PAGE_DATA.map(entry => { + const singlePageDataAsStrings = SINGLE_PAGE_DATA.map((entry) => { const result: string[] = []; result.push(entry.position.toString()); result.push(entry.name); @@ -361,13 +360,8 @@ describe('MatDatatableComponent', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - DatatableEmptyTestComponent, - DatatableTestComponent - ], - providers: [ - provideNoopAnimations() - ] + imports: [DatatableEmptyTestComponent, DatatableTestComponent], + providers: [provideNoopAnimations()] }).compileComponents(); })); @@ -378,11 +372,13 @@ describe('MatDatatableComponent', () => { loader = TestbedHarnessEnvironment.loader(fixture); }); - it('should filter a table for a single string value', async() => { - const currentFilter = { fieldName:'name', value:'Helium' }; + it('should filter a table for a single string value', async () => { + const currentFilter = { fieldName: 'name', value: 'Helium' }; component.currentFilters = [currentFilter] as FieldFilterDefinition[]; fixture.detectChanges(); - const expectedRows = singlePageDataAsStrings.filter(element => element[1] === currentFilter.value); + const expectedRows = singlePageDataAsStrings.filter( + (element) => element[1] === currentFilter.value + ); const table = await loader.getHarness(MatDatatableHarness); const rows = await table.getRows(); @@ -392,12 +388,15 @@ describe('MatDatatableComponent', () => { expect(rowContent).toEqual(expectedRows); }); - it('should filter a table for a range of numeric values', async() => { - const currentFilter = { fieldName:'position', valueFrom:5, valueTo:7 }; + it('should filter a table for a range of numeric values', async () => { + const currentFilter = { fieldName: 'position', valueFrom: 5, valueTo: 7 }; component.currentFilters = [currentFilter] as FieldFilterDefinition[]; fixture.detectChanges(); - const expectedRows = singlePageDataAsStrings.filter(element => (parseInt(element[0]) >= currentFilter.valueFrom) && - (parseInt(element[0]) <= currentFilter.valueTo)); + const expectedRows = singlePageDataAsStrings.filter( + (element) => + parseInt(element[0]) >= currentFilter.valueFrom && + parseInt(element[0]) <= currentFilter.valueTo + ); const table = await loader.getHarness(MatDatatableHarness); const rows = await table.getRows(); @@ -414,13 +413,8 @@ describe('MatDatatableComponent', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - DatatableEmptyTestComponent, - DatatableTestComponent - ], - providers: [ - provideNoopAnimations() - ] + imports: [DatatableEmptyTestComponent, DatatableTestComponent], + providers: [provideNoopAnimations()] }).compileComponents(); })); @@ -447,13 +441,8 @@ describe('MatDatatableComponent', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - DatatableEmptyTestComponent, - DatatableTestComponent - ], - providers: [ - provideNoopAnimations() - ] + imports: [DatatableEmptyTestComponent, DatatableTestComponent], + providers: [provideNoopAnimations()] }).compileComponents(); })); @@ -583,13 +572,8 @@ describe('MatDatatableComponent', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - DatatableEmptyTestComponent, - DatatableTestComponent - ], - providers: [ - provideNoopAnimations() - ] + imports: [DatatableEmptyTestComponent, DatatableTestComponent], + providers: [provideNoopAnimations()] }).compileComponents(); })); @@ -622,13 +606,8 @@ describe('MatDatatableComponent', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - DatatableEmptyTestComponent, - DatatableTestComponent - ], - providers: [ - provideNoopAnimations() - ] + imports: [DatatableEmptyTestComponent, DatatableTestComponent], + providers: [provideNoopAnimations()] }).compileComponents(); })); @@ -672,12 +651,8 @@ describe('MatDatatableComponent', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - DatatableDoubleTestComponent - ], - providers: [ - provideNoopAnimations() - ] + imports: [DatatableDoubleTestComponent], + providers: [provideNoopAnimations()] }).compileComponents(); })); @@ -724,13 +699,8 @@ describe('MatDatatableComponent', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - DatatableEmptyTestComponent, - DatatableTestComponent - ], - providers: [ - provideNoopAnimations() - ] + imports: [DatatableEmptyTestComponent, DatatableTestComponent], + providers: [provideNoopAnimations()] }).compileComponents(); })); @@ -836,7 +806,11 @@ class StaticTableDataStore implements DataStoreProvider { filters?: FieldFilterDefinition[] ): Observable { const selectedDataset = this.getRawDataSortedFiltered(sorts, filters); - return of(selectedDataset.findIndex(currentRow => this.trackBy(0, row) === this.trackBy(0, currentRow))); + return of( + selectedDataset.findIndex( + (currentRow) => this.trackBy(0, row) === this.trackBy(0, currentRow) + ) + ); } getRow(rowIndex: number, fromSortedRows = false) { @@ -858,16 +832,15 @@ class StaticTableDataStore implements DataStoreProvider { let selectedDataset = structuredClone(this.unsortedData); // Filter data - if ((filters !== undefined) && Array.isArray(filters) && (filters.length > 0)) { + if (filters !== undefined && Array.isArray(filters) && filters.length > 0) { selectedDataset = selectedDataset.filter((row: TRow) => { return filters.reduce((isSelected: boolean, currentFilter: FieldFilterDefinition) => { if (currentFilter.value !== undefined) { isSelected ||= row[currentFilter.fieldName] === currentFilter.value; - } else if ((currentFilter.valueFrom !== undefined) && (currentFilter.valueTo !== undefined)) { - isSelected ||= ( - (row[currentFilter.fieldName] >= currentFilter.valueFrom) && - (row[currentFilter.fieldName] <= currentFilter.valueTo) - ); + } else if (currentFilter.valueFrom !== undefined && currentFilter.valueTo !== undefined) { + isSelected ||= + row[currentFilter.fieldName] >= currentFilter.valueFrom && + row[currentFilter.fieldName] <= currentFilter.valueTo; } return isSelected; }, false); @@ -875,9 +848,9 @@ class StaticTableDataStore implements DataStoreProvider { } // Sort data - only the first entry of the definitions is used - if ((sorts !== undefined) && Array.isArray(sorts)) { + if (sorts !== undefined && Array.isArray(sorts)) { this.currentSortingDefinitions = sorts; - if ((sorts.length > 0)) { + if (sorts.length > 0) { selectedDataset.sort(this.compareFn); } } @@ -908,7 +881,7 @@ class StaticTableDataStore implements DataStoreProvider { let result = 0; for (let i = 0; i < this.currentSortingDefinitions.length; i++) { const fieldName = this.currentSortingDefinitions[i].fieldName; - const isAsc = (this.currentSortingDefinitions[i].sortDirection === 'asc'); + const isAsc = this.currentSortingDefinitions[i].sortDirection === 'asc'; const valueA = a[fieldName] as string | number; const valueB = b[fieldName] as string | number; result = this.compare(valueA, valueB, isAsc); @@ -927,7 +900,7 @@ class StaticTableDataStore implements DataStoreProvider { * @returns comparison result (0:a===b; -1:ab) */ private compare(a: string | number, b: string | number, isAsc: boolean): number { - return (a === b ? 0 : (a < b ? -1 : 1)) * (isAsc ? 1 : -1); + return (a === b ? 0 : a < b ? -1 : 1) * (isAsc ? 1 : -1); } } @@ -936,13 +909,15 @@ type DatatableTestRow = { name: string; weight: number; symbol: string; -} +}; const datatableTestColumnDefinitions: MatColumnDefinition[] = [ { columnId: 'position', header: 'No.', - cell: (row: DatatableTestRow) => row.position.toString(), + cell: (row: DatatableTestRow) => { + return { type: 'string', text: row?.position.toString() }; + }, headerAlignment: 'right', cellAlignment: 'right', width: '5em', @@ -952,7 +927,9 @@ const datatableTestColumnDefinitions: MatColumnDefinition[] = { columnId: 'name', header: 'Name', - cell: (row: DatatableTestRow) => row.name, + cell: (row: DatatableTestRow) => { + return { type: 'string', text: row.name }; + }, headerAlignment: 'left', cellAlignment: 'left', width: '20em', @@ -965,7 +942,9 @@ const datatableTestColumnDefinitions: MatColumnDefinition[] = { columnId: 'weight', header: 'Weight', - cell: (row: DatatableTestRow) => row.weight.toString(), + cell: (row: DatatableTestRow) => { + return { type: 'string', text: row.weight.toString() }; + }, headerAlignment: 'right', cellAlignment: 'right', width: '10em', @@ -975,11 +954,12 @@ const datatableTestColumnDefinitions: MatColumnDefinition[] = { columnId: 'symbol', header: 'Symbol', - cell: (row: DatatableTestRow) => row.symbol, + cell: (row: DatatableTestRow) => { + return { type: 'mailtoLink', text: row.symbol, url: `mailto:${row.symbol}` }; + }, headerAlignment: 'center', cellAlignment: 'center', tooltip: (row: DatatableTestRow) => `Hint: ${row.symbol}`, - showAsMailtoLink: true, footer: 'f4' } ]; @@ -988,27 +968,26 @@ const datatableTestData: DatatableTestRow[] = SINGLE_PAGE_DATA; @Component({ template: ` -
- - No data to display. - -
+
+ + No data to display. + +
`, styles: ['.content-table { height: 400px; }'], standalone: true, - imports: [ - MatDatatableComponent - ] + imports: [MatDatatableComponent] }) class DatatableTestComponent { @ViewChild('testTable') matDataTable!: MatDatatableComponent; @@ -1022,10 +1001,10 @@ class DatatableTestComponent { protected readonly currentSorts$ = new BehaviorSubject([]); protected currentSelectionMode: RowSelectionType = 'none'; - public get currentFilters() : FieldFilterDefinition[] { + public get currentFilters(): FieldFilterDefinition[] { return this.matDataTable.filterDefinitions; } - public set currentFilters(newFilters : FieldFilterDefinition[]) { + public set currentFilters(newFilters: FieldFilterDefinition[]) { if (newFilters && Array.isArray(newFilters)) { this.matDataTable.filterDefinitions = newFilters; } @@ -1055,10 +1034,11 @@ class DatatableTestComponent { protected onRowSelectionChange($event: DatatableTestRow[]) { let result = '-'; if ($event.length > 0) { - result = $event - .sort((a: DatatableTestRow, b: DatatableTestRow) => a.position - b.position ) - .map(row => row.name) - .join('; ') || '-'; + result = + $event + .sort((a: DatatableTestRow, b: DatatableTestRow) => a.position - b.position) + .map((row) => row.name) + .join('; ') || '-'; } this.selectedRowsAsString = result; } @@ -1066,26 +1046,24 @@ class DatatableTestComponent { @Component({ template: ` -
- - No data to display. - -
+
+ + No data to display. + +
`, styles: ['.content-table { height: 400px; }'], standalone: true, - imports: [ - MatDatatableComponent - ] + imports: [MatDatatableComponent] }) class DatatableEmptyTestComponent { dataStore = new StaticTableDataStore([] as DatatableTestRow[], this.trackBy); @@ -1112,41 +1090,47 @@ class DatatableEmptyTestComponent { @Component({ template: ` -
- - No data to display. - - - No data to display. - -
+
+ + No data to display. + + + No data to display. + +
`, styles: ['.content-table { height: 400px; }'], standalone: true, - imports: [ - MatDatatableComponent - ] + imports: [MatDatatableComponent] }) class DatatableDoubleTestComponent { @ViewChild('testTable1') matDataTable1!: MatDatatableComponent; @ViewChild('testTable2') matDataTable2!: MatDatatableComponent; - dataStore1 = new StaticTableDataStore(structuredClone(datatableTestData), this.trackBy); - dataStore2 = new StaticTableDataStore(structuredClone(datatableTestData), this.trackBy); + dataStore1 = new StaticTableDataStore( + structuredClone(datatableTestData), + this.trackBy + ); + dataStore2 = new StaticTableDataStore( + structuredClone(datatableTestData), + this.trackBy + ); protected columnDefinitions = datatableTestColumnDefinitions; protected displayedColumns: string[] = ['position', 'name', 'weight', 'symbol']; protected currentSorts1: MatSortDefinition[] = []; diff --git a/projects/mat-datatable-lib/src/lib/mat-datatable.component.ts b/projects/mat-datatable-lib/src/lib/mat-datatable.component.ts index 2903a78..4388668 100644 --- a/projects/mat-datatable-lib/src/lib/mat-datatable.component.ts +++ b/projects/mat-datatable-lib/src/lib/mat-datatable.component.ts @@ -32,18 +32,28 @@ import { } from '@angular/material/table'; import { Observable, Subject, takeUntil } from 'rxjs'; -import { EmptyDataStoreProvider, TableVirtualScrollDataSource } from '../components/data-source.class'; +import { + EmptyDataStoreProvider, + TableVirtualScrollDataSource +} from '../components/data-source.class'; import { MatDatatableHeaderAlignDirective } from '../directives/datatable-header-align.directive'; import { MatDatatableResizableDirective } from '../directives/datatable-resizable.directive'; -import { - MatMultiSort, - Sort, - SortHeaderArrowPosition -} from '../directives/datatable-sort'; +import { MatMultiSort, Sort, SortHeaderArrowPosition } from '../directives/datatable-sort'; import { MatMultiSortHeader } from '../directives/datatable-sort/mat-multi-sort-header.component'; import { TableItemSizeDirective } from '../directives/virtual-scroll/table-item-size.directive'; -import { FieldSortDefinition, FieldFilterDefinition, DataStoreProvider } from '../interfaces/datastore-provider.interface'; -import { ColumnAlignmentType, MatColumnDefinition } from '../interfaces/datatable-column-definition.interface'; +import { + FieldSortDefinition, + FieldFilterDefinition, + DataStoreProvider +} from '../interfaces/datastore-provider.interface'; +import { + ColumnAlignmentType, + MatColumnDefinition, + CastPipe, + CellValueString, + CellValueImage, + CellValueMailTo +} from '../interfaces/datatable-column-definition.interface'; import { MatSortDefinition } from '../interfaces/datatable-sort-definition.interface'; export type RowSelectionType = 'none' | 'single' | 'multi'; @@ -55,47 +65,47 @@ export type RowSelectionType = 'none' | 'single' | 'multi'; * @template TRowData - type / interface definition for data of a single row */ @Component({ - selector: 'mat-datatable', - templateUrl: './mat-datatable.component.html', - styleUrls: [ - './mat-datatable.component.scss', - '../directives/datatable-resizable.directive.scss' - ], - standalone: true, - imports: [ - AsyncPipe, - CdkVirtualScrollViewport, - MatCell, - MatCellDef, - MatColumnDef, - MatDatatableHeaderAlignDirective, - MatDatatableResizableDirective, - MatFooterCell, - MatFooterCellDef, - MatFooterRow, - MatFooterRowDef, - MatHeaderCell, - MatHeaderCellDef, - MatHeaderRow, - MatHeaderRowDef, - MatMultiSort, - MatMultiSortHeader, - MatProgressBar, - MatRow, - MatRowDef, - MatTable, - NgClass, - NgFor, - NgIf, - NgStyle, - TableItemSizeDirective - ] + selector: 'mat-datatable', + templateUrl: './mat-datatable.component.html', + styleUrls: ['./mat-datatable.component.scss', '../directives/datatable-resizable.directive.scss'], + standalone: true, + imports: [ + AsyncPipe, + CdkVirtualScrollViewport, + MatCell, + MatCellDef, + MatColumnDef, + MatDatatableHeaderAlignDirective, + MatDatatableResizableDirective, + MatFooterCell, + MatFooterCellDef, + MatFooterRow, + MatFooterRowDef, + MatHeaderCell, + MatHeaderCellDef, + MatHeaderRow, + MatHeaderRowDef, + MatMultiSort, + MatMultiSortHeader, + MatProgressBar, + MatRow, + MatRowDef, + MatTable, + NgClass, + NgFor, + NgIf, + NgStyle, + TableItemSizeDirective, + CastPipe + ] }) -export class MatDatatableComponent implements AfterViewInit, OnChanges, OnDestroy, OnInit { +export class MatDatatableComponent + implements AfterViewInit, OnChanges, OnDestroy, OnInit +{ @Input() columnDefinitions: MatColumnDefinition[] = []; @Input() displayedColumns: string[] = []; @Input() rowSelectionMode: RowSelectionType = 'none'; - @Input() dataStoreProvider: DataStoreProvider = new EmptyDataStoreProvider; + @Input() dataStoreProvider: DataStoreProvider = new EmptyDataStoreProvider(); @Input() get trackBy(): TrackByFunction { return this._trackByFn; @@ -122,6 +132,11 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges @ViewChild(CdkVirtualScrollViewport) protected viewport!: CdkVirtualScrollViewport; @ViewChild(MatMultiSort) protected sort!: MatMultiSort; + // publish the CellValueXXX interfaces for the html template + protected StringCell!: CellValueString; + protected MailToCell!: CellValueMailTo; + protected ImageCell!: CellValueImage; + protected displayedFooterColumns: string[] = []; protected dataSource!: TableVirtualScrollDataSource; protected currentActivatedRow: TRowData | undefined; @@ -138,16 +153,12 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges // Set DataStoreProvider to value defined in parent element this.dataSource.dataStoreProvider = this.dataStoreProvider; - this.sort.multiSortChange - .pipe( - takeUntil(this.unsubscribe$) - ) - .subscribe((newSort: Sort[]) => { - this.dataSource.sorts = this.requestSortDefinitionFromSortArray(newSort); + this.sort.multiSortChange.pipe(takeUntil(this.unsubscribe$)).subscribe((newSort: Sort[]) => { + this.dataSource.sorts = this.requestSortDefinitionFromSortArray(newSort); - // Scroll to start of list - this.viewport.scrollToIndex(0); - }); + // Scroll to start of list + this.viewport.scrollToIndex(0); + }); this.firstVisibleIndexChanged = this.tvs.firstVisibleIndexChanged; this.totalRowsChanged = this.tvs.totalRowsChanged; @@ -163,14 +174,15 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges ngOnChanges(changes: SimpleChanges): void { const displayedColumnsChanges = changes['displayedColumns']; - if (this.withFooter && (displayedColumnsChanges !== undefined)) { + if (this.withFooter && displayedColumnsChanges !== undefined) { if (Array.isArray(displayedColumnsChanges.currentValue)) { const displayedColumns = displayedColumnsChanges.currentValue as string[]; this.displayedFooterColumns = this.footerColumnsFromColumns(displayedColumns); } else { - console.error(`'displayedColumns' is not an array of strings ('${typeof displayedColumnsChanges.currentValue}')`); + console.error( + `'displayedColumns' is not an array of strings ('${typeof displayedColumnsChanges.currentValue}')` + ); } - } } @@ -201,12 +213,12 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges this.currentSelectedRows = newSelection; break; case 'single': - this.currentSelectedRows = [ newSelection[0] ]; + this.currentSelectedRows = [newSelection[0]]; break; - } - this.onRowSelectionChanged(); + } + this.onRowSelectionChanged(); } else { - throw new Error('\'newSelection\' must be an array.'); + throw new Error("'newSelection' must be an array."); } } @@ -225,10 +237,10 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges this.sort.sortDefinitions = sortables; } - get filterDefinitions() : FieldFilterDefinition[] { + get filterDefinitions(): FieldFilterDefinition[] { return this.dataSource.filters; } - set filterDefinitions(newFilters : FieldFilterDefinition[]) { + set filterDefinitions(newFilters: FieldFilterDefinition[]) { if (newFilters && Array.isArray(newFilters)) { this.dataSource.filters = newFilters; } @@ -236,11 +248,10 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges scrollToRow(row: TRowData) { if (row !== undefined) { - this.dataSource.rowToIndex(row) - .pipe( - takeUntil(this.unsubscribe$) - ) - .subscribe(index => { + this.dataSource + .rowToIndex(row) + .pipe(takeUntil(this.unsubscribe$)) + .subscribe((index) => { const indexOfRow = Math.max(index, 0); this.viewport.scrollToIndex(indexOfRow); }); @@ -257,11 +268,13 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges * @param columnDefinition - object defining a table cell * @returns object with properties that reflect the css styles of the header cell */ - protected headerFormat(columnDefinition: MatColumnDefinition): Record | undefined { + protected headerFormat( + columnDefinition: MatColumnDefinition + ): Record | undefined { let result: Record | undefined; if (columnDefinition.width !== undefined) { result = { - 'width': columnDefinition.width + width: columnDefinition.width }; } @@ -274,13 +287,15 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges * @param columnDefinition - object defining a table cell * @returns object with properties that reflect the css styles of the data cell */ - protected columnFormat(columnDefinition: MatColumnDefinition): Record | undefined { + protected columnFormat( + columnDefinition: MatColumnDefinition + ): Record | undefined { let result: Record | undefined; if (columnDefinition.width !== undefined) { result = { 'text-overflow': 'ellipsis', 'white-space': 'nowrap', - 'overflow':'hidden' + overflow: 'hidden' }; } @@ -288,7 +303,7 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges const singleLineResult = { 'text-overflow': 'ellipsis', 'white-space': 'nowrap', - 'overflow':'hidden' + overflow: 'hidden' }; result = Object.assign({}, result, singleLineResult); } @@ -318,7 +333,9 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges * @param footerCellAlignment - definition of the footer cell * @returns object with properties that reflect the css styles of the footer cell */ - protected footerColumnFormat(footerCellAlignment?: ColumnAlignmentType): Record | undefined { + protected footerColumnFormat( + footerCellAlignment?: ColumnAlignmentType + ): Record | undefined { const result: Record = {}; if (footerCellAlignment !== undefined) { switch (footerCellAlignment) { @@ -337,7 +354,9 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges return result; } - protected sortArrowPosition(columnDefinition: MatColumnDefinition): SortHeaderArrowPosition { + protected sortArrowPosition( + columnDefinition: MatColumnDefinition + ): SortHeaderArrowPosition { return columnDefinition.headerAlignment === 'right' ? 'before' : 'after'; } @@ -346,7 +365,7 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges if (!this.currentSelectedRows.includes(row)) { switch (this.rowSelectionMode) { case 'single': - this.currentSelectedRows = [ row ]; + this.currentSelectedRows = [row]; break; case 'multi': this.currentSelectedRows.push(row); @@ -357,10 +376,10 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges } else { switch (this.rowSelectionMode) { case 'single': - this.currentSelectedRows = [ ]; + this.currentSelectedRows = []; break; case 'multi': - this.currentSelectedRows = this.currentSelectedRows.filter(e => e !== row); + this.currentSelectedRows = this.currentSelectedRows.filter((e) => e !== row); break; } this.rowClick.emit(row); @@ -395,7 +414,7 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges * @returns true = given row is part of the selectedRows */ protected selectedRowsIncludes(row: TRowData): boolean { - return this.selectedRows.some(selectedRow => this.areRowsEqual(row, selectedRow)); + return this.selectedRows.some((selectedRow) => this.areRowsEqual(row, selectedRow)); } /** @@ -413,10 +432,12 @@ export class MatDatatableComponent implements AfterViewInit, OnChanges displayedFooterColumns.push(newDisplayedColumns[i]); // Do we have a footer columnSpan for this column? - const columnDefinition = this.columnDefinitions.find(element => element.columnId === newDisplayedColumns[i]); + const columnDefinition = this.columnDefinitions.find( + (element) => element.columnId === newDisplayedColumns[i] + ); if (columnDefinition !== undefined) { const colSpan = columnDefinition.footerColumnSpan; - if ((colSpan !== undefined) && Number.isInteger(colSpan) && (colSpan > 1)) { + if (colSpan !== undefined && Number.isInteger(colSpan) && colSpan > 1) { i += colSpan; } else { i++; diff --git a/projects/mat-datatable-lib/src/public-api.ts b/projects/mat-datatable-lib/src/public-api.ts index a378ba8..888993c 100644 --- a/projects/mat-datatable-lib/src/public-api.ts +++ b/projects/mat-datatable-lib/src/public-api.ts @@ -5,7 +5,11 @@ // MatDatatableComponent export * from './lib/mat-datatable.component'; -export { MatMultiSort, MatMultiSortHeader, SortDirectionAscDesc } from './directives/datatable-sort'; +export { + MatMultiSort, + MatMultiSortHeader, + SortDirectionAscDesc +} from './directives/datatable-sort'; export { Sort } from '@angular/material/sort'; // MatColumnDefinition diff --git a/projects/mat-datatable-lib/src/theming/index.scss b/projects/mat-datatable-lib/src/theming/index.scss index 9513766..09739ce 100644 --- a/projects/mat-datatable-lib/src/theming/index.scss +++ b/projects/mat-datatable-lib/src/theming/index.scss @@ -1,5 +1,5 @@ -@use "sass:map"; -@use "@angular/material" as mat; +@use 'sass:map'; +@use '@angular/material' as mat; @use '../lib/mat-datatable-table-theme' as mat-datatable-table; @use '../directives/datatable-sort/mat-multi-sort-badge-theme' as mat-datatable-badge; diff --git a/projects/mat-datatable-lib/testing/src/mat-datatable-cell-harness.ts b/projects/mat-datatable-lib/testing/src/mat-datatable-cell-harness.ts index 4defa19..3a7774d 100644 --- a/projects/mat-datatable-lib/testing/src/mat-datatable-cell-harness.ts +++ b/projects/mat-datatable-lib/testing/src/mat-datatable-cell-harness.ts @@ -1,9 +1,10 @@ -import { - ContentContainerComponentHarness, - HarnessPredicate -} from '@angular/cdk/testing'; +import { ContentContainerComponentHarness, HarnessPredicate } from '@angular/cdk/testing'; -import { CellHarnessFilters, HeaderCellHarnessFilters, RowCellHarnessFilters } from './mat-datatable-harness-filters'; +import { + CellHarnessFilters, + HeaderCellHarnessFilters, + RowCellHarnessFilters +} from './mat-datatable-harness-filters'; export abstract class _MatCellHarnessBase extends ContentContainerComponentHarness { /** @@ -47,8 +48,8 @@ export abstract class _MatCellHarnessBase extends ContentContainerComponentHarne const prefix = 'mat-column-'; const name = classAttribute .split(' ') - .map(c => c.trim()) - .find(c => c.startsWith(prefix)); + .map((c) => c.trim()) + .find((c) => c.startsWith(prefix)); if (name) { return name.split(prefix)[1]; @@ -93,7 +94,7 @@ export class MatRowCellHarness extends _MatCellHarnessBase { HarnessPredicate.stringMatches(harness.getColumnName(), name) ) .addOption('isSingleLine', options.isSingleLine, (harness, isSingleLine) => - _MatCellHarnessBase.booleanMatches(harness.isSingleLine(), isSingleLine) + _MatCellHarnessBase.booleanMatches(harness.isSingleLine(), isSingleLine) ); } @@ -128,7 +129,7 @@ export class MatHeaderCellHarness extends _MatCellHarnessBase { HarnessPredicate.stringMatches(harness.getColumnName(), name) ) .addOption('isResizable', options.isResizable, (harness, isResizable) => - _MatCellHarnessBase.booleanMatches(harness.isResizable(), isResizable) + _MatCellHarnessBase.booleanMatches(harness.isResizable(), isResizable) ); } @@ -147,7 +148,7 @@ export class MatHeaderCellHarness extends _MatCellHarnessBase { * @returns true, if cell is resizable */ async isResizable(): Promise { - return (await this.resizerElement() !== null); + return (await this.resizerElement()) !== null; } /** @@ -163,7 +164,7 @@ export class MatHeaderCellHarness extends _MatCellHarnessBase { const originalWidth = await this.getColumnWidth(); const startPosition = 0; const endPosition = startPosition + (newWidth - originalWidth); - await (element).dispatchEvent('mousedown', { pageX: startPosition }); + await element.dispatchEvent('mousedown', { pageX: startPosition }); document.dispatchEvent(new MouseEvent('mousemove', { buttons: 1, clientX: endPosition })); document.dispatchEvent(new MouseEvent('mouseup', { clientX: endPosition })); } diff --git a/projects/mat-datatable-lib/testing/src/mat-datatable-harness-filters.ts b/projects/mat-datatable-lib/testing/src/mat-datatable-harness-filters.ts index bbbbe29..3c24043 100644 --- a/projects/mat-datatable-lib/testing/src/mat-datatable-harness-filters.ts +++ b/projects/mat-datatable-lib/testing/src/mat-datatable-harness-filters.ts @@ -28,4 +28,4 @@ export interface RowHarnessFilters extends BaseHarnessFilters { } /** A set of criteria that can be used to filter a list of mat-datatable harness instances. */ -export type MatDatatableHarnessFilters = BaseHarnessFilters +export type MatDatatableHarnessFilters = BaseHarnessFilters; diff --git a/projects/mat-datatable-lib/testing/src/mat-datatable-row-harness.spec.ts b/projects/mat-datatable-lib/testing/src/mat-datatable-row-harness.spec.ts index 2b5a106..4cdd363 100644 --- a/projects/mat-datatable-lib/testing/src/mat-datatable-row-harness.spec.ts +++ b/projects/mat-datatable-lib/testing/src/mat-datatable-row-harness.spec.ts @@ -5,7 +5,11 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { BehaviorSubject, Observable, of } from 'rxjs'; -import { MatFooterRowHarness, MatHeaderRowHarness, MatRowHarness } from './mat-datatable-row-harness'; +import { + MatFooterRowHarness, + MatHeaderRowHarness, + MatRowHarness +} from './mat-datatable-row-harness'; import { DataStoreProvider, @@ -26,18 +30,12 @@ describe('MatRowHarness', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - MatDatatableModule, - NoopAnimationsModule - ], - declarations: [ - TableWithoutFooterHarnessTestComponent - ] - }) - .compileComponents(); + imports: [MatDatatableModule, NoopAnimationsModule], + declarations: [TableWithoutFooterHarnessTestComponent] + }).compileComponents(); })); - beforeEach( async () => { + beforeEach(async () => { fixture = TestBed.createComponent(TableWithoutFooterHarnessTestComponent); fixture.autoDetectChanges(); @@ -48,7 +46,7 @@ describe('MatRowHarness', () => { loader = TestbedHarnessEnvironment.loader(fixture); }); - it('should get array of MatRowCellHarness of cells in first row', async () => { + it('should get array of MatRowCellHarness of cells in first row', async () => { const rowHarness = await loader.getHarness(MatRowHarness); const cellsHarnesses = await rowHarness.getCells(); @@ -104,7 +102,7 @@ describe('MatRowHarness', () => { it('should get array of content of selected cells in 1st row - filter with regex', async () => { const rowHarness = await loader.getHarness(MatRowHarness); - const rowContent = await rowHarness.getCellTextByIndex({ text: /H.*/ }); + const rowContent = await rowHarness.getCellTextByIndex({ text: /H.*/ }); expect(rowContent.length).toEqual(2); expect(rowContent).toEqual(['Hydrogen', 'H']); @@ -130,42 +128,50 @@ describe('MatRowHarness', () => { }); it('should get array of MatRowHarness - filter by element content with string', async () => { - const filteredRowHarnesses = await loader.getAllHarnesses(MatRowHarness.with( - { rowCellsContent: { - weight: '9.0122' - }} - )); + const filteredRowHarnesses = await loader.getAllHarnesses( + MatRowHarness.with({ + rowCellsContent: { + weight: '9.0122' + } + }) + ); expect(filteredRowHarnesses.length).toEqual(1); }); it('should get array of MatRowHarness - filter by element content without match', async () => { - const filteredRowHarnesses = await loader.getAllHarnesses(MatRowHarness.with( - { rowCellsContent: { - weight: '12' - }} - )); + const filteredRowHarnesses = await loader.getAllHarnesses( + MatRowHarness.with({ + rowCellsContent: { + weight: '12' + } + }) + ); expect(filteredRowHarnesses.length).toEqual(0); }); it('should get array of MatRowHarness - filter by element content with regexp', async () => { - const filteredRowHarnesses = await loader.getAllHarnesses(MatRowHarness.with( - { rowCellsContent: { - name: /(H.+|Boron)/ - }} - )); + const filteredRowHarnesses = await loader.getAllHarnesses( + MatRowHarness.with({ + rowCellsContent: { + name: /(H.+|Boron)/ + } + }) + ); expect(filteredRowHarnesses.length).toEqual(3); }); it('should get array of MatRowHarness - filter by element content with mixed filters', async () => { - const filteredRowHarnesses = await loader.getAllHarnesses(MatRowHarness.with( - { rowCellsContent: { - name: /(H.+|Boron)/, - weight: '4.0026' - }} - )); + const filteredRowHarnesses = await loader.getAllHarnesses( + MatRowHarness.with({ + rowCellsContent: { + name: /(H.+|Boron)/, + weight: '4.0026' + } + }) + ); expect(filteredRowHarnesses.length).toEqual(1); @@ -175,11 +181,13 @@ describe('MatRowHarness', () => { }); it('should get array of MatRowHarness - filter by element content with illegal column name', async () => { - const filteredRowHarnesses = await loader.getAllHarnesses(MatRowHarness.with( - { rowCellsContent: { - illegalCol: 'any value' - }} - )); + const filteredRowHarnesses = await loader.getAllHarnesses( + MatRowHarness.with({ + rowCellsContent: { + illegalCol: 'any value' + } + }) + ); expect(filteredRowHarnesses.length).toEqual(0); }); @@ -209,15 +217,9 @@ describe('MatHeaderRowHarness', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - MatDatatableModule, - NoopAnimationsModule - ], - declarations: [ - TableWithoutFooterHarnessTestComponent - ] - }) - .compileComponents(); + imports: [MatDatatableModule, NoopAnimationsModule], + declarations: [TableWithoutFooterHarnessTestComponent] + }).compileComponents(); })); beforeEach(() => { @@ -317,15 +319,9 @@ describe('MatFooterRowHarness', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - MatDatatableModule, - NoopAnimationsModule - ], - declarations: [ - TableWithoutFooterHarnessTestComponent - ] - }) - .compileComponents(); + imports: [MatDatatableModule, NoopAnimationsModule], + declarations: [TableWithoutFooterHarnessTestComponent] + }).compileComponents(); })); beforeEach(() => { @@ -392,7 +388,7 @@ type TableHarnessTestRow = { name: string; weight: number; symbol: string; -} +}; class TableHarnessTestDataStore implements DataStoreProvider { private trackBy: TrackByFunction; @@ -450,7 +446,11 @@ class TableHarnessTestDataStore implements DataStoreProvider[] ): Observable { const selectedDataset = this.getRawDataSortedFiltered(sorts, filters); - return of(selectedDataset.findIndex(currentRow => this.trackBy(0, row) === this.trackBy(0, currentRow))); + return of( + selectedDataset.findIndex( + (currentRow) => this.trackBy(0, row) === this.trackBy(0, currentRow) + ) + ); } private getRawDataSortedFiltered( @@ -460,26 +460,31 @@ class TableHarnessTestDataStore implements DataStoreProvider 0)) { + if (filters !== undefined && Array.isArray(filters) && filters.length > 0) { selectedDataset = selectedDataset.filter((row: DatatableItem) => { - return filters.reduce((isSelected: boolean, currentFilter: FieldFilterDefinition) => { - if (currentFilter.value !== undefined) { - isSelected ||= row[currentFilter.fieldName] === currentFilter.value; - } else if ((currentFilter.valueFrom !== undefined) && (currentFilter.valueTo !== undefined)) { - isSelected ||= ( - (row[currentFilter.fieldName] >= currentFilter.valueFrom) && - (row[currentFilter.fieldName] <= currentFilter.valueTo) - ); - } - return isSelected; - }, false); + return filters.reduce( + (isSelected: boolean, currentFilter: FieldFilterDefinition) => { + if (currentFilter.value !== undefined) { + isSelected ||= row[currentFilter.fieldName] === currentFilter.value; + } else if ( + currentFilter.valueFrom !== undefined && + currentFilter.valueTo !== undefined + ) { + isSelected ||= + row[currentFilter.fieldName] >= currentFilter.valueFrom && + row[currentFilter.fieldName] <= currentFilter.valueTo; + } + return isSelected; + }, + false + ); }); } // Sort data - only the first entry of the definitions is used - if ((sorts !== undefined) && Array.isArray(sorts)) { + if (sorts !== undefined && Array.isArray(sorts)) { this.currentSortingDefinitions = sorts; - if ((sorts.length > 0)) { + if (sorts.length > 0) { selectedDataset.sort(this.compareFn); } } @@ -510,7 +515,7 @@ class TableHarnessTestDataStore implements DataStoreProvider implements DataStoreProviderb) */ private compare(a: string | number, b: string | number, isAsc: boolean): number { - return (a === b ? 0 : (a < b ? -1 : 1)) * (isAsc ? 1 : -1); + return (a === b ? 0 : a < b ? -1 : 1) * (isAsc ? 1 : -1); } } // HTML for mat-datatable requires surrounding div with height set @Component({ template: ` -
- + - loading... - -
+ loading... + +
`, styles: ['.content-table { height: 400px; }'] }) diff --git a/projects/mat-datatable-lib/testing/src/mat-datatable-row-harness.ts b/projects/mat-datatable-lib/testing/src/mat-datatable-row-harness.ts index d2c51fa..76a3e86 100644 --- a/projects/mat-datatable-lib/testing/src/mat-datatable-row-harness.ts +++ b/projects/mat-datatable-lib/testing/src/mat-datatable-row-harness.ts @@ -57,9 +57,10 @@ export abstract class _MatRowHarnessBase< const sourceValue = value[key]; const targetValueOrPattern = pattern[key]; - result = typeof targetValueOrPattern === 'string' ? - sourceValue === targetValueOrPattern : - targetValueOrPattern.test(sourceValue); + result = + typeof targetValueOrPattern === 'string' + ? sourceValue === targetValueOrPattern + : targetValueOrPattern.test(sourceValue); if (!result) { break; @@ -84,7 +85,7 @@ export abstract class _MatRowHarnessBase< */ async getCellTextByIndex(filter: CellFilterType = {} as CellFilterType): Promise { const cells = await this.getCells(filter); - return parallel(() => cells.map(cell => cell.getText())); + return parallel(() => cells.map((cell) => cell.getText())); } /** @@ -92,11 +93,13 @@ export abstract class _MatRowHarnessBase< * @param filter - filter to select the sut (default; all cells). * @returns an object for the selected cells with the columns as properties. */ - async getCellTextByColumnName(filter: CellFilterType = {} as CellFilterType): Promise { + async getCellTextByColumnName( + filter: CellFilterType = {} as CellFilterType + ): Promise { const output: MatRowHarnessColumnsText = {}; const cells = await this.getCells(filter); const cellsData = await parallel(() => - cells.map(cell => { + cells.map((cell) => { return parallel(() => [cell.getColumnName(), cell.getText()]); }) ); @@ -124,14 +127,16 @@ export class MatRowHarness extends _MatRowHarnessBase< this: ComponentHarnessConstructor, options: RowHarnessFilters = {} ): HarnessPredicate { - return new HarnessPredicate(this, options) - // Filter for content of row cells defined in an object with - // ` property name = column name - // ` property value = cell value or RegExp to be filtered for - .addOption('rowCellsContent', options.rowCellsContent, (harness, cellsContent) => - _MatRowHarnessBase.rowCellsContentMatch(harness.getCellTextByColumnName(), cellsContent) - ); -} + return ( + new HarnessPredicate(this, options) + // Filter for content of row cells defined in an object with + // ` property name = column name + // ` property value = cell value or RegExp to be filtered for + .addOption('rowCellsContent', options.rowCellsContent, (harness, cellsContent) => + _MatRowHarnessBase.rowCellsContentMatch(harness.getCellTextByColumnName(), cellsContent) + ) + ); + } /** * Clicks the row. diff --git a/projects/mat-datatable-lib/testing/src/mat-datatable-table-harness.spec.ts b/projects/mat-datatable-lib/testing/src/mat-datatable-table-harness.spec.ts index d79737f..444d5e4 100644 --- a/projects/mat-datatable-lib/testing/src/mat-datatable-table-harness.spec.ts +++ b/projects/mat-datatable-lib/testing/src/mat-datatable-table-harness.spec.ts @@ -26,15 +26,9 @@ describe('MatDatatableHarness without footer', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - MatDatatableModule, - NoopAnimationsModule - ], - declarations: [ - TableWithoutFooterHarnessTestComponent - ] - }) - .compileComponents(); + imports: [MatDatatableModule, NoopAnimationsModule], + declarations: [TableWithoutFooterHarnessTestComponent] + }).compileComponents(); })); beforeEach(() => { @@ -68,7 +62,9 @@ describe('MatDatatableHarness without footer', () => { const rows = await table.getRows(); const headerCells = await headerRows[0].getCellTextByIndex(); - const cells = (await parallel(() => rows.map(row => row.getCells()))).map(row => row.length); + const cells = (await parallel(() => rows.map((row) => row.getCells()))).map( + (row) => row.length + ); expect(headerCells.length).toEqual(4); expect(headerCells).toEqual(['No.', 'Name', 'Weight', 'Symbol']); @@ -80,7 +76,7 @@ describe('MatDatatableHarness without footer', () => { const table = await loader.getHarness(MatDatatableHarness); const secondRow = (await table.getRows())[1]; const cells = await secondRow.getCells(); - const cellTexts = await parallel(() => cells.map(cell => cell.getText())); + const cellTexts = await parallel(() => cells.map((cell) => cell.getText())); expect(cellTexts).toEqual(['2', 'Helium', '4.0026', 'He']); }); @@ -89,7 +85,7 @@ describe('MatDatatableHarness without footer', () => { const table = await loader.getHarness(MatDatatableHarness); const fifthRow = (await table.getRows())[1]; const cells = await fifthRow.getCells(); - const cellColumnNames = await parallel(() => cells.map(cell => cell.getColumnName())); + const cellColumnNames = await parallel(() => cells.map((cell) => cell.getColumnName())); expect(cellColumnNames).toEqual(['position', 'name', 'weight', 'symbol']); }); @@ -98,7 +94,7 @@ describe('MatDatatableHarness without footer', () => { const table = await loader.getHarness(MatDatatableHarness); const firstRow = (await table.getRows())[0]; const cells = await firstRow.getCells({ text: '1.0079' }); - const cellTexts = await parallel(() => cells.map(cell => cell.getText())); + const cellTexts = await parallel(() => cells.map((cell) => cell.getText())); expect(cellTexts).toEqual(['1.0079']); }); @@ -107,7 +103,7 @@ describe('MatDatatableHarness without footer', () => { const table = await loader.getHarness(MatDatatableHarness); const firstRow = (await table.getRows())[0]; const cells = await firstRow.getCells({ columnName: 'symbol' }); - const cellTexts = await parallel(() => cells.map(cell => cell.getText())); + const cellTexts = await parallel(() => cells.map((cell) => cell.getText())); expect(cellTexts).toEqual(['H']); }); @@ -116,7 +112,7 @@ describe('MatDatatableHarness without footer', () => { const table = await loader.getHarness(MatDatatableHarness); const firstRow = (await table.getRows())[0]; const cells = await firstRow.getCells({ text: /^H/ }); - const cellTexts = await parallel(() => cells.map(cell => cell.getText())); + const cellTexts = await parallel(() => cells.map((cell) => cell.getText())); expect(cellTexts).toEqual(['Hydrogen', 'H']); }); @@ -233,15 +229,9 @@ describe('MatDatatableHarness with footer', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - MatDatatableModule, - NoopAnimationsModule - ], - declarations: [ - TableWithFooterHarnessTestComponent - ] - }) - .compileComponents(); + imports: [MatDatatableModule, NoopAnimationsModule], + declarations: [TableWithFooterHarnessTestComponent] + }).compileComponents(); })); beforeEach(() => { @@ -271,7 +261,9 @@ describe('MatDatatableHarness with footer', () => { const headerCells = await headerRows[0].getCellTextByIndex(); const footerCells = await footerRows[0].getCellTextByIndex(); - const cells = (await parallel(() => rows.map(row => row.getCells()))).map(row => row.length); + const cells = (await parallel(() => rows.map((row) => row.getCells()))).map( + (row) => row.length + ); expect(headerCells.length).toEqual(4); expect(headerCells).toEqual(['No.', 'Name', 'Weight', 'Symbol']); @@ -338,7 +330,7 @@ type TableHarnessTestRow = { name: string; weight: number; symbol: string; -} +}; class TableHarnessTestDataStore implements DataStoreProvider { private trackBy: TrackByFunction; @@ -396,7 +388,11 @@ class TableHarnessTestDataStore implements DataStoreProvider[] ): Observable { const selectedDataset = this.getRawDataSortedFiltered(sorts, filters); - return of(selectedDataset.findIndex(currentRow => this.trackBy(0, row) === this.trackBy(0, currentRow))); + return of( + selectedDataset.findIndex( + (currentRow) => this.trackBy(0, row) === this.trackBy(0, currentRow) + ) + ); } /** @@ -415,26 +411,31 @@ class TableHarnessTestDataStore implements DataStoreProvider 0)) { + if (filters !== undefined && Array.isArray(filters) && filters.length > 0) { selectedDataset = selectedDataset.filter((row: DatatableItem) => { - return filters.reduce((isSelected: boolean, currentFilter: FieldFilterDefinition) => { - if (currentFilter.value !== undefined) { - isSelected ||= row[currentFilter.fieldName] === currentFilter.value; - } else if ((currentFilter.valueFrom !== undefined) && (currentFilter.valueTo !== undefined)) { - isSelected ||= ( - (row[currentFilter.fieldName] >= currentFilter.valueFrom) && - (row[currentFilter.fieldName] <= currentFilter.valueTo) - ); - } - return isSelected; - }, false); + return filters.reduce( + (isSelected: boolean, currentFilter: FieldFilterDefinition) => { + if (currentFilter.value !== undefined) { + isSelected ||= row[currentFilter.fieldName] === currentFilter.value; + } else if ( + currentFilter.valueFrom !== undefined && + currentFilter.valueTo !== undefined + ) { + isSelected ||= + row[currentFilter.fieldName] >= currentFilter.valueFrom && + row[currentFilter.fieldName] <= currentFilter.valueTo; + } + return isSelected; + }, + false + ); }); } // Sort data - only the first entry of the definitions is used - if ((sorts !== undefined) && Array.isArray(sorts)) { + if (sorts !== undefined && Array.isArray(sorts)) { this.currentSortingDefinitions = sorts; - if ((sorts.length > 0)) { + if (sorts.length > 0) { selectedDataset.sort(this.compareFn); } } @@ -465,7 +466,7 @@ class TableHarnessTestDataStore implements DataStoreProvider implements DataStoreProviderb) */ private compare(a: string | number, b: string | number, isAsc: boolean): number { - return (a === b ? 0 : (a < b ? -1 : 1)) * (isAsc ? 1 : -1); + return (a === b ? 0 : a < b ? -1 : 1) * (isAsc ? 1 : -1); } } // HTML for mat-datatable requires surrounding div with height set @Component({ template: ` -
- + - loading... - -
+ loading... + +
`, styles: ['.content-table { height: 400px; }'] }) @@ -572,7 +574,11 @@ class TableWithoutFooterHarnessTestComponent { } // arrow function is required to give dataStore.getPagedData the correct 'this' - protected getData = (rowsRange: RequestRowsRange, sorts?: FieldSortDefinition[], filters?: FieldFilterDefinition[]) => { + protected getData = ( + rowsRange: RequestRowsRange, + sorts?: FieldSortDefinition[], + filters?: FieldFilterDefinition[] + ) => { return this.dataStore.getPagedData(rowsRange, sorts, filters); }; @@ -589,20 +595,21 @@ class TableWithoutFooterHarnessTestComponent { // HTML for mat-datatable requires surrounding div with height set @Component({ template: ` -
- + - loading... - -
+ loading... + +
`, styles: ['.content-table { height: 400px; }'] }) @@ -671,7 +678,11 @@ class TableWithFooterHarnessTestComponent { protected selectedRowsAsString = '-'; // arrow function is required to give dataStore.getPagedData the correct 'this' - protected getData = (rowsRange: RequestRowsRange, sorts?: FieldSortDefinition[], filters?: FieldFilterDefinition[]) => { + protected getData = ( + rowsRange: RequestRowsRange, + sorts?: FieldSortDefinition[], + filters?: FieldFilterDefinition[] + ) => { return this.dataStore.getPagedData(rowsRange, sorts, filters); }; diff --git a/projects/mat-datatable-lib/testing/src/mat-datatable-table-harness.ts b/projects/mat-datatable-lib/testing/src/mat-datatable-table-harness.ts index 4703328..5c8cf40 100644 --- a/projects/mat-datatable-lib/testing/src/mat-datatable-table-harness.ts +++ b/projects/mat-datatable-lib/testing/src/mat-datatable-table-harness.ts @@ -5,10 +5,7 @@ import { parallel } from '@angular/cdk/testing'; -import { - MatDatatableHarnessFilters, - RowHarnessFilters -} from './mat-datatable-harness-filters'; +import { MatDatatableHarnessFilters, RowHarnessFilters } from './mat-datatable-harness-filters'; import { MatFooterRowHarness, MatHeaderRowHarness, @@ -44,13 +41,13 @@ export class MatDatatableHarness extends ContentContainerComponentHarness { - return this.locatorForAll(MatHeaderRowHarness)(); - } + /** + * Gets a list of the header rows of a mat-datatable. + * @returns array of the header rows of the mat-datatable (length is always 1). + */ + async getHeaderRows(): Promise { + return this.locatorForAll(MatHeaderRowHarness)(); + } /** * Gets all of the regular data rows in a mat-datatable. @@ -75,7 +72,7 @@ export class MatDatatableHarness extends ContentContainerComponentHarness { const rows = await this.getRows(); - return parallel(() => rows.map(row => row.getCellTextByIndex())); + return parallel(() => rows.map((row) => row.getCellTextByIndex())); } /** @@ -92,18 +89,21 @@ export class MatDatatableHarness extends ContentContainerComponentHarness [ headerRows[0].getCellTextByColumnName(), - (footerRows.length === 1) ? footerRows[0].getCellTextByColumnName() : undefined, - parallel(() => dataRows.map(row => row.getCellTextByColumnName())) + footerRows.length === 1 ? footerRows[0].getCellTextByColumnName() : undefined, + parallel(() => dataRows.map((row) => row.getCellTextByColumnName())) ]); - rowsData.forEach(data => { - Object.keys(data).forEach(columnName => { + rowsData.forEach((data) => { + Object.keys(data).forEach((columnName) => { const cellText = data[columnName]; if (!text[columnName]) { text[columnName] = { headerText: getHeaderCellTextByColumn(headerData, columnName), - footerText: (footerData !== undefined) ? getFooterCellTextByColumn(footerData, columnName) : undefined, + footerText: + footerData !== undefined + ? getFooterCellTextByColumn(footerData, columnName) + : undefined, text: [] }; } @@ -122,10 +122,13 @@ export class MatDatatableHarness extends ContentContainerComponentHarness { +const getHeaderCellTextByColumn = ( + headerRowData: MatRowHarnessColumnsText, + column: string +): string => { let columnText = ''; - Object.keys(headerRowData).forEach(columnName => { + Object.keys(headerRowData).forEach((columnName) => { if (columnName === column) { columnText = headerRowData[columnName]; } @@ -140,10 +143,13 @@ const getHeaderCellTextByColumn = (headerRowData: MatRowHarnessColumnsText, colu * @param column - Name of the column to get the text from. * @returns the selected footer cell text (or undefined, if no footer is defined). */ -const getFooterCellTextByColumn = (footerRowData: MatRowHarnessColumnsText, column: string): string | undefined => { +const getFooterCellTextByColumn = ( + footerRowData: MatRowHarnessColumnsText, + column: string +): string | undefined => { let columnText: string | undefined; - Object.keys(footerRowData).forEach(columnName => { + Object.keys(footerRowData).forEach((columnName) => { if (columnName === column) { columnText = footerRowData[columnName]; } diff --git a/projects/mat-datatable-lib/testing/src/mat-multi-sort-harness-filters.ts b/projects/mat-datatable-lib/testing/src/mat-multi-sort-harness-filters.ts index 60de463..9d09edd 100644 --- a/projects/mat-datatable-lib/testing/src/mat-multi-sort-harness-filters.ts +++ b/projects/mat-datatable-lib/testing/src/mat-multi-sort-harness-filters.ts @@ -1,7 +1,7 @@ import { BaseHarnessFilters } from '@angular/cdk/testing'; import { SortDirection } from '@angular/material/sort'; -export type MultiSortHarnessFilters = BaseHarnessFilters +export type MultiSortHarnessFilters = BaseHarnessFilters; export interface MultiSortHeaderHarnessFilters extends BaseHarnessFilters { label?: string | RegExp; diff --git a/projects/mat-datatable-lib/testing/src/mat-multi-sort-harness.spec.ts b/projects/mat-datatable-lib/testing/src/mat-multi-sort-harness.spec.ts index 087bebb..e3b9e86 100644 --- a/projects/mat-datatable-lib/testing/src/mat-multi-sort-harness.spec.ts +++ b/projects/mat-datatable-lib/testing/src/mat-multi-sort-harness.spec.ts @@ -15,13 +15,8 @@ describe('MatSortHarness', () => { beforeEach(waitForAsync(() => { void TestBed.configureTestingModule({ - imports: [ - MatMultiSortModule, - NoopAnimationsModule - ], - declarations: [ - SortHarnessTestComponent - ] + imports: [MatMultiSortModule, NoopAnimationsModule], + declarations: [SortHarnessTestComponent] }).compileComponents(); })); @@ -56,7 +51,7 @@ describe('MatSortHarness', () => { it('should be able to filter headers by their labels via a regex', async () => { const sort = await loader.getHarness(MatMultiSortHarness); const headers = await sort.getSortHeaders({ label: /^C/ }); - const labels = await parallel(() => headers.map(header => header.getLabel())); + const labels = await parallel(() => headers.map((header) => header.getLabel())); expect(headers).toHaveSize(2); expect(labels).toEqual(['Calories', 'Carbs']); @@ -82,9 +77,9 @@ describe('MatSortHarness', () => { expect(headers).toHaveSize(5); const sortDefinitions: Sort[] = [ - { active:'protein', direction:'desc' }, - { active:'calories', direction:'asc' }, - { active:'name', direction:'asc' } + { active: 'protein', direction: 'desc' }, + { active: 'calories', direction: 'asc' }, + { active: 'name', direction: 'asc' } ]; component.matMultiSort.sortDefinitions = sortDefinitions; fixture.detectChanges(); @@ -99,17 +94,21 @@ describe('MatSortHarness', () => { const headers = await matMultiSort.getSortHeaders({ sortDirection: '' }); const sortDefinitions: Sort[] = [ - { active:'protein', direction:'desc' }, - { active:'name', direction:'asc' } + { active: 'protein', direction: 'desc' }, + { active: 'name', direction: 'asc' } ]; component.matMultiSort.sortDefinitions = sortDefinitions; fixture.detectChanges(); const activeHeaders = await matMultiSort.getActiveHeaders(); - const activeIds = await parallel(() => activeHeaders.map(header => header.getId())); - const activeLabels = await parallel(() => activeHeaders.map(header => header.getLabel())); - const activeSortDirections = await parallel(() => activeHeaders.map(header => header.getSortDirection())); - const activeSortPositions = await parallel(() => activeHeaders.map(header => header.getSortPosition())); + const activeIds = await parallel(() => activeHeaders.map((header) => header.getId())); + const activeLabels = await parallel(() => activeHeaders.map((header) => header.getLabel())); + const activeSortDirections = await parallel(() => + activeHeaders.map((header) => header.getSortDirection()) + ); + const activeSortPositions = await parallel(() => + activeHeaders.map((header) => header.getSortPosition()) + ); expect(headers).toHaveSize(5); @@ -137,9 +136,9 @@ describe('MatSortHarness', () => { it('should be able to get header by the sorting position', async () => { const sort = await loader.getHarness(MatMultiSortHarness); const sortDefinitions: Sort[] = [ - { active:'protein', direction:'desc' }, - { active:'calories', direction:'asc' }, - { active:'name', direction:'asc' } + { active: 'protein', direction: 'desc' }, + { active: 'calories', direction: 'asc' }, + { active: 'name', direction: 'asc' } ]; component.matMultiSort.sortDefinitions = sortDefinitions; fixture.detectChanges(); @@ -156,7 +155,7 @@ describe('MatSortHarness', () => { it('should be able to get the label of a header', async () => { const sort = await loader.getHarness(MatMultiSortHarness); const headers = await sort.getSortHeaders(); - const labels = await parallel(() => headers.map(header => header.getLabel())); + const labels = await parallel(() => headers.map((header) => header.getLabel())); expect(labels).toEqual(['Dessert', 'Calories', 'Fat', 'Carbs', 'Protein']); }); @@ -221,17 +220,21 @@ describe('MatSortHarness', () => { const headers = await matMultiSort.getSortHeaders({ sortDirection: '' }); const sortDefinitions: Sort[] = [ - { active:'protein', direction:'desc' }, - { active:'name', direction:'asc' } + { active: 'protein', direction: 'desc' }, + { active: 'name', direction: 'asc' } ]; component.matMultiSort.sortDefinitions = sortDefinitions; fixture.detectChanges(); const activeHeaders = await matMultiSort.getActiveHeaders(); - const activeIds = await parallel(() => activeHeaders.map(header => header.getId())); - const activeLabels = await parallel(() => activeHeaders.map(header => header.getLabel())); - const activeSortDirections = await parallel(() => activeHeaders.map(header => header.getSortDirection())); - const activeSortPositions = await parallel(() => activeHeaders.map(header => header.getSortPosition())); + const activeIds = await parallel(() => activeHeaders.map((header) => header.getId())); + const activeLabels = await parallel(() => activeHeaders.map((header) => header.getLabel())); + const activeSortDirections = await parallel(() => + activeHeaders.map((header) => header.getSortDirection()) + ); + const activeSortPositions = await parallel(() => + activeHeaders.map((header) => header.getSortPosition()) + ); expect(headers).toHaveSize(5); @@ -260,9 +263,7 @@ describe('MatSortHarness', () => { const sort = await loader.getHarness(MatMultiSortHarness); const headers = await sort.getSortHeaders(); - const sortDefinitions: Sort[] = [ - { active:'name', direction:'asc' } - ]; + const sortDefinitions: Sort[] = [{ active: 'name', direction: 'asc' }]; component.matMultiSort.sortDefinitions = sortDefinitions; fixture.detectChanges(); @@ -275,9 +276,7 @@ describe('MatSortHarness', () => { const sort = await loader.getHarness(MatMultiSortHarness); const headers = await sort.getSortHeaders(); - const sortDefinitions: Sort[] = [ - { active:'name', direction:'asc' } - ]; + const sortDefinitions: Sort[] = [{ active: 'name', direction: 'asc' }]; component.matMultiSort.sortDefinitions = sortDefinitions; fixture.detectChanges(); @@ -290,9 +289,7 @@ describe('MatSortHarness', () => { const sort = await loader.getHarness(MatMultiSortHarness); const headers = await sort.getSortHeaders(); - const sortDefinitions: Sort[] = [ - { active:'calories', direction:'desc' } - ]; + const sortDefinitions: Sort[] = [{ active: 'calories', direction: 'desc' }]; component.matMultiSort.sortDefinitions = sortDefinitions; fixture.detectChanges(); @@ -305,9 +302,7 @@ describe('MatSortHarness', () => { const sort = await loader.getHarness(MatMultiSortHarness); const headers = await sort.getSortHeaders(); - const sortDefinitions: Sort[] = [ - { active:'name', direction:'asc' } - ]; + const sortDefinitions: Sort[] = [{ active: 'name', direction: 'asc' }]; component.matMultiSort.sortDefinitions = sortDefinitions; fixture.detectChanges(); @@ -320,9 +315,7 @@ describe('MatSortHarness', () => { const sort = await loader.getHarness(MatMultiSortHarness); const headers = await sort.getSortHeaders(); - const sortDefinitions: Sort[] = [ - { active:'name', direction:'desc' } - ]; + const sortDefinitions: Sort[] = [{ active: 'name', direction: 'desc' }]; component.matMultiSort.sortDefinitions = sortDefinitions; fixture.detectChanges(); @@ -335,9 +328,7 @@ describe('MatSortHarness', () => { const sort = await loader.getHarness(MatMultiSortHarness); const headers = await sort.getSortHeaders(); - const sortDefinitions: Sort[] = [ - { active:'name', direction:'asc' } - ]; + const sortDefinitions: Sort[] = [{ active: 'name', direction: 'asc' }]; component.matMultiSort.sortDefinitions = sortDefinitions; fixture.detectChanges(); @@ -349,9 +340,7 @@ describe('MatSortHarness', () => { it('should get the sortingData for 1 header', async () => { const sort = await loader.getHarness(MatMultiSortHarness); const headers = await sort.getSortHeaders(); - const sortDefinitions: Sort[] = [ - { active:'name', direction:'asc' } - ]; + const sortDefinitions: Sort[] = [{ active: 'name', direction: 'asc' }]; component.matMultiSort.sortDefinitions = sortDefinitions; fixture.detectChanges(); const sortData = await headers[0].getAllSortData(); @@ -368,8 +357,8 @@ describe('MatSortHarness', () => { const sort = await loader.getHarness(MatMultiSortHarness); const sortDefinitions: Sort[] = [ - { active:'protein', direction:'desc' }, - { active:'name', direction:'asc' } + { active: 'protein', direction: 'desc' }, + { active: 'name', direction: 'asc' } ]; component.matMultiSort.sortDefinitions = sortDefinitions; fixture.detectChanges(); @@ -412,11 +401,11 @@ type Dessert = { - {{dessert.name}} - {{dessert.calories}} - {{dessert.fat}} - {{dessert.carbs}} - {{dessert.protein}} + {{ dessert.name }} + {{ dessert.calories }} + {{ dessert.fat }} + {{ dessert.carbs }} + {{ dessert.protein }} ` @@ -446,7 +435,7 @@ class SortHarnessTestComponent { let result = 0; for (let i = 0; i < sorts.length; i++) { const fieldName = sorts[i].active; - const isAsc = (sorts[i].direction === 'asc'); + const isAsc = sorts[i].direction === 'asc'; const valueA = a[fieldName as keyof Dessert]; const valueB = b[fieldName as keyof Dessert]; result = compare(valueA, valueB, isAsc); @@ -469,5 +458,5 @@ class SortHarnessTestComponent { */ // eslint-disable-next-line prefer-arrow/prefer-arrow-functions function compare(a: string | number, b: string | number, isAsc: boolean): number { - return (a === b ? 0 : (a < b ? -1 : 1)) * (isAsc ? 1 : -1); + return (a === b ? 0 : a < b ? -1 : 1) * (isAsc ? 1 : -1); } diff --git a/projects/mat-datatable-lib/testing/src/mat-multi-sort-harness.ts b/projects/mat-datatable-lib/testing/src/mat-multi-sort-harness.ts index 7d2b6c2..8da04a1 100644 --- a/projects/mat-datatable-lib/testing/src/mat-multi-sort-harness.ts +++ b/projects/mat-datatable-lib/testing/src/mat-multi-sort-harness.ts @@ -1,6 +1,9 @@ import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing'; -import { MultiSortHarnessFilters, MultiSortHeaderHarnessFilters } from './mat-multi-sort-harness-filters'; +import { + MultiSortHarnessFilters, + MultiSortHeaderHarnessFilters +} from './mat-multi-sort-harness-filters'; import { MatMultiSortHeaderHarness, DomSortingDefinition } from './mat-multi-sort-header-harness'; /** Harness for interacting with a standard `mat-sort` in tests. */ @@ -21,7 +24,9 @@ export class MatMultiSortHarness extends ComponentHarness { * @param filter - filter to select the sut (default; all headers). * @returns an array of the selected sort header harnesses. */ - async getSortHeaders(filter: MultiSortHeaderHarnessFilters = {}): Promise { + async getSortHeaders( + filter: MultiSortHeaderHarnessFilters = {} + ): Promise { return this.locatorForAll(MatMultiSortHeaderHarness.with(filter))(); } @@ -51,11 +56,9 @@ export class MatMultiSortHarness extends ComponentHarness { for (let i = 0; i < headers.length; i++) { result.push(await headers[i].getAllSortData()); } - const sortedResult = result.sort( - (a:DomSortingDefinition, b:DomSortingDefinition) => { - return a.sortPosition - b.sortPosition; - } - ); + const sortedResult = result.sort((a: DomSortingDefinition, b: DomSortingDefinition) => { + return a.sortPosition - b.sortPosition; + }); return sortedResult; } diff --git a/projects/mat-datatable-lib/testing/src/mat-multi-sort-header-harness.ts b/projects/mat-datatable-lib/testing/src/mat-multi-sort-header-harness.ts index 2c33d41..9ac7121 100644 --- a/projects/mat-datatable-lib/testing/src/mat-multi-sort-header-harness.ts +++ b/projects/mat-datatable-lib/testing/src/mat-multi-sort-header-harness.ts @@ -27,7 +27,9 @@ export class MatMultiSortHeaderHarness extends ComponentHarness { * @param options - Options for narrowing the search. * @returns predicate for identifying the sut */ - static with(options: MultiSortHeaderHarnessFilters = {}): HarnessPredicate { + static with( + options: MultiSortHeaderHarnessFilters = {} + ): HarnessPredicate { return new HarnessPredicate(MatMultiSortHeaderHarness, options) .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label) @@ -76,11 +78,11 @@ export class MatMultiSortHeaderHarness extends ComponentHarness { async getSortPosition(): Promise { let result = Number.NaN; try { - const position = await ((await this._badgeContent()).text()); + const position = await (await this._badgeContent()).text(); if (position.length !== 0) { result = Number(position); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { result = Number.NaN; } @@ -174,7 +176,7 @@ export class MatMultiSortHeaderHarness extends ComponentHarness { * the value is expected to be `null`. * @returns Whether the value matches the pattern. */ -const numberMatches= async ( +const numberMatches = async ( value: number | null | Promise, pattern: number | null ): Promise => { diff --git a/projects/mat-datatable-lib/tsconfig.lib.json b/projects/mat-datatable-lib/tsconfig.lib.json index f9a1985..faa9bf5 100644 --- a/projects/mat-datatable-lib/tsconfig.lib.json +++ b/projects/mat-datatable-lib/tsconfig.lib.json @@ -8,8 +8,5 @@ "inlineSources": true, "types": [] }, - "exclude": [ - "**/*.spec.ts", - "cypress" - ] + "exclude": ["**/*.spec.ts", "cypress"] } diff --git a/projects/mat-datatable-lib/tsconfig.spec.json b/projects/mat-datatable-lib/tsconfig.spec.json index a1a2420..f3e7066 100644 --- a/projects/mat-datatable-lib/tsconfig.spec.json +++ b/projects/mat-datatable-lib/tsconfig.spec.json @@ -3,13 +3,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "../../out-tsc/spec", - "types": [ - "jasmine", - "node" - ] + "types": ["jasmine", "node"] }, - "include": [ - "src/**/*.spec.ts", - "src/**/*.d.ts" - ] + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] } diff --git a/tools/npm-scripts/readme-updater.js b/tools/npm-scripts/readme-updater.js index 355591f..8175bd4 100644 --- a/tools/npm-scripts/readme-updater.js +++ b/tools/npm-scripts/readme-updater.js @@ -1,8 +1,9 @@ - // readme-updater.js +// readme-updater.js // standard-version updater for readme files with iconified semantic version string. -module.exports.readVersion = function(contents) { - const versionSearchPattern = /(.*!\[Version]\(https:\/\/img.shields.io\/badge\/version-)(.+)(-blue\.svg.*)/is; +module.exports.readVersion = function (contents) { + const versionSearchPattern = + /(.*!\[Version]\(https:\/\/img.shields.io\/badge\/version-)(.+)(-blue\.svg.*)/is; const regexResult = contents.match(versionSearchPattern); let version = ''; if (regexResult.length === 4) { @@ -12,8 +13,9 @@ module.exports.readVersion = function(contents) { return version; }; -module.exports.writeVersion = function(contents, version) { - const versionSearchPattern = /(.*!\[Version]\(https:\/\/img.shields.io\/badge\/version-)(.+)(-blue\.svg.*)/is; +module.exports.writeVersion = function (contents, version) { + const versionSearchPattern = + /(.*!\[Version]\(https:\/\/img.shields.io\/badge\/version-)(.+)(-blue\.svg.*)/is; const regexResult = contents.match(versionSearchPattern); let newContents = contents; if (regexResult.length === 4) { diff --git a/tools/open-coverage.mjs b/tools/open-coverage.mjs index 295da6d..ce1a106 100644 --- a/tools/open-coverage.mjs +++ b/tools/open-coverage.mjs @@ -3,6 +3,9 @@ import url from 'node:url'; import { exec } from 'node:child_process'; const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); -const coverageReportFile = url.pathToFileURL(path.join(__dirname, '../coverage', 'mat-datatable-lib/index.html')); -const start = (process.platform == 'darwin'? 'open': process.platform == 'win32'? 'start': 'xdg-open'); +const coverageReportFile = url.pathToFileURL( + path.join(__dirname, '../coverage', 'mat-datatable-lib/index.html') +); +const start = + process.platform == 'darwin' ? 'open' : process.platform == 'win32' ? 'start' : 'xdg-open'; exec(start + ' ' + coverageReportFile); diff --git a/tsconfig.json b/tsconfig.json index 3bf81af..8e21772 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,10 +9,7 @@ "experimentalDecorators": true, "forceConsistentCasingInFileNames": true, "importHelpers": true, - "lib": [ - "ES2022", - "dom" - ], + "lib": ["ES2022", "dom"], "module": "ES2022", "moduleResolution": "node", "noFallthroughCasesInSwitch": true, @@ -21,12 +18,8 @@ "noPropertyAccessFromIndexSignature": true, "outDir": "./dist/out-tsc", "paths": { - "mat-datatable": [ - "projects/mat-datatable-lib/src" - ], - "mat-datatable/testing": [ - "projects/mat-datatable-lib/testing/src" - ] + "mat-datatable": ["projects/mat-datatable-lib/src"], + "mat-datatable/testing": ["projects/mat-datatable-lib/testing/src"] }, "sourceMap": true, "strict": true, @@ -40,11 +33,6 @@ "strictInputAccessModifiers": true, "strictTemplates": true }, - "include": [ - "**/src/**/*.ts", - "node_modules/cypress" - ], - "exclude": [ - "node_modules/cypress" - ] + "include": ["**/src/**/*.ts", "node_modules/cypress"], + "exclude": ["node_modules/cypress"] }