From 377b094f2dd6f6d3bdc1d0ce5a3abb067496099d Mon Sep 17 00:00:00 2001
From: Richard Russell <2265225+rars@users.noreply.github.com>
Date: Thu, 27 Nov 2025 15:36:07 +0000
Subject: [PATCH] feat(ngx-diff): allow diff computation to run in Web worker
for large diffs
---
README.md | 85 +++
angular.json | 8 +-
package-lock.json | 693 +++++++-----------
package.json | 2 +-
projects/ngx-diff/package.json | 10 +-
.../progress-bar/progress-bar.component.html | 5 +
.../progress-bar/progress-bar.component.scss | 30 +
.../progress-bar.component.spec.ts | 23 +
.../progress-bar/progress-bar.component.ts | 9 +
.../side-by-side-diff.component.html | 117 +--
.../side-by-side-diff.component.spec.ts | 177 ++++-
.../side-by-side-diff.component.ts | 73 +-
.../unified-diff/unified-diff.component.html | 79 +-
.../unified-diff.component.spec.ts | 96 +--
.../unified-diff/unified-diff.component.ts | 61 +-
.../diff-match-patch.service.ts | 125 +++-
projects/ngx-diff/styles/default-theme.css | 6 +
src/app/app.component.html | 3 +-
.../diff-web-worker-factory.service.spec.ts | 16 +
.../diff-web-worker-factory.service.ts | 13 +
src/app/web-workers/diff-web-worker.worker.ts | 25 +
src/main.ts | 3 +
tsconfig.worker.json | 15 +
23 files changed, 1073 insertions(+), 601 deletions(-)
create mode 100644 projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.html
create mode 100644 projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.scss
create mode 100644 projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.spec.ts
create mode 100644 projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.ts
create mode 100644 src/app/services/diff-web-worker-factory/diff-web-worker-factory.service.spec.ts
create mode 100644 src/app/services/diff-web-worker-factory/diff-web-worker-factory.service.ts
create mode 100644 src/app/web-workers/diff-web-worker.worker.ts
create mode 100644 tsconfig.worker.json
diff --git a/README.md b/README.md
index 77e6c06..404ae5c 100644
--- a/README.md
+++ b/README.md
@@ -95,6 +95,9 @@ To create your own theme, override the relevant CSS variables; for example, in y
--ngx-diff-selected-border-color: #000;
--ngx-diff-selected-line-background-color: #d6f1ff;
+ --ngx-diff-progress-background-color: #dfdfdf;
+ --ngx-diff-progress-foreground-color: #aaaaaa;
+
--ngx-diff-line-number-width: 2rem;
--ngx-diff-line-number-width-dynamic-padding: 1rem;
--ngx-diff-border-width: 1px;
@@ -128,6 +131,88 @@ Then use this class in your desired component in your HTML template:
It is recommended to use these settings rather than attempt to override styles based upon DOM structure or class names that are internal details that may change.
+## Run diff in Web Worker
+
+**New in version 13.1.0**
+
+Very large diffs can take over 100ms to compute in Js. To avoid locking the UI main thread, you can configure this to run in a Web worker as follows:
+
+1. In your application create a new Web worker:
+
+```bash
+npx ng g web-worker web-workers/DiffWebWorker
+```
+
+2. In your newly created file `diff-web-worker.worker.ts`, add implementation that will compute the diff:
+
+```js
+///
+
+import { DiffMatchPatch } from 'diff-match-patch-ts';
+
+addEventListener('message', ({ data }) => {
+ try {
+ if (typeof data.before !== 'string' || typeof data.after !== 'string') {
+ throw new TypeError('Input data for diffing must be strings.');
+ }
+
+ const dmp = new DiffMatchPatch();
+ const diffs = dmp.diff_lineMode(data.before, data.after);
+
+ postMessage({ id: data.id, status: 'success', diffs });
+ } catch (error: any) {
+ postMessage({ id: data.id, status: 'error', error: { message: error.message, stack: error.stack } });
+ }
+});
+```
+
+3. Create a factory service that will create the Web worker:
+
+```bash
+npx ng g s services/diff-web-worker-factory/DiffWebWorkerFactory
+```
+
+And implement the `IDiffWebWorkerFactory` interface in the generated diff-web-worker-factory.service.ts:
+
+```js
+import { Injectable } from '@angular/core';
+import { IDiffWebWorkerFactory } from 'ngx-diff';
+
+@Injectable()
+export class DiffWebWorkerFactoryService implements IDiffWebWorkerFactory {
+ public createWorker(): Worker | undefined {
+ if (typeof Worker !== 'undefined') {
+ return new Worker(new URL('../../web-workers/diff-web-worker.worker', import.meta.url));
+ } else {
+ return undefined;
+ }
+ }
+}
+
+```
+
+4. Specify the factory service in your `main.ts` file:
+
+```js
+import { NGX_DIFF_WEB_WORKER_FACTORY } from 'ngx-diff';
+import { DiffWebWorkerFactoryService } from './app/services/diff-web-worker-factory/diff-web-worker-factory.service';
+
+if (environment.production) {
+ enableProdMode();
+}
+
+bootstrapApplication(AppComponent, {
+ providers: [
+ importProvidersFrom(BrowserModule, AppRoutingModule),
+ provideZonelessChangeDetection(),
+ /* Add this line below: */
+ { provide: NGX_DIFF_WEB_WORKER_FACTORY, useClass: DiffWebWorkerFactoryService },
+ ],
+}).catch((err) => console.error(err));
+```
+
+Now `ngx-diff` will detect that you have a Web worker factory configured and use that to run the diff on a Web worker instead of the main UI thread.
+
## Version history
| Angular Version | ngx-diff Version |
diff --git a/angular.json b/angular.json
index 302db82..42a0c10 100644
--- a/angular.json
+++ b/angular.json
@@ -24,7 +24,8 @@
"sourceMap": true,
"optimization": false,
"namedChunks": true,
- "browser": "src/main.ts"
+ "browser": "src/main.ts",
+ "webWorkerTsConfig": "tsconfig.worker.json"
},
"configurations": {
"production": {
@@ -78,7 +79,10 @@
}
},
"test": {
- "builder": "@angular/build:unit-test"
+ "builder": "@angular/build:unit-test",
+ "options": {
+ "webWorkerTsConfig": "tsconfig.worker.json"
+ }
}
}
},
diff --git a/package-lock.json b/package-lock.json
index 71918d4..b163ffe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,7 +25,7 @@
"@angular-eslint/builder": "^21.0.0",
"@angular-eslint/eslint-plugin": "^21.0.0",
"@angular-eslint/eslint-plugin-template": "^21.0.0",
- "@angular-eslint/schematics": "21.0.0",
+ "@angular-eslint/schematics": "21.0.1",
"@angular-eslint/template-parser": "^21.0.0",
"@angular/build": "^21.0.0",
"@angular/cli": "^21.0.0",
@@ -280,13 +280,13 @@
}
},
"node_modules/@angular-devkit/architect": {
- "version": "0.2100.0",
- "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2100.0.tgz",
- "integrity": "sha512-BNt6Rw53WauCw31ku/r/ksVIY+Pi8XZptsSUIHiDUeqB2iZOWu4L3c5kuDGmoGkGByY588H48hfR2MgIpBhgAg==",
+ "version": "0.2100.1",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2100.1.tgz",
+ "integrity": "sha512-MLxTT6EE7NHuCen9yGdv9iT2vtB/fAdXTRnulOWfVa/SVmGoKawBGCNOAPpI2yA8Fb/D5xlU6ThS1ggDsiCqrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-devkit/core": "21.0.0",
+ "@angular-devkit/core": "21.0.1",
"rxjs": "7.8.2"
},
"engines": {
@@ -296,9 +296,9 @@
}
},
"node_modules/@angular-devkit/core": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.0.0.tgz",
- "integrity": "sha512-d3n5GvrwqN1AUkWE3Wd8rrdY2u6/5bzorlZVT5W4CcH7ekAIoMu4SBTbSJ7bfRe/l2z/A1WZ6hFlnQzLclOjJA==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.0.1.tgz",
+ "integrity": "sha512-AGdAu0hV2TLCWYHiyVSxUFbpR2chO+xA4OkRrG2YirQGcqJTmr651C4rWDkheWqeWDxMicZklqKaTw66mNSUkw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -324,13 +324,13 @@
}
},
"node_modules/@angular-devkit/schematics": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.0.0.tgz",
- "integrity": "sha512-8zwXp8OTzJO3IY3Ge3lLqXokNAtQy6kM1FeTyPT20M+0AQHTX9WJlGaYEWdLYI9WwNPWy1/Iq6AaZNcR5phPpw==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.0.1.tgz",
+ "integrity": "sha512-3koB1xJNkqMg7g6JwH2rhQO268WjnPVA852lwoLW7wzSZRpJH0kHtZsnY9FYOC2kbmAGnCWWbnPLJ5/T1wemoA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-devkit/core": "21.0.0",
+ "@angular-devkit/core": "21.0.1",
"jsonc-parser": "3.3.1",
"magic-string": "0.30.19",
"ora": "9.0.0",
@@ -343,9 +343,9 @@
}
},
"node_modules/@angular-eslint/builder": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-21.0.0.tgz",
- "integrity": "sha512-+d2H2MBo6DgaZnTZ5v6arqrqYrP0qyQNcpQ6UlECGFwx5SC2zgQFHfKjJXWBaH8/nv2QF/zNXeV9/OEpmNw4PA==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-21.0.1.tgz",
+ "integrity": "sha512-6BqpmW0XvjTOs2YOHwzeZcQ32eL8vs8SCHjt1cQnq1+libOVDXky1eb/jRs7ouyA49UagLDoM34K1kjrYo8P3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -358,21 +358,21 @@
}
},
"node_modules/@angular-eslint/bundled-angular-compiler": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-21.0.0.tgz",
- "integrity": "sha512-jHO8ifzv+E/kkDzqrIZ5NCbLlLh9fuWoiuBmcjMdRJDDz6bjKDREuouqflvbzxIPxf6pAwI11yCn2nWSEMEYhA==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-21.0.1.tgz",
+ "integrity": "sha512-Kb59SopkJ2sDgZSysL+ZqcfqM2cbK+gciAyHljkrCUsqo66eEq5KCZUU//RVoo4MHi+qL/dFy54JG/+A/35xcQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@angular-eslint/eslint-plugin": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-21.0.0.tgz",
- "integrity": "sha512-Ry6MPJey2MCE7JbhZ9KXJmao4WpH9BarBZL8tcmaqbRQqb9N8KRQlWNMAtPdfKN/y1P5m87PwL/QYVVRt2A34w==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-21.0.1.tgz",
+ "integrity": "sha512-tSb5qgIwoMrX3Z17dSsHrNFWrgBWafxK7IQudU0RXxdzq6joq1qDrzHwLT3Jn+Y6ocn0jdavAefEGHAhomCjcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-eslint/bundled-angular-compiler": "21.0.0",
- "@angular-eslint/utils": "21.0.0",
+ "@angular-eslint/bundled-angular-compiler": "21.0.1",
+ "@angular-eslint/utils": "21.0.1",
"ts-api-utils": "^2.1.0"
},
"peerDependencies": {
@@ -382,19 +382,19 @@
}
},
"node_modules/@angular-eslint/eslint-plugin-template": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-21.0.0.tgz",
- "integrity": "sha512-N05NMslhY+isR/aSPnNjaC+l5OV1dDrGnxPETQ7Oxag3MyPUrwLv/HRWC+DxSJkJMAQT7GnxxNx4JyfHQWusFA==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-21.0.1.tgz",
+ "integrity": "sha512-DF1WEMalbV1hNKxbu3nwK1yUa+E2FQpNz0KDORU65/vdCffeuftCetobrsAS7zDgJ6FO+Fsb+ZeCzNKEhhh1vA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-eslint/bundled-angular-compiler": "21.0.0",
- "@angular-eslint/utils": "21.0.0",
+ "@angular-eslint/bundled-angular-compiler": "21.0.1",
+ "@angular-eslint/utils": "21.0.1",
"aria-query": "5.3.2",
"axobject-query": "4.1.0"
},
"peerDependencies": {
- "@angular-eslint/template-parser": "21.0.0",
+ "@angular-eslint/template-parser": "21.0.1",
"@typescript-eslint/types": "^7.11.0 || ^8.0.0",
"@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
"eslint": "^8.57.0 || ^9.0.0",
@@ -402,30 +402,30 @@
}
},
"node_modules/@angular-eslint/schematics": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-21.0.0.tgz",
- "integrity": "sha512-lu28D3H/LS+7kf95AP3tk6GYxOkhePxeF2DuUm/xDESmZjecmi8sUB7PmApaBx/vwae3afkBIHHNGy2b2Hxa3g==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-21.0.1.tgz",
+ "integrity": "sha512-IdtGdRPuJctHuiZ8v8SN3MqWiUa3cD9Q5jFvIRkAkjpHXXmTk5PYelSjUP8UX/zfUfFkxHXghasTmJd2+252OQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@angular-devkit/core": ">= 21.0.0 < 22.0.0",
"@angular-devkit/schematics": ">= 21.0.0 < 22.0.0",
- "@angular-eslint/eslint-plugin": "21.0.0",
- "@angular-eslint/eslint-plugin-template": "21.0.0",
+ "@angular-eslint/eslint-plugin": "21.0.1",
+ "@angular-eslint/eslint-plugin-template": "21.0.1",
"ignore": "7.0.5",
"semver": "7.7.3",
"strip-json-comments": "3.1.1"
}
},
"node_modules/@angular-eslint/template-parser": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-21.0.0.tgz",
- "integrity": "sha512-bkqFdIbK7CB0TZVcfxr+xRQdoZ+07S5DlrurmsT1zu4ttfYd5KY1QgtngpMOvBOlPBy3uKQ23QkW/EQ4RB1OxA==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-21.0.1.tgz",
+ "integrity": "sha512-1KocmjmBP0qlKQGRhRGN0MGvLxf1q2KDWbvzn7ZGdQrIDLC/hFJ8YmnOWsPrM9RxiZi0o5BxCCu9D7KlbthxIg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "@angular-eslint/bundled-angular-compiler": "21.0.0",
+ "@angular-eslint/bundled-angular-compiler": "21.0.1",
"eslint-scope": "^9.0.0"
},
"peerDependencies": {
@@ -434,13 +434,13 @@
}
},
"node_modules/@angular-eslint/utils": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-21.0.0.tgz",
- "integrity": "sha512-Ner0su/FKzwOlvVWn61XCVn3g6JoQY3+Cuq1qilMYstO1HwuGJY6CZIsih8RMim0cSpSPO+VlBtg2V+/Czi9yw==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-21.0.1.tgz",
+ "integrity": "sha512-tovWIDiEsfSAsPWH+/wL9Hfl/Hc+2j2IP+Z85I6uWTbynLVdyURx8gmJjKBUTSCmcyrgBnTbnnlr4DTM6/aFOg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-eslint/bundled-angular-compiler": "21.0.0"
+ "@angular-eslint/bundled-angular-compiler": "21.0.1"
},
"peerDependencies": {
"@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
@@ -449,9 +449,9 @@
}
},
"node_modules/@angular/animations": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.0.0.tgz",
- "integrity": "sha512-9AX4HFJmSP8SFNiweKNxasBzn3zbL3xRtwaUxw1I+x/WAzubm4ZziLnXqb+tai7C4UmwV+9XDlRVPfw5WxJ9zg==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.0.1.tgz",
+ "integrity": "sha512-P7i/jpNnzXwo0vHEG0cDXYojwTz0WQlXJHrmOJzLVveyfcFwgXYXJxhGGUI2+k21YrlJTKkR/4QZTEJ0GP0f8Q==",
"license": "MIT",
"peer": true,
"dependencies": {
@@ -461,18 +461,18 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
- "@angular/core": "21.0.0"
+ "@angular/core": "21.0.1"
}
},
"node_modules/@angular/build": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.0.0.tgz",
- "integrity": "sha512-TobXT9fXZVee1yULlcOVowOurCUoJlku8st5vzkRZekP520qRjBSEbIk8V2emkFbzgzOeJUtXv1pvrBY7yAYhQ==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.0.1.tgz",
+ "integrity": "sha512-AQFZWG5TtujCRs7ncajeBZpl/hLBKkuF0lZSziJL8tsgBru0hz0OobOkEuS/nb3FuCRQfva8YP2EPhLdcuo50g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "2.3.0",
- "@angular-devkit/architect": "0.2100.0",
+ "@angular-devkit/architect": "0.2100.1",
"@babel/core": "7.28.4",
"@babel/helper-annotate-as-pure": "7.27.3",
"@babel/helper-split-export-declaration": "7.24.7",
@@ -515,7 +515,7 @@
"@angular/platform-browser": "^21.0.0",
"@angular/platform-server": "^21.0.0",
"@angular/service-worker": "^21.0.0",
- "@angular/ssr": "^21.0.0",
+ "@angular/ssr": "^21.0.1",
"karma": "^6.4.0",
"less": "^4.2.0",
"ng-packagr": "^21.0.0",
@@ -565,19 +565,19 @@
}
},
"node_modules/@angular/cli": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.0.0.tgz",
- "integrity": "sha512-713DfTD/ThIy/BOmZ+8zhXo/OhPE9jYaAS0UhXVhtp2ptqzRqSzLvW9fWgtqP4ITAqulOoitiWPLXxOEQ2Cixw==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.0.1.tgz",
+ "integrity": "sha512-i0+7jwf19D73yAzR/lL4+eKVhooM+J055qfSaJWL5QLCF9/JSSjMPCG8I/qIGNdVr+lVmWvvxqpt7O7kR3zfUw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-devkit/architect": "0.2100.0",
- "@angular-devkit/core": "21.0.0",
- "@angular-devkit/schematics": "21.0.0",
+ "@angular-devkit/architect": "0.2100.1",
+ "@angular-devkit/core": "21.0.1",
+ "@angular-devkit/schematics": "21.0.1",
"@inquirer/prompts": "7.9.0",
"@listr2/prompt-adapter-inquirer": "3.0.5",
"@modelcontextprotocol/sdk": "1.20.1",
- "@schematics/angular": "21.0.0",
+ "@schematics/angular": "21.0.1",
"@yarnpkg/lockfile": "1.1.0",
"algoliasearch": "5.40.1",
"ini": "5.0.0",
@@ -601,9 +601,9 @@
}
},
"node_modules/@angular/common": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.0.0.tgz",
- "integrity": "sha512-uFvQDYU5X5nEnI9C4Bkdxcu4aIzNesGLJzmFlnwChVxB4BxIRF0uHL0oRhdkInGTIzPDJPH4nF6B/22c5gDVqA==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.0.1.tgz",
+ "integrity": "sha512-EqdTGpFp7PVdTVztO7TB6+QxdzUbYXKKT2jwG2Gg+PIQZ2A8XrLPRmGXyH/DLlc5IhnoJlLbngmBRCLCO4xWog==",
"license": "MIT",
"peer": true,
"dependencies": {
@@ -613,14 +613,14 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
- "@angular/core": "21.0.0",
+ "@angular/core": "21.0.1",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/compiler": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.0.0.tgz",
- "integrity": "sha512-6jCH3UYga5iokj5F40SR4dlwo9ZRMkT8YzHCTijwZuDX9zvugp9jPof092RvIeNsTvCMVfGWuM9yZ1DRUsU/yg==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.0.1.tgz",
+ "integrity": "sha512-YRzHpThgCaC9b3xzK1Wx859ePeHEPR7ewQklUB5TYbpzVacvnJo38PcSAx/nzOmgX9y4mgyros6LzECmBb8d8w==",
"license": "MIT",
"peer": true,
"dependencies": {
@@ -631,9 +631,9 @@
}
},
"node_modules/@angular/compiler-cli": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.0.0.tgz",
- "integrity": "sha512-KTXp+e2UPGyfFew6Wq95ULpHWQ20dhqkAMZ6x6MCYfOe2ccdnGYsAbLLmnWGmSg5BaOI4B0x/1XCFZf/n6WDgA==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.0.1.tgz",
+ "integrity": "sha512-BxGLtL5bxlaaAs/kSN4oyXhMfvzqsj1Gc4Jauz39R4xtgOF5cIvjBtj6dJ9mD3PK0s6zaFi7WYd0YwWkxhjgMA==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -655,7 +655,7 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
- "@angular/compiler": "21.0.0",
+ "@angular/compiler": "21.0.1",
"typescript": ">=5.9 <6.0"
},
"peerDependenciesMeta": {
@@ -665,9 +665,9 @@
}
},
"node_modules/@angular/core": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.0.0.tgz",
- "integrity": "sha512-bqi8fT4csyITeX8vdN5FJDBWx5wuWzdCg4mKSjHd+onVzZLyZ8bcnuAKz4mklgvjvwuXoRYukmclUurLwfq3Rg==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.0.1.tgz",
+ "integrity": "sha512-z0G9Bwzgqr0fQVbtMgqwl+SbbiqtJD7I2xT6U5p45LetKHojcfigH29dxi/vqALPwEdgb2nSIx7RqVhoyynraQ==",
"license": "MIT",
"peer": true,
"dependencies": {
@@ -677,9 +677,9 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
- "@angular/compiler": "21.0.0",
+ "@angular/compiler": "21.0.1",
"rxjs": "^6.5.3 || ^7.4.0",
- "zone.js": "~0.15.0"
+ "zone.js": "~0.15.0 || ~0.16.0"
},
"peerDependenciesMeta": {
"@angular/compiler": {
@@ -691,9 +691,9 @@
}
},
"node_modules/@angular/forms": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.0.0.tgz",
- "integrity": "sha512-kcudwbZs/ddKqaELz4eEW9kOGCsX61qsf9jkQsGTARBEOUcU2K+rM6mX5sTf9azHvQ9wlX4N36h0eYzBA4Y4Qg==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.0.1.tgz",
+ "integrity": "sha512-BVFPuKjxkzjzKMmpc6KxUKICpVs6J2/KzA4HjtPp/UKvdZPe8dj8vIXuc9pGf8DA4XdkjCwvv8szCgzTWi02LQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -702,17 +702,17 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
- "@angular/common": "21.0.0",
- "@angular/core": "21.0.0",
- "@angular/platform-browser": "21.0.0",
+ "@angular/common": "21.0.1",
+ "@angular/core": "21.0.1",
+ "@angular/platform-browser": "21.0.1",
"@standard-schema/spec": "^1.0.0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/language-service": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.0.0.tgz",
- "integrity": "sha512-onJI3CzNSszcXK0/zVS66IDfaZpTVUdkduZTqth2w8CNaBkG6N/g9wleUVLwarx1+Vy4c4Fqr+gb85QkeGy2aQ==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.0.1.tgz",
+ "integrity": "sha512-+QohcgWbgrsPsHFhbie1ZQaNsnoBpuVK7479WZXPyFiw4PWEceNuF0hSr9yrSNEh/kvgCu9BfJSzVf7w5Yj39A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -720,9 +720,9 @@
}
},
"node_modules/@angular/platform-browser": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.0.0.tgz",
- "integrity": "sha512-KQrANla4RBLhcGkwlndqsKzBwVFOWQr1640CfBVjj2oz4M3dW5hyMtXivBACvuwyUhYU/qJbqlDMBXl/OUSudQ==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.0.1.tgz",
+ "integrity": "sha512-68StH9HILKUqNhQKz6KKNHzpgk1n88CIusWlmJvnb0l6iWGf3ydq5lTMKAKiZQmSDAVP5unTGfNvIkh59GRyVg==",
"license": "MIT",
"peer": true,
"dependencies": {
@@ -732,9 +732,9 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
- "@angular/animations": "21.0.0",
- "@angular/common": "21.0.0",
- "@angular/core": "21.0.0"
+ "@angular/animations": "21.0.1",
+ "@angular/common": "21.0.1",
+ "@angular/core": "21.0.1"
},
"peerDependenciesMeta": {
"@angular/animations": {
@@ -743,9 +743,9 @@
}
},
"node_modules/@angular/platform-browser-dynamic": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.0.0.tgz",
- "integrity": "sha512-H7nfgQvtzl242Tjs34k20XQC3ZNssJCCvYkGTkVowR61khsX87OE5ggKqTSnLiqq1+OoR29hyvvqn5e9truS7w==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.0.1.tgz",
+ "integrity": "sha512-TzCKf3p1NBK1NYoPJXLScSjVeiQ52DaXf9gweNUGtCmX3EkVKf1sx4Ny1x4DxaTwB5XZn+O+L3nVLstPBj7UGA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -754,16 +754,16 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
- "@angular/common": "21.0.0",
- "@angular/compiler": "21.0.0",
- "@angular/core": "21.0.0",
- "@angular/platform-browser": "21.0.0"
+ "@angular/common": "21.0.1",
+ "@angular/compiler": "21.0.1",
+ "@angular/core": "21.0.1",
+ "@angular/platform-browser": "21.0.1"
}
},
"node_modules/@angular/router": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.0.0.tgz",
- "integrity": "sha512-ARx1R2CmTgAezlMkUpV40V4T/IbXhL7dm4SuMVKbuEOsCKZC0TLOSSTsGYY7HKem45JHlJaByv819cJnabFgBg==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.0.1.tgz",
+ "integrity": "sha512-EnNbiScESZ0op9XS9qUNncWc1UcSYy90uCbDMVTTChikZt9b+e19OusFMf50zecb96VMMz+BzNY1see7Rmvx4g==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -772,9 +772,9 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
- "@angular/common": "21.0.0",
- "@angular/core": "21.0.0",
- "@angular/platform-browser": "21.0.0",
+ "@angular/common": "21.0.1",
+ "@angular/core": "21.0.1",
+ "@angular/platform-browser": "21.0.1",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
@@ -1607,9 +1607,9 @@
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
- "version": "1.0.17",
- "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.17.tgz",
- "integrity": "sha512-LCC++2h8pLUSPY+EsZmrrJ1EOUu+5iClpEiDhhdw3zRJpPbABML/N5lmRuBHjxtKm9VnRcsUzioyD0sekFMF0A==",
+ "version": "1.0.19",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.19.tgz",
+ "integrity": "sha512-QW5/SM2ARltEhoKcmRI1LoLf3/C7dHGswwCnfLcoMgqurBT4f8GvwXMgAbK/FwcxthmJRK5MGTtddj0yQn0J9g==",
"dev": true,
"funding": [
{
@@ -3455,44 +3455,6 @@
"@tybys/wasm-util": "^0.10.1"
}
},
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/@npmcli/agent": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz",
@@ -3597,9 +3559,9 @@
}
},
"node_modules/@npmcli/git/node_modules/proc-log": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz",
- "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
+ "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
"dev": true,
"license": "ISC",
"engines": {
@@ -3669,9 +3631,9 @@
}
},
"node_modules/@npmcli/package-json/node_modules/proc-log": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz",
- "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
+ "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
"dev": true,
"license": "ISC",
"engines": {
@@ -3769,9 +3731,9 @@
}
},
"node_modules/@npmcli/run-script/node_modules/proc-log": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz",
- "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
+ "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
"dev": true,
"license": "ISC",
"engines": {
@@ -4761,14 +4723,14 @@
"license": "MIT"
},
"node_modules/@schematics/angular": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.0.0.tgz",
- "integrity": "sha512-50eEsBaT++Gwr+5FAhaKIzTUjpE1DJAwmE5QwtogbTnr2viZc8CsbFOfuMrokQbgdcXRvbkBDPXgO15STMcDRQ==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.0.1.tgz",
+ "integrity": "sha512-m7Z/gykPxOyC5Gs9nkFkGwYTc5xLNLcVkjjZPcYszycwsWBohDREjQLZzRG86AauWFYy8mBUrTF9CD63ZqYHeQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-devkit/core": "21.0.0",
- "@angular-devkit/schematics": "21.0.0",
+ "@angular-devkit/core": "21.0.1",
+ "@angular-devkit/schematics": "21.0.1",
"jsonc-parser": "3.3.1"
},
"engines": {
@@ -5028,17 +4990,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz",
- "integrity": "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz",
+ "integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/type-utils": "8.47.0",
- "@typescript-eslint/utils": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
+ "@typescript-eslint/scope-manager": "8.48.0",
+ "@typescript-eslint/type-utils": "8.48.0",
+ "@typescript-eslint/utils": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -5052,23 +5014,23 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.47.0",
+ "@typescript-eslint/parser": "^8.48.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.47.0.tgz",
- "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz",
+ "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
+ "@typescript-eslint/scope-manager": "8.48.0",
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/typescript-estree": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0",
"debug": "^4.3.4"
},
"engines": {
@@ -5084,14 +5046,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz",
- "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz",
+ "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.47.0",
- "@typescript-eslint/types": "^8.47.0",
+ "@typescript-eslint/tsconfig-utils": "^8.48.0",
+ "@typescript-eslint/types": "^8.48.0",
"debug": "^4.3.4"
},
"engines": {
@@ -5106,14 +5068,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz",
- "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz",
+ "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0"
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -5124,9 +5086,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz",
- "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz",
+ "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5141,15 +5103,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz",
- "integrity": "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz",
+ "integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0",
- "@typescript-eslint/utils": "8.47.0",
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/typescript-estree": "8.48.0",
+ "@typescript-eslint/utils": "8.48.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -5166,9 +5128,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.47.0.tgz",
- "integrity": "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz",
+ "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -5181,21 +5143,20 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.47.0.tgz",
- "integrity": "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz",
+ "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.47.0",
- "@typescript-eslint/tsconfig-utils": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
+ "@typescript-eslint/project-service": "8.48.0",
+ "@typescript-eslint/tsconfig-utils": "8.48.0",
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0",
"debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
+ "tinyglobby": "^0.2.15",
"ts-api-utils": "^2.1.0"
},
"engines": {
@@ -5210,17 +5171,17 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz",
- "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz",
+ "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0"
+ "@typescript-eslint/scope-manager": "8.48.0",
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/typescript-estree": "8.48.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -5235,13 +5196,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz",
- "integrity": "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz",
+ "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.47.0",
+ "@typescript-eslint/types": "8.48.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -5279,16 +5240,16 @@
}
},
"node_modules/@vitest/expect": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.13.tgz",
- "integrity": "sha512-zYtcnNIBm6yS7Gpr7nFTmq8ncowlMdOJkWLqYvhr/zweY6tFbDkDi8BPPOeHxEtK1rSI69H7Fd4+1sqvEGli6w==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.14.tgz",
+ "integrity": "sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@types/chai": "^5.2.2",
- "@vitest/spy": "4.0.13",
- "@vitest/utils": "4.0.13",
+ "@vitest/spy": "4.0.14",
+ "@vitest/utils": "4.0.14",
"chai": "^6.2.1",
"tinyrainbow": "^3.0.3"
},
@@ -5297,13 +5258,13 @@
}
},
"node_modules/@vitest/mocker": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.13.tgz",
- "integrity": "sha512-eNCwzrI5djoauklwP1fuslHBjrbR8rqIVbvNlAnkq1OTa6XT+lX68mrtPirNM9TnR69XUPt4puBCx2Wexseylg==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.14.tgz",
+ "integrity": "sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "4.0.13",
+ "@vitest/spy": "4.0.14",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@@ -5344,9 +5305,9 @@
}
},
"node_modules/@vitest/pretty-format": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.13.tgz",
- "integrity": "sha512-ooqfze8URWbI2ozOeLDMh8YZxWDpGXoeY3VOgcDnsUxN0jPyPWSUvjPQWqDGCBks+opWlN1E4oP1UYl3C/2EQA==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz",
+ "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5357,13 +5318,13 @@
}
},
"node_modules/@vitest/runner": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.13.tgz",
- "integrity": "sha512-9IKlAru58wcVaWy7hz6qWPb2QzJTKt+IOVKjAx5vb5rzEFPTL6H4/R9BMvjZ2ppkxKgTrFONEJFtzvnyEpiT+A==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.14.tgz",
+ "integrity": "sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "4.0.13",
+ "@vitest/utils": "4.0.14",
"pathe": "^2.0.3"
},
"funding": {
@@ -5371,13 +5332,13 @@
}
},
"node_modules/@vitest/snapshot": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.13.tgz",
- "integrity": "sha512-hb7Usvyika1huG6G6l191qu1urNPsq1iFc2hmdzQY3F5/rTgqQnwwplyf8zoYHkpt7H6rw5UfIw6i/3qf9oSxQ==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.14.tgz",
+ "integrity": "sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.0.13",
+ "@vitest/pretty-format": "4.0.14",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -5396,9 +5357,9 @@
}
},
"node_modules/@vitest/spy": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.13.tgz",
- "integrity": "sha512-hSu+m4se0lDV5yVIcNWqjuncrmBgwaXa2utFLIrBkQCQkt+pSwyZTPFQAZiiF/63j8jYa8uAeUZ3RSfcdWaYWw==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.14.tgz",
+ "integrity": "sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==",
"dev": true,
"license": "MIT",
"funding": {
@@ -5406,13 +5367,13 @@
}
},
"node_modules/@vitest/utils": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.13.tgz",
- "integrity": "sha512-ydozWyQ4LZuu8rLp47xFUWis5VOKMdHjXCWhs1LuJsTNKww+pTHQNK4e0assIB9K80TxFyskENL6vCu3j34EYA==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz",
+ "integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.0.13",
+ "@vitest/pretty-format": "4.0.14",
"tinyrainbow": "^3.0.3"
},
"funding": {
@@ -5824,9 +5785,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.8.30",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz",
- "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==",
+ "version": "2.8.31",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz",
+ "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -5864,37 +5825,28 @@
}
},
"node_modules/body-parser": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
- "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz",
+ "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==",
"dev": true,
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
- "debug": "^4.4.0",
+ "debug": "^4.4.3",
"http-errors": "^2.0.0",
- "iconv-lite": "^0.6.3",
+ "iconv-lite": "^0.7.0",
"on-finished": "^2.4.1",
"qs": "^6.14.0",
- "raw-body": "^3.0.0",
- "type-is": "^2.0.0"
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
},
"engines": {
"node": ">=18"
- }
- },
- "node_modules/body-parser/node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
},
- "engines": {
- "node": ">=0.10.0"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/boolbase": {
@@ -5920,6 +5872,7 @@
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
+ "optional": true,
"dependencies": {
"fill-range": "^7.1.1"
},
@@ -6114,9 +6067,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001756",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz",
- "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==",
+ "version": "1.0.30001757",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
+ "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
"dev": true,
"funding": [
{
@@ -8092,9 +8045,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.259",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz",
- "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==",
+ "version": "1.5.262",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz",
+ "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==",
"dev": true,
"license": "ISC"
},
@@ -9110,36 +9063,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/fast-glob": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -9171,16 +9094,6 @@
],
"license": "BSD-3-Clause"
},
- "node_modules/fastq": {
- "version": "1.19.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
- "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -9244,6 +9157,7 @@
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
+ "optional": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -10931,6 +10845,7 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
+ "optional": true,
"engines": {
"node": ">=0.12.0"
}
@@ -11858,9 +11773,9 @@
}
},
"node_modules/make-fetch-happen/node_modules/proc-log": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz",
- "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
+ "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
"dev": true,
"license": "ISC",
"engines": {
@@ -11946,22 +11861,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
+ "optional": true,
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
@@ -11976,6 +11882,7 @@
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
+ "optional": true,
"engines": {
"node": ">=8.6"
},
@@ -12982,9 +12889,9 @@
}
},
"node_modules/node-gyp/node_modules/proc-log": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz",
- "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
+ "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
"dev": true,
"license": "ISC",
"engines": {
@@ -13146,9 +13053,9 @@
}
},
"node_modules/npm-packlist/node_modules/proc-log": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz",
- "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
+ "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
"dev": true,
"license": "ISC",
"engines": {
@@ -13202,9 +13109,9 @@
}
},
"node_modules/npm-registry-fetch/node_modules/proc-log": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz",
- "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
+ "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
"dev": true,
"license": "ISC",
"engines": {
@@ -13338,6 +13245,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -13897,9 +13815,9 @@
}
},
"node_modules/prettier": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
- "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.1.tgz",
+ "integrity": "sha512-RWKXE4qB3u5Z6yz7omJkjWwmTfLdcbv44jUVHC5NpfXwFGzvpQM798FGv/6WNK879tc+Cn0AAyherCl1KjbyZQ==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -14004,27 +13922,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
"node_modules/quick-lru": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
@@ -14378,17 +14275,6 @@
"node": ">= 4"
}
},
- "node_modules/reusify": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
- "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
@@ -14473,13 +14359,13 @@
}
},
"node_modules/rollup-plugin-dts": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.2.3.tgz",
- "integrity": "sha512-UgnEsfciXSPpASuOelix7m4DrmyQgiaWBnvI0TM4GxuDh5FkqW8E5hu57bCxXB90VvR1WNfLV80yEDN18UogSA==",
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.3.0.tgz",
+ "integrity": "sha512-d0UrqxYd8KyZ6i3M2Nx7WOMy708qsV/7fTHMHxCMCBOAe3V/U7OMPu5GkX8hC+cmkHhzGnfeYongl1IgiooddA==",
"dev": true,
"license": "LGPL-3.0-only",
"dependencies": {
- "magic-string": "^0.30.17"
+ "magic-string": "^0.30.21"
},
"engines": {
"node": ">=16"
@@ -14495,6 +14381,16 @@
"typescript": "^4.5 || ^5.0"
}
},
+ "node_modules/rollup-plugin-dts/node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
@@ -14512,30 +14408,6 @@
"node": ">= 18"
}
},
- "node_modules/run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "queue-microtask": "^1.2.2"
- }
- },
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
@@ -15808,6 +15680,7 @@
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
+ "optional": true,
"dependencies": {
"is-number": "^7.0.0"
},
@@ -16830,24 +16703,24 @@
}
},
"node_modules/vitest": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.13.tgz",
- "integrity": "sha512-QSD4I0fN6uZQfftryIXuqvqgBxTvJ3ZNkF6RWECd82YGAYAfhcppBLFXzXJHQAAhVFyYEuFTrq6h0hQqjB7jIQ==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.14.tgz",
+ "integrity": "sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "@vitest/expect": "4.0.13",
- "@vitest/mocker": "4.0.13",
- "@vitest/pretty-format": "4.0.13",
- "@vitest/runner": "4.0.13",
- "@vitest/snapshot": "4.0.13",
- "@vitest/spy": "4.0.13",
- "@vitest/utils": "4.0.13",
- "debug": "^4.4.3",
+ "@vitest/expect": "4.0.14",
+ "@vitest/mocker": "4.0.14",
+ "@vitest/pretty-format": "4.0.14",
+ "@vitest/runner": "4.0.14",
+ "@vitest/snapshot": "4.0.14",
+ "@vitest/spy": "4.0.14",
+ "@vitest/utils": "4.0.14",
"es-module-lexer": "^1.7.0",
"expect-type": "^1.2.2",
"magic-string": "^0.30.21",
+ "obug": "^2.1.1",
"pathe": "^2.0.3",
"picomatch": "^4.0.3",
"std-env": "^3.10.0",
@@ -16870,12 +16743,11 @@
"peerDependencies": {
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
- "@types/debug": "^4.1.12",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
- "@vitest/browser-playwright": "4.0.13",
- "@vitest/browser-preview": "4.0.13",
- "@vitest/browser-webdriverio": "4.0.13",
- "@vitest/ui": "4.0.13",
+ "@vitest/browser-playwright": "4.0.14",
+ "@vitest/browser-preview": "4.0.14",
+ "@vitest/browser-webdriverio": "4.0.14",
+ "@vitest/ui": "4.0.14",
"happy-dom": "*",
"jsdom": "*"
},
@@ -16886,9 +16758,6 @@
"@opentelemetry/api": {
"optional": true
},
- "@types/debug": {
- "optional": true
- },
"@types/node": {
"optional": true
},
diff --git a/package.json b/package.json
index 1d9cd5c..b3899e3 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
"@angular-eslint/builder": "^21.0.0",
"@angular-eslint/eslint-plugin": "^21.0.0",
"@angular-eslint/eslint-plugin-template": "^21.0.0",
- "@angular-eslint/schematics": "21.0.0",
+ "@angular-eslint/schematics": "21.0.1",
"@angular-eslint/template-parser": "^21.0.0",
"@angular/build": "^21.0.0",
"@angular/cli": "^21.0.0",
diff --git a/projects/ngx-diff/package.json b/projects/ngx-diff/package.json
index 55933e2..5e473aa 100644
--- a/projects/ngx-diff/package.json
+++ b/projects/ngx-diff/package.json
@@ -1,6 +1,7 @@
{
"name": "ngx-diff",
"version": "13.0.0",
+ "description": "An Angular component for viewing text diffs.",
"peerDependencies": {
"@angular/common": ">=21.0.0",
"@angular/core": ">=21.0.0",
@@ -16,7 +17,14 @@
"keywords": [
"Angular",
"ng",
- "diff"
+ "ngx",
+ "diff",
+ "difference",
+ "text",
+ "compare",
+ "component",
+ "text-diff",
+ "diff-viewer"
],
"author": "Richard Russell",
"license": "MIT",
diff --git a/projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.html b/projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.html
new file mode 100644
index 0000000..918cdbb
--- /dev/null
+++ b/projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.html
@@ -0,0 +1,5 @@
+
diff --git a/projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.scss b/projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.scss
new file mode 100644
index 0000000..d2118a2
--- /dev/null
+++ b/projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.scss
@@ -0,0 +1,30 @@
+.progress-bar-container {
+ position: relative;
+ width: 100%;
+ height: 4px;
+}
+
+.progress-bar {
+ height: 4px;
+ width: 100%;
+ position: absolute;
+ top: 0;
+ overflow: hidden;
+ background-color: var(--ngx-diff-progress-background-color);
+}
+
+.progress-bar-inner {
+ height: 100%;
+ width: 30%;
+ background-color: var(--ngx-diff-progress-foreground-color);
+ animation: move 1.5s infinite linear;
+}
+
+@keyframes move {
+ 0% {
+ transform: translateX(-100%);
+ }
+ 100% {
+ transform: translateX(300%);
+ }
+}
diff --git a/projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.spec.ts b/projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.spec.ts
new file mode 100644
index 0000000..b0ec41c
--- /dev/null
+++ b/projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ProgressBarComponent } from './progress-bar.component';
+
+describe('ProgressBarComponent', () => {
+ let component: ProgressBarComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ProgressBarComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(ProgressBarComponent);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.ts b/projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.ts
new file mode 100644
index 0000000..bb9f089
--- /dev/null
+++ b/projects/ngx-diff/src/lib/components/progress-bar/progress-bar.component.ts
@@ -0,0 +1,9 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'ngx-progress-bar',
+ imports: [],
+ templateUrl: './progress-bar.component.html',
+ styleUrl: './progress-bar.component.scss',
+})
+export class ProgressBarComponent {}
diff --git a/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.html b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.html
index f4c3b43..832715e 100644
--- a/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.html
+++ b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.html
@@ -1,77 +1,88 @@
-
- @if (title()) {
- {{ title() }}
- }
- +++ {{ diffSummary().numLinesAdded }}
- --- {{ diffSummary().numLinesRemoved }}
-
-@if (isContentEqual()) {
-
-
There are no changes to display.
+@if (isCalculating()) {
+
+ @if (processedDiff().title) {
+ {{ processedDiff().title }}
+ }
+
} @else {
-
-
-
- @for (lineDiff of beforeLines(); track lineDiff.id; let idx = $index) {
-
-
{{ lineDiff.lineNumber | lineNumber }}
-
- }
-
+
+ @if (processedDiff().title) {
+ {{ processedDiff().title }}
+ }
+
+ +++ {{ processedDiff().diffSummary.numLinesAdded }}
+
+
+ --- {{ processedDiff().diffSummary.numLinesRemoved }}
+
+
+ @if (processedDiff().isContentEqual) {
+
+
There are no changes to display.
-
-
+ } @else {
+
+
+
@for (lineDiff of beforeLines(); track lineDiff.id; let idx = $index) {
-
{{ lineDiff.line }}
+
{{ lineDiff.lineNumber | lineNumber }}
}
-
+
-
-
-
- @for (lineDiff of afterLines(); track lineDiff.id; let idx = $index) {
-
-
{{ lineDiff.lineNumber | lineNumber }}
+
+
+ @for (lineDiff of beforeLines(); track lineDiff.id; let idx = $index) {
+
+ }
+
- }
-
-
-
-
+
+
+
@for (lineDiff of afterLines(); track lineDiff.id; let idx = $index) {
-
{{ lineDiff.line }}
+
{{ lineDiff.lineNumber | lineNumber }}
}
-
+
+
+
+
+ @for (lineDiff of afterLines(); track lineDiff.id; let idx = $index) {
+
+ }
+
+
-
+ }
}
diff --git a/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.spec.ts b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.spec.ts
index 6430114..6493673 100644
--- a/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.spec.ts
+++ b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.spec.ts
@@ -1,24 +1,199 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { Diff, DiffOp } from 'diff-match-patch-ts';
import { SideBySideDiffComponent } from './side-by-side-diff.component';
+import { DiffMatchPatchService } from '../../services/diff-match-patch/diff-match-patch.service';
+import { LineSelectEvent } from '../../common/line-select-event';
+import { LineDiffType } from '../../common/line-diff-type';
-describe('SideBySideDiffComponent', () => {
+class DiffMatchPatchServiceMock {
+ computeLineDiff(before: string, after: string): Promise
{
+ const diffs: Diff[] = [];
+ if (before === after) {
+ diffs.push([DiffOp.Equal, before]);
+ } else {
+ diffs.push([DiffOp.Delete, before]);
+ diffs.push([DiffOp.Insert, after]);
+ }
+ return Promise.resolve(diffs);
+ }
+}
+
+describe('SideBySideDiffComponent with Vitest', () => {
let component: SideBySideDiffComponent;
let fixture: ComponentFixture;
+ let dmpMock: DiffMatchPatchServiceMock;
beforeEach(async () => {
+ vi.useFakeTimers();
+
+ dmpMock = new DiffMatchPatchServiceMock();
+
await TestBed.configureTestingModule({
imports: [SideBySideDiffComponent],
+ providers: [{ provide: DiffMatchPatchService, useValue: dmpMock }],
}).compileComponents();
fixture = TestBed.createComponent(SideBySideDiffComponent);
component = fixture.componentInstance;
+ fixture.componentRef.setInput('title', 'my-diff.ts');
fixture.componentRef.setInput('before', 'a');
fixture.componentRef.setInput('after', 'b');
fixture.detectChanges();
+
+ await vi.runAllTimersAsync();
+ await fixture.whenStable();
+ fixture.detectChanges();
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
});
it('should create', () => {
expect(component).toBeTruthy();
});
+
+ it('should set title', () => {
+ const titleEl = fixture.nativeElement.querySelector('.sbs-diff-title-bar');
+ expect(titleEl.textContent).toContain('my-diff.ts');
+ });
+
+ it('should handle identical content', async () => {
+ const text = 'a\nb\nc';
+ fixture.componentRef.setInput('before', text);
+ fixture.componentRef.setInput('after', text);
+
+ fixture.detectChanges();
+ await vi.runAllTimersAsync();
+ fixture.detectChanges();
+
+ expect(component.isCalculating()).toBe(false);
+ expect(component.processedDiff().isContentEqual).toBe(true);
+ expect(component.beforeLines().length).toBe(0);
+ expect(component.afterLines().length).toBe(0);
+ });
+
+ it('should handle different content', async () => {
+ const before = 'a\nb';
+ const after = 'a\nc';
+ vi.spyOn(dmpMock, 'computeLineDiff').mockReturnValue(
+ Promise.resolve([
+ [DiffOp.Equal, 'a\n'],
+ [DiffOp.Delete, 'b'],
+ [DiffOp.Insert, 'c'],
+ ]),
+ );
+
+ fixture.componentRef.setInput('before', before);
+ fixture.componentRef.setInput('after', after);
+
+ fixture.detectChanges();
+ await vi.runAllTimersAsync();
+ fixture.detectChanges();
+
+ expect(component.isCalculating()).toBe(false);
+ expect(component.processedDiff().isContentEqual).toBe(false);
+ expect(component.beforeLines().length).toBe(3);
+ expect(component.afterLines().length).toBe(3);
+
+ // Line 1: equal
+ expect(component.beforeLines()[0].type).toBe(LineDiffType.Equal);
+ expect(component.beforeLines()[0].line).toBe('a');
+ expect(component.afterLines()[0].type).toBe(LineDiffType.Equal);
+ expect(component.afterLines()[0].line).toBe('a');
+
+ // Line 2: delete
+ expect(component.beforeLines()[1].type).toBe(LineDiffType.Delete);
+ expect(component.beforeLines()[1].line).toBe('b');
+ expect(component.afterLines()[1].type).toBe(LineDiffType.Delete);
+ expect(component.afterLines()[1].line).toBe(null);
+
+ // Line 3: insert
+ expect(component.beforeLines()[2].type).toBe(LineDiffType.Insert);
+ expect(component.beforeLines()[2].line).toBe(null);
+ expect(component.afterLines()[2].type).toBe(LineDiffType.Insert);
+ expect(component.afterLines()[2].line).toBe('c');
+ });
+
+ it('should emit line select event', async () => {
+ const emitSpy = vi.spyOn(component.selectedLineChange, 'emit');
+ const before = 'a';
+ const after = 'b';
+ fixture.componentRef.setInput('before', before);
+ fixture.componentRef.setInput('after', after);
+
+ fixture.detectChanges();
+ await vi.runAllTimersAsync();
+ fixture.detectChanges();
+
+ component.selectLine(0);
+
+ const expectedEvent: LineSelectEvent = {
+ index: 0,
+ type: LineDiffType.Delete,
+ lineNumberInOldText: 1,
+ lineNumberInNewText: null,
+ line: 'a',
+ };
+ expect(emitSpy).toHaveBeenCalledWith(expect.objectContaining(expectedEvent));
+ });
+
+ it('should expand placeholder on click', async () => {
+ const before = 'delete-before\nline1\nline2\nline3\nline4\nline5\ndelete-after';
+ const after = 'insert-before\nline1\nline2\nline3\nline4\nline5\ninsert-after';
+
+ vi.spyOn(dmpMock, 'computeLineDiff').mockReturnValue(
+ Promise.resolve([
+ [DiffOp.Delete, 'delete-before'],
+ [DiffOp.Insert, 'insert-before'],
+ [DiffOp.Equal, '\nline1\nline2\nline3\nline4\nline5'], // 6 lines when split
+ [DiffOp.Delete, '\ndelete-after'],
+ [DiffOp.Insert, '\ninsert-after'],
+ ]),
+ );
+
+ fixture.componentRef.setInput('before', before);
+ fixture.componentRef.setInput('after', after);
+ fixture.componentRef.setInput('lineContextSize', 2);
+ fixture.detectChanges();
+ await vi.runAllTimersAsync();
+ fixture.detectChanges();
+
+ const placeholderIndex = component
+ .beforeLines()
+ .findIndex((l) => l.type === LineDiffType.Placeholder);
+ expect(placeholderIndex, 'placeholder should be created').toBeGreaterThan(-1);
+
+ const placeholderLine = component.beforeLines()[placeholderIndex];
+ expect(placeholderLine.line).toContain('hidden lines');
+ const beforeLineCount = component.beforeLines().length;
+
+ component.selectLine(placeholderIndex);
+ fixture.detectChanges();
+
+ expect(component.beforeLines().length, 'number of lines should grow').toBeGreaterThan(
+ beforeLineCount,
+ );
+ const newPlaceholderIndex = component
+ .beforeLines()
+ .findIndex((l) => l.type === LineDiffType.Placeholder);
+
+ expect(newPlaceholderIndex, 'placeholder should be gone').toBe(-1);
+ });
+
+ it('should set dynamic line number width', async () => {
+ const before = 'a\nb\nc\nd\ne\nf\ng\nh\ni\nj'; // 10 lines
+ const after = 'a\nb\nc';
+ fixture.componentRef.setInput('before', before);
+ fixture.componentRef.setInput('after', after);
+ fixture.componentRef.setInput('isDynamicLineNumberWidthEnabled', true);
+ fixture.detectChanges();
+ await vi.runAllTimersAsync();
+ fixture.detectChanges();
+
+ const element = fixture.nativeElement as HTMLElement;
+ // JSDOM doesn't compute styles, so we check if the style property is set.
+ expect(element.style.getPropertyValue('--ngx-diff-line-number-width')).toBeTruthy();
+ });
});
diff --git a/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.ts b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.ts
index 32dabbe..02df9f0 100644
--- a/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.ts
+++ b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.ts
@@ -21,6 +21,9 @@ import { LineDiffType } from '../../common/line-diff-type';
import { NgClass } from '@angular/common';
import { LineSelectEvent } from '../../common/line-select-event';
import { StyleCalculatorService } from '../../services/style-calculator/style-calculator.service';
+import { BehaviorSubject, debounceTime, startWith, switchMap } from 'rxjs';
+import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
+import { ProgressBarComponent } from '../progress-bar/progress-bar.component';
interface IDiffCalculation {
beforeLineNumber: number;
@@ -50,17 +53,11 @@ type LineDiffResult = {
};
};
-const transformToString = (value: string | number | boolean | undefined) => {
- if (typeof value === 'number' || typeof value === 'boolean') {
- return value.toString();
- }
-
- return value ?? '';
-};
+const transformToString = (value: string | number | boolean | undefined) => value?.toString() ?? '';
@Component({
selector: 'ngx-side-by-side-diff',
- imports: [NgClass, LineNumberPipe],
+ imports: [NgClass, LineNumberPipe, ProgressBarComponent],
templateUrl: './side-by-side-diff.component.html',
styleUrl: './side-by-side-diff.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -86,6 +83,12 @@ export class SideBySideDiffComponent implements AfterViewInit {
public readonly before = input.required();
public readonly after = input.required();
+ protected readonly diffData = computed(() => ({
+ title: this.title(),
+ before: transformToString(this.before()),
+ after: transformToString(this.after()),
+ }));
+
/**
* The number of lines of context to provide either side of a DiffOp.Insert or DiffOp.Delete diff.
* Context is taken from a DiffOp.Equal section.
@@ -94,31 +97,43 @@ export class SideBySideDiffComponent implements AfterViewInit {
public readonly selectedLineChange = output();
- public readonly isContentEqual = computed(() => this.lineDiffResult().isContentEqual);
- public readonly diffSummary = computed(() => this.lineDiffResult().diffSummary);
+ private readonly isCalculatingSubject = new BehaviorSubject(false);
+ public readonly isCalculating = toSignal(
+ this.isCalculatingSubject.asObservable().pipe(debounceTime(50)),
+ );
- public readonly beforeLines = signal([]); // computed(() => this.lineDiffResult().beforeLines);
- public readonly afterLines = signal([]); // computed(() => this.lineDiffResult().afterLines);
+ public readonly beforeLines = signal([]);
+ public readonly afterLines = signal([]);
public selectedLineIndex?: number;
- private readonly beforeText = computed(() => transformToString(this.before()));
- private readonly afterText = computed(() => transformToString(this.after()));
-
- private readonly lineDiffs = computed(() => {
- return this.dmp.computeLineDiff(this.beforeText(), this.afterText());
- });
-
- private readonly lineDiffResult = computed(() => {
- return this.calculateLineDiffs(this.lineDiffs());
- });
+ private readonly lineDiffs = toSignal(
+ toObservable(this.diffData).pipe(
+ takeUntilDestroyed(),
+ debounceTime(50),
+ switchMap(async ({ title, before, after }) => {
+ this.isCalculatingSubject.next(true);
+ const diffs = await this.dmp.computeLineDiff(before, after);
+ return { title, diffs };
+ }),
+ startWith({ title: undefined, diffs: [] }),
+ ),
+ { requireSync: true },
+ );
+
+ public readonly processedDiff = computed(() => ({
+ ...this.calculateLineDiffs(this.lineDiffs().diffs),
+ title: this.lineDiffs().title,
+ }));
public constructor() {
effect(() => {
- this.beforeLines.set(this.lineDiffResult().beforeLines);
- });
- effect(() => {
- this.afterLines.set(this.lineDiffResult().afterLines);
+ this.isCalculatingSubject.next(false);
+
+ const { beforeLines, afterLines } = this.processedDiff();
+
+ this.beforeLines.set(beforeLines);
+ this.afterLines.set(afterLines);
});
}
@@ -129,14 +144,14 @@ export class SideBySideDiffComponent implements AfterViewInit {
return;
}
- const lineDiffResult = this.lineDiffResult();
+ const { beforeLines, afterLines } = this.processedDiff();
- let maxLineNumber = lineDiffResult.beforeLines.reduce(
+ let maxLineNumber = beforeLines.reduce(
(maxSoFar, entry) => Math.max(maxSoFar, entry.lineNumber ?? 0),
0,
);
- maxLineNumber = lineDiffResult.afterLines.reduce(
+ maxLineNumber = afterLines.reduce(
(maxSoFar, entry) => Math.max(maxSoFar, entry.lineNumber ?? 0),
maxLineNumber,
);
diff --git a/projects/ngx-diff/src/lib/components/unified-diff/unified-diff.component.html b/projects/ngx-diff/src/lib/components/unified-diff/unified-diff.component.html
index 4c4fa31..0b78826 100644
--- a/projects/ngx-diff/src/lib/components/unified-diff/unified-diff.component.html
+++ b/projects/ngx-diff/src/lib/components/unified-diff/unified-diff.component.html
@@ -1,47 +1,58 @@
-
- @if (title()) {
- {{ title() }}
- }
- +++ {{ diffSummary().numLinesAdded }}
- --- {{ diffSummary().numLinesRemoved }}
-
-@if (isContentEqual()) {
-
-
There are no changes to display.
+@if (isCalculating()) {
+
+ @if (processedDiff().title) {
+ {{ processedDiff().title }}
+ }
+
} @else {
-
-
- @for (lineDiff of calculatedDiff(); track lineDiff.id; let idx = $index) {
-
-
{{ lineDiff.lineNumberInOldText | lineNumber }}
-
{{ lineDiff.lineNumberInNewText | lineNumber }}
-
- }
-
+
+ @if (processedDiff().title) {
+ {{ processedDiff().title }}
+ }
+
+ +++ {{ processedDiff().diffSummary.numLinesAdded }}
+
+
+ --- {{ processedDiff().diffSummary.numLinesRemoved }}
+
+
+ @if (processedDiff().isContentEqual) {
+
+
There are no changes to display.
-
-
- @for (lineDiff of calculatedDiff(); track lineDiff.id) {
+ } @else {
+
+
+ @for (lineDiff of calculatedDiff(); track lineDiff.id; let idx = $index) {
-
{{ lineDiff.line }}
+
{{ lineDiff.lineNumberInOldText | lineNumber }}
+
{{ lineDiff.lineNumberInNewText | lineNumber }}
}
-
+
+
+
+
+ @for (lineDiff of calculatedDiff(); track lineDiff.id) {
+
+ }
+
+
-
+ }
}
diff --git a/projects/ngx-diff/src/lib/components/unified-diff/unified-diff.component.spec.ts b/projects/ngx-diff/src/lib/components/unified-diff/unified-diff.component.spec.ts
index 0d74def..7b42400 100644
--- a/projects/ngx-diff/src/lib/components/unified-diff/unified-diff.component.spec.ts
+++ b/projects/ngx-diff/src/lib/components/unified-diff/unified-diff.component.spec.ts
@@ -10,14 +10,13 @@ import { DiffMatchPatchService } from '../../services/diff-match-patch/diff-matc
class DiffMatchPatchServiceMock {
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars, no-underscore-dangle, id-blacklist, id-match
- public computeLineDiff(_oldText: string, _newText: string): Diff[] {
- return [
+ public computeLineDiff = (_oldText: string, _newText: string): Promise
=>
+ Promise.resolve([
[DiffOp.Equal, 'Diff One A\r\nDiff One B\r\n'],
[DiffOp.Insert, 'Diff Two A\r\nDiff Two B\r\n'],
[DiffOp.Delete, 'Diff Three A\r\nDiff Three B'],
[DiffOp.Equal, 'Diff Four A\r\nDiff Four B\r\n'],
- ];
- }
+ ]);
}
describe('UnifiedDiffComponent', () => {
@@ -25,11 +24,12 @@ describe('UnifiedDiffComponent', () => {
let fixture: ComponentFixture;
beforeEach(async () => {
+ vi.useFakeTimers();
+
await TestBed.configureTestingModule({
imports: [UnifiedDiffComponent, LineNumberPipe],
providers: [{ provide: DiffMatchPatchService, useClass: DiffMatchPatchServiceMock }],
}).compileComponents();
-
fixture = TestBed.createComponent(UnifiedDiffComponent);
component = fixture.componentInstance;
fixture.componentRef.setInput('before', 'a');
@@ -37,47 +37,61 @@ describe('UnifiedDiffComponent', () => {
fixture.detectChanges();
});
- it('should create', () => {
- expect(component).toBeTruthy();
+ afterEach(() => {
+ vi.useRealTimers();
});
- it('should have 8 line diffs', () => {
- expect(component.calculatedDiff().length).toBe(8);
- });
+ describe('after diff calculation', () => {
+ beforeEach(async () => {
+ vi.advanceTimersByTime(100);
+ await fixture.whenStable();
+ fixture.detectChanges();
+ });
- it('should have correct line numbers', () => {
- const leftLineNumbers = component.calculatedDiff().map((x) => x.lineNumberInOldText);
- expect(leftLineNumbers).toEqual([1, 2, null, null, 3, 4, 5, 6]);
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
- const rightLineNumbers = component.calculatedDiff().map((x) => x.lineNumberInNewText);
- expect(rightLineNumbers).toEqual([1, 2, 3, 4, null, null, 5, 6]);
- });
+ it('should have 8 line diffs', () => {
+ expect(component.calculatedDiff().length).toBe(8);
+ });
- it('should have correct class annotations', () => {
- const classes = component.calculatedDiff().map((x) => x.type);
- expect(classes).toEqual([
- LineDiffType.Equal,
- LineDiffType.Equal,
- LineDiffType.Insert,
- LineDiffType.Insert,
- LineDiffType.Delete,
- LineDiffType.Delete,
- LineDiffType.Equal,
- LineDiffType.Equal,
- ]);
- });
+ it('should have correct line numbers', () => {
+ const leftLineNumbers = component.calculatedDiff().map((x) => x.lineNumberInOldText);
+ expect(leftLineNumbers).toEqual([1, 2, null, null, 3, 4, 5, 6]);
- it('should have correct line contents', () => {
- const contents = component.calculatedDiff().map((x) => x.line);
- expect(contents).toEqual([
- 'Diff One A',
- 'Diff One B',
- 'Diff Two A',
- 'Diff Two B',
- 'Diff Three A',
- 'Diff Three B',
- 'Diff Four A',
- 'Diff Four B',
- ]);
+ const rightLineNumbers = component.calculatedDiff().map((x) => x.lineNumberInNewText);
+ expect(rightLineNumbers).toEqual([1, 2, 3, 4, null, null, 5, 6]);
+ });
+
+ it('should have correct class annotations', () => {
+ const classes = component.calculatedDiff().map((x) => x.type);
+ const expected = [
+ LineDiffType.Equal,
+ LineDiffType.Equal,
+ LineDiffType.Insert,
+ LineDiffType.Insert,
+ LineDiffType.Delete,
+ LineDiffType.Delete,
+ LineDiffType.Equal,
+ LineDiffType.Equal,
+ ];
+ expect(classes).toEqual(expected);
+ });
+
+ it('should have correct line contents', () => {
+ const contents = component.calculatedDiff().map((x) => x.line);
+ const expected = [
+ 'Diff One A',
+ 'Diff One B',
+ 'Diff Two A',
+ 'Diff Two B',
+ 'Diff Three A',
+ 'Diff Three B',
+ 'Diff Four A',
+ 'Diff Four B',
+ ];
+ expect(contents).toEqual(expected);
+ });
});
});
diff --git a/projects/ngx-diff/src/lib/components/unified-diff/unified-diff.component.ts b/projects/ngx-diff/src/lib/components/unified-diff/unified-diff.component.ts
index 9eedb99..5d2f2e6 100644
--- a/projects/ngx-diff/src/lib/components/unified-diff/unified-diff.component.ts
+++ b/projects/ngx-diff/src/lib/components/unified-diff/unified-diff.component.ts
@@ -23,6 +23,9 @@ import { DiffMatchPatchService } from '../../services/diff-match-patch/diff-matc
import { LineNumberPipe } from '../../pipes/line-number/line-number.pipe';
import { NgClass } from '@angular/common';
import { StyleCalculatorService } from '../../services/style-calculator/style-calculator.service';
+import { BehaviorSubject, debounceTime, startWith, switchMap } from 'rxjs';
+import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
+import { ProgressBarComponent } from '../progress-bar/progress-bar.component';
type LineDiff = {
id: string;
@@ -43,17 +46,11 @@ type LineDiffResult = {
};
};
-const transformToString = (value: string | number | boolean | undefined) => {
- if (typeof value === 'number' || typeof value === 'boolean') {
- return value.toString();
- }
-
- return value ?? '';
-};
+const transformToString = (value: string | number | boolean | undefined) => value?.toString() ?? '';
@Component({
selector: 'ngx-unified-diff',
- imports: [NgClass, LineNumberPipe],
+ imports: [NgClass, LineNumberPipe, ProgressBarComponent],
templateUrl: './unified-diff.component.html',
styleUrl: './unified-diff.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -79,6 +76,12 @@ export class UnifiedDiffComponent implements AfterViewInit {
public readonly before = input.required();
public readonly after = input.required();
+ protected readonly diffData = computed(() => ({
+ title: this.title(),
+ before: transformToString(this.before()),
+ after: transformToString(this.after()),
+ }));
+
/**
* The number of lines of context to provide either side of a DiffOp.Insert or DiffOp.Delete diff.
* Context is taken from a DiffOp.Equal section.
@@ -89,24 +92,38 @@ export class UnifiedDiffComponent implements AfterViewInit {
public selectedLine?: LineDiff;
- public readonly isContentEqual = computed(() => this.lineDiffResult().isContentEqual);
+ // This needs to be a signal, rather than computed(..) to support alterations when a placeholder is expanded.
public readonly calculatedDiff = signal([]);
- public readonly diffSummary = computed(() => this.lineDiffResult().diffSummary);
-
- protected readonly beforeString = computed(() => transformToString(this.before()));
- protected readonly afterString = computed(() => transformToString(this.after()));
-
- protected readonly diffs = computed(() => {
- return this.dmp.computeLineDiff(this.beforeString(), this.afterString());
- });
- private readonly lineDiffResult = computed(() => {
- return UnifiedDiffComponent.calculateLineDiff(this.diffs(), this.lineContextSize());
- });
+ private readonly isCalculatingSubject = new BehaviorSubject(false);
+
+ protected readonly isCalculating = toSignal(
+ this.isCalculatingSubject.asObservable().pipe(debounceTime(50)),
+ );
+
+ protected readonly diffs = toSignal(
+ toObservable(this.diffData).pipe(
+ takeUntilDestroyed(),
+ debounceTime(50),
+ switchMap(async ({ title, before, after }) => {
+ this.isCalculatingSubject.next(true);
+ const diffs = await this.dmp.computeLineDiff(before, after);
+ return { title, diffs };
+ }),
+ startWith({ title: undefined, diffs: [] }),
+ ),
+ { requireSync: true },
+ );
+
+ protected readonly processedDiff = computed(() => ({
+ ...UnifiedDiffComponent.calculateLineDiff(this.diffs().diffs, this.lineContextSize()),
+ title: this.diffs().title,
+ }));
public constructor() {
effect(() => {
- this.calculatedDiff.set(this.lineDiffResult().calculatedDiff);
+ this.isCalculatingSubject.next(false);
+ this.calculatedDiff.set(this.processedDiff().calculatedDiff);
});
}
@@ -117,7 +134,7 @@ export class UnifiedDiffComponent implements AfterViewInit {
return;
}
- const maxLineNumber = this.lineDiffResult().calculatedDiff.reduce(
+ const maxLineNumber = this.processedDiff().calculatedDiff.reduce(
(maxLineNumber, entry) =>
Math.max(
maxLineNumber,
diff --git a/projects/ngx-diff/src/lib/services/diff-match-patch/diff-match-patch.service.ts b/projects/ngx-diff/src/lib/services/diff-match-patch/diff-match-patch.service.ts
index 2a332a6..1ed4f33 100644
--- a/projects/ngx-diff/src/lib/services/diff-match-patch/diff-match-patch.service.ts
+++ b/projects/ngx-diff/src/lib/services/diff-match-patch/diff-match-patch.service.ts
@@ -1,14 +1,131 @@
import { type Diff, DiffMatchPatch } from 'diff-match-patch-ts';
-import { Injectable } from '@angular/core';
+import { inject, Injectable, InjectionToken, OnDestroy } from '@angular/core';
+
+export interface IDiffWebWorkerFactory {
+ createWorker(): Worker | undefined;
+}
+
+interface DiffWorkerSuccessResponse {
+ id: number;
+ status: 'success';
+ diffs: Diff[];
+}
+
+interface DiffWorkerErrorResponse {
+ id: number;
+ status: 'error';
+ error: { message: string };
+}
+
+export const NGX_DIFF_WEB_WORKER_FACTORY = new InjectionToken('NGX_DIFF_WEB_WORKER_FACTORY');
@Injectable({
providedIn: 'root',
})
-export class DiffMatchPatchService {
+export class DiffMatchPatchService implements OnDestroy {
private readonly dmp = new DiffMatchPatch();
- public computeLineDiff(text1: string, text2: string): Diff[] {
- return this.dmp.diff_lineMode(text1, text2);
+ private readonly promises = new Map<
+ number,
+ { resolve: (value: Diff[]) => void; reject: (reason?: unknown) => void }
+ >();
+
+ private readonly factory = inject(NGX_DIFF_WEB_WORKER_FACTORY, {
+ optional: true,
+ });
+
+ private worker?: Worker;
+ private messageId = 0;
+
+ public computeLineDiff(text1: string, text2: string): Promise {
+ if (this.factory && this.isPotentiallyLongComputation(text1, text2)) {
+ const worker = this.getOrCreateWorker();
+
+ if (worker) {
+ return new Promise((resolve, reject) => {
+ const id = this.messageId++;
+ this.promises.set(id, { resolve, reject });
+
+ try {
+ worker.postMessage({ id, before: text1, after: text2 });
+ } catch (error) {
+ this.promises.delete(id);
+ reject(error);
+ }
+ });
+ }
+ }
+
+ return new Promise((resolve) => resolve(this.dmp.diff_lineMode(text1, text2)));
+ }
+
+ public ngOnDestroy(): void {
+ if (this.worker) {
+ const error = new Error('DiffMatchPatchService is being destroyed.');
+ for (const promise of this.promises.values()) {
+ promise.reject(error);
+ }
+ this.promises.clear();
+
+ this.worker.terminate();
+ this.worker = undefined;
+ }
+ }
+
+ private getOrCreateWorker(): Worker | undefined {
+ if (this.worker) {
+ return this.worker;
+ }
+
+ const worker = this.factory?.createWorker();
+ if (worker) {
+ this.worker = worker;
+ this.worker.onmessage = this.onWorkerMessage.bind(this);
+ this.worker.onerror = this.onWorkerError.bind(this);
+ }
+ return this.worker;
+ }
+
+ private onWorkerMessage({
+ data,
+ }: MessageEvent): void {
+ const promise = this.promises.get(data.id);
+ if (!promise) {
+ console.error('Received a message from web worker with an unknown id.', data);
+ return;
+ }
+
+ if (data.status === 'success') {
+ promise.resolve(data.diffs);
+ } else if (data.status === 'error') {
+ console.error('Web worker error:', data.error);
+ promise.reject(new Error(data.error.message));
+ }
+ this.promises.delete(data.id);
+ }
+
+ private onWorkerError(error: ErrorEvent): void {
+ for (const promise of this.promises.values()) {
+ promise.reject(error);
+ }
+ this.promises.clear();
+
+ if (this.worker) {
+ this.worker.terminate();
+ this.worker = undefined;
+ }
+ }
+
+ private isPotentiallyLongComputation(text1: string, text2: string): boolean {
+ const numLines1 = this.countNewLines(text1);
+ const numLines2 = this.countNewLines(text2);
+
+ return numLines1 + numLines2 > 10000;
+ }
+
+ private countNewLines(input: string): number {
+ const matches = input.match(/\n/g);
+ return matches ? matches.length : 0;
}
}
diff --git a/projects/ngx-diff/styles/default-theme.css b/projects/ngx-diff/styles/default-theme.css
index c7ce9bd..8e28db6 100644
--- a/projects/ngx-diff/styles/default-theme.css
+++ b/projects/ngx-diff/styles/default-theme.css
@@ -26,6 +26,9 @@
--ngx-diff-light-mix-percentage: 4%;
--ngx-diff-heavy-mix-percentage: 10%;
+ --ngx-diff-progress-background-color: #dfdfdf;
+ --ngx-diff-progress-foreground-color: #aaaaaa;
+
--ngx-diff-inserted-background-color: var(--ngx-diff-insert-color);
--ngx-diff-deleted-background-color: var(--ngx-diff-delete-color);
--ngx-diff-equal-background-color: var(--ngx-diff-equal-color);
@@ -73,4 +76,7 @@
--ngx-diff-mix-color: #fff;
--ngx-diff-light-mix-percentage: 4%;
--ngx-diff-heavy-mix-percentage: 10%;
+
+ --ngx-diff-progress-background-color: #354a54;
+ --ngx-diff-progress-foreground-color: #636363;
}
diff --git a/src/app/app.component.html b/src/app/app.component.html
index cf86277..10c6e74 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -34,6 +34,7 @@
(selectedLineChange)="selectedLineChange($event)"
/>
+
+
{
+ let service: DiffWebWorkerFactoryService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(DiffWebWorkerFactoryService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/services/diff-web-worker-factory/diff-web-worker-factory.service.ts b/src/app/services/diff-web-worker-factory/diff-web-worker-factory.service.ts
new file mode 100644
index 0000000..eaa97d3
--- /dev/null
+++ b/src/app/services/diff-web-worker-factory/diff-web-worker-factory.service.ts
@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core';
+import { IDiffWebWorkerFactory } from 'ngx-diff';
+
+@Injectable()
+export class DiffWebWorkerFactoryService implements IDiffWebWorkerFactory {
+ public createWorker(): Worker | undefined {
+ if (typeof Worker !== 'undefined') {
+ return new Worker(new URL('../../web-workers/diff-web-worker.worker', import.meta.url));
+ } else {
+ return undefined;
+ }
+ }
+}
diff --git a/src/app/web-workers/diff-web-worker.worker.ts b/src/app/web-workers/diff-web-worker.worker.ts
new file mode 100644
index 0000000..85f1dc2
--- /dev/null
+++ b/src/app/web-workers/diff-web-worker.worker.ts
@@ -0,0 +1,25 @@
+///
+
+import { DiffMatchPatch } from 'diff-match-patch-ts';
+
+addEventListener('message', ({ data }) => {
+ try {
+ if (typeof data.before !== 'string' || typeof data.after !== 'string') {
+ throw new TypeError('Input data for diffing must be strings.');
+ }
+
+ const dmp = new DiffMatchPatch();
+ console.time('dmp');
+ const diffs = dmp.diff_lineMode(data.before, data.after);
+ console.timeEnd('dmp');
+ console.log(`${data.before.length} ${data.after.length} length`);
+
+ postMessage({ id: data.id, status: 'success', diffs });
+ } catch (error: any) {
+ postMessage({
+ id: data.id,
+ status: 'error',
+ error: { message: error.message, stack: error.stack },
+ });
+ }
+});
diff --git a/src/main.ts b/src/main.ts
index 7c7f48a..70d39cf 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -4,6 +4,8 @@ import { BrowserModule, bootstrapApplication } from '@angular/platform-browser';
import { AppRoutingModule } from './app/app-routing.module';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment';
+import { NGX_DIFF_WEB_WORKER_FACTORY } from 'ngx-diff';
+import { DiffWebWorkerFactoryService } from './app/services/diff-web-worker-factory/diff-web-worker-factory.service';
if (environment.production) {
enableProdMode();
@@ -13,5 +15,6 @@ bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(BrowserModule, AppRoutingModule),
provideZonelessChangeDetection(),
+ { provide: NGX_DIFF_WEB_WORKER_FACTORY, useClass: DiffWebWorkerFactoryService },
],
}).catch((err) => console.error(err));
diff --git a/tsconfig.worker.json b/tsconfig.worker.json
new file mode 100644
index 0000000..0ed426a
--- /dev/null
+++ b/tsconfig.worker.json
@@ -0,0 +1,15 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/worker",
+ "lib": [
+ "es2018",
+ "webworker"
+ ],
+ "types": []
+ },
+ "include": [
+ "src/**/*.worker.ts"
+ ]
+}