From e92ef6a865eae24f3464d259df108a1c7fac2bbb Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Mon, 4 May 2026 18:11:44 -0300 Subject: [PATCH] feat(examples): vanilla custom UI tracked-changes example (SD-2929) Third focused minimal example under examples/editor/custom-ui/. Single file (~140 lines) demonstrating the tracked-changes review panel that pairs naturally with the comments composer. Patterns on display: - ui.trackChanges.observe(snapshot => ...) drives the panel list AND the bulk-action enable state from one subscription. - ui.trackChanges.accept(id) / .reject(id) for per-change decisions; .acceptAll() / .rejectAll() for bulk; .next() / .previous() / .scrollTo(id) for navigation. The snapshot's activeId reflects whichever change is under the cursor. - ui.document.observe + setMode('editing' | 'suggesting') so the user can toggle between editing normally and recording each edit as a tracked change. - modules.trackChanges: { replacements: 'independent' } surfaces typed-over selections as separate insert + delete review items instead of one composite, matching what most review UIs render. - ui.createScope() collects every subscription so the whole surface tears down cleanly on ui.destroy(). Wired into examples/manifest.json and the custom-ui CI smoke matrix. Stacked on caio/sd-2929-vanilla-custom-ui-comments. --- .github/workflows/ci-examples.yml | 2 +- examples/README.md | 1 + .../custom-ui/track-changes/vanilla/README.md | 28 ++++ .../track-changes/vanilla/index.html | 29 ++++ .../track-changes/vanilla/package.json | 18 +++ .../vanilla/public/test_file.docx | Bin 0 -> 16714 bytes .../track-changes/vanilla/src/main.ts | 140 ++++++++++++++++++ .../track-changes/vanilla/src/style.css | 51 +++++++ .../track-changes/vanilla/tsconfig.json | 16 ++ .../track-changes/vanilla/vite.config.ts | 3 + examples/manifest.json | 10 ++ pnpm-lock.yaml | 38 ++--- 12 files changed, 313 insertions(+), 23 deletions(-) create mode 100644 examples/editor/custom-ui/track-changes/vanilla/README.md create mode 100644 examples/editor/custom-ui/track-changes/vanilla/index.html create mode 100644 examples/editor/custom-ui/track-changes/vanilla/package.json create mode 100644 examples/editor/custom-ui/track-changes/vanilla/public/test_file.docx create mode 100644 examples/editor/custom-ui/track-changes/vanilla/src/main.ts create mode 100644 examples/editor/custom-ui/track-changes/vanilla/src/style.css create mode 100644 examples/editor/custom-ui/track-changes/vanilla/tsconfig.json create mode 100644 examples/editor/custom-ui/track-changes/vanilla/vite.config.ts diff --git a/.github/workflows/ci-examples.yml b/.github/workflows/ci-examples.yml index 3c94c34a9c..a7d6e46809 100644 --- a/.github/workflows/ci-examples.yml +++ b/.github/workflows/ci-examples.yml @@ -200,7 +200,7 @@ jobs: strategy: fail-fast: false matrix: - surface: [toolbar, comments] + surface: [toolbar, comments, track-changes] framework: [vanilla] steps: - name: Restore workspace diff --git a/examples/README.md b/examples/README.md index e45968bfec..89c8d97db4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -41,6 +41,7 @@ Build your own toolbar, comments sidebar, and review panel against the `superdoc |---------|------| | [toolbar/vanilla](./editor/custom-ui/toolbar/vanilla) | [docs](https://docs.superdoc.dev/editor/custom-ui/overview) | | [comments/vanilla](./editor/custom-ui/comments/vanilla) | [docs](https://docs.superdoc.dev/editor/custom-ui/comments) | +| [track-changes/vanilla](./editor/custom-ui/track-changes/vanilla) | [docs](https://docs.superdoc.dev/editor/custom-ui/track-changes) | ### Theming diff --git a/examples/editor/custom-ui/track-changes/vanilla/README.md b/examples/editor/custom-ui/track-changes/vanilla/README.md new file mode 100644 index 0000000000..0284753c05 --- /dev/null +++ b/examples/editor/custom-ui/track-changes/vanilla/README.md @@ -0,0 +1,28 @@ +# Custom UI: vanilla tracked changes + +A custom SuperDoc tracked-changes review panel in plain TypeScript. Single file, no framework, copy-paste into your own app. + +## What this teaches + +- `ui.trackChanges.observe(snapshot => ...)` to render the review list from a single subscription. +- `ui.trackChanges.accept(id)` / `.reject(id)` for per-change decisions. +- `ui.trackChanges.acceptAll()` / `.rejectAll()` for bulk decisions. +- `ui.trackChanges.next()` / `.previous()` / `.scrollTo(id)` for navigation, plus the live `activeId` so the panel highlights the change under the cursor. +- `ui.document.observe` + `setMode('editing' | 'suggesting')` so the user can toggle between editing normally and recording tracked changes. +- `ui.createScope()` for lifecycle, plus `ui.destroy()` cascading on tear-down. + +## Run + +```bash +pnpm install +pnpm dev +``` + +Switch to **Suggest** mode and edit the document. Each insertion or deletion becomes a tracked change in the right-hand panel. Accept and reject decisions round-trip through Word. + +The `predev` script builds the local `superdoc` workspace package so type imports resolve from `dist/`. From a published `npm` install this step is unnecessary. + +## See also + +- Docs: [Custom UI > Track changes](https://docs.superdoc.dev/editor/custom-ui/track-changes) +- Sibling examples: [`toolbar/vanilla`](../../toolbar/vanilla), [`comments/vanilla`](../../comments/vanilla) diff --git a/examples/editor/custom-ui/track-changes/vanilla/index.html b/examples/editor/custom-ui/track-changes/vanilla/index.html new file mode 100644 index 0000000000..507432ed46 --- /dev/null +++ b/examples/editor/custom-ui/track-changes/vanilla/index.html @@ -0,0 +1,29 @@ + + + + + + SuperDoc Custom UI: vanilla tracked changes + + +
+
+
+
+
+ +
+ + + diff --git a/examples/editor/custom-ui/track-changes/vanilla/package.json b/examples/editor/custom-ui/track-changes/vanilla/package.json new file mode 100644 index 0000000000..b0a86a05b2 --- /dev/null +++ b/examples/editor/custom-ui/track-changes/vanilla/package.json @@ -0,0 +1,18 @@ +{ + "name": "@superdoc-examples/custom-ui-track-changes-vanilla", + "private": true, + "type": "module", + "scripts": { + "predev": "pnpm --filter superdoc build", + "dev": "vite", + "build": "tsc --noEmit && vite build", + "preview": "vite preview" + }, + "dependencies": { + "superdoc": "workspace:*" + }, + "devDependencies": { + "typescript": "catalog:", + "vite": "catalog:" + } +} diff --git a/examples/editor/custom-ui/track-changes/vanilla/public/test_file.docx b/examples/editor/custom-ui/track-changes/vanilla/public/test_file.docx new file mode 100644 index 0000000000000000000000000000000000000000..b1b8c8f5a7d4e97c704586bf9b1d540054a28f36 GIT binary patch literal 16714 zcmeIaWmH_-wk=$^1$XxZ2$taP?iSqL-GaNjdvJGmcMt9k0fM`Km3?IIle1s@+IxTR zU9Ht>RkdaxCG;_8uQB^TRsswh4FCy%1^@uW01h1n4=)e^fC35tKm|a9stZ_KIT%_w zXe+wf7}{&nx>#Be<$!}yd;@?2$N%r~KlmG{OBl8Cqel|DOL#;aT7gY>kW*3;#qgkB z5tZWUst(dYw;0WAd+iV)Dk_OZV$$^kXSiEuV6G8~lTsD@c+RIDSEKt`2N}yWfjKPR zV|;yE1Ooz1IHU(hbO{4AE!^5#xVGF6DQ4L}elPemOdw(GQJ$#%5yF|@ zGYsPVse98$!W`+{fQoC?#Se(j)ztJ~*QO3q`vj0xt~FS`s8XU%psT^=v1?=Bd?2?% zL6JZG%3q<`lnXh4_+Bw#2PERuFLA&eVp`F~9h2PgqW&iIevQ(Sj*dI5jIdOIOv7KB9GP`kQi_e0OLdkw0W}Et?J{iCkx3Z`akR8vQC3oA-^9Km znWJ>OqmQ)HTU7O(h;x3VsSXsu%^^>@6xnguf#1;=*>4C2S8H4>ufXhldjkW={zu*t z#NafZ0m�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 AZU6uP literal 0 HcmV?d00001 diff --git a/examples/editor/custom-ui/track-changes/vanilla/src/main.ts b/examples/editor/custom-ui/track-changes/vanilla/src/main.ts new file mode 100644 index 0000000000..57cf47ad94 --- /dev/null +++ b/examples/editor/custom-ui/track-changes/vanilla/src/main.ts @@ -0,0 +1,140 @@ +/** + * Custom tracked-changes review panel (vanilla TypeScript), single file. + * + * Patterns to notice: + * + * - `ui.trackChanges.observe(snapshot => ...)` drives the panel + * list AND the bulk-action enable state from one subscription. + * The snapshot carries `items`, `total`, and `activeId`. + * - `ui.trackChanges.accept(id) / .reject(id)` resolve one change. + * `.acceptAll() / .rejectAll()` are the bulk verbs. + * `.next() / .previous() / .scrollTo(id)` drive navigation; + * `activeId` reflects whichever change is under the cursor. + * - `ui.document.observe + setMode('editing' | 'suggesting')` lets + * the user toggle between editing normally and recording each + * edit as a tracked change. Without Suggest mode, the panel is + * empty by design. + * - `ui.createScope()` collects every subscription so the whole + * surface tears down cleanly on `ui.destroy()`. + * + * `trackChanges.replacements: 'independent'` tells the engine to + * surface a typed-over selection as two separate review items + * (insert + delete) instead of one composite. Matches what most + * review UIs want to render. + */ + +import { SuperDoc } from 'superdoc'; +import { createSuperDocUI, type TrackChangesSlice } from 'superdoc/ui'; +import 'superdoc/style.css'; +import './style.css'; + +const superdoc = new SuperDoc({ + selector: '#editor', + document: '/test_file.docx', + documentMode: 'suggesting', + user: { name: 'Alex Rivera', email: 'alex@example.com' }, + modules: { trackChanges: { replacements: 'independent' } }, +}); + +const ui = createSuperDocUI({ superdoc }); +const scope = ui.createScope(); + +const modeEl = document.querySelector('#mode-toggle')!; +const list = document.querySelector('#changes')!; +const prevBtn = document.querySelector('#prev')!; +const nextBtn = document.querySelector('#next')!; +const acceptAllBtn = document.querySelector('#accept-all')!; +const rejectAllBtn = document.querySelector('#reject-all')!; + +// Edit / Suggest toggle. ui.document carries `mode`; setMode flips +// the routed editor and fires the same `document-mode-change` event +// the React wrapper consumes. +modeEl.innerHTML = ` + + +`; +modeEl.addEventListener('click', (e) => { + const t = (e.target as HTMLElement).closest('button[data-mode]'); + if (!t) return; + ui.document.setMode(t.dataset.mode as 'editing' | 'suggesting'); +}); +scope.add( + ui.document.observe((doc) => { + modeEl.querySelectorAll('button[data-mode]').forEach((b) => { + b.classList.toggle('active', b.dataset.mode === doc.mode); + b.disabled = !doc.ready; + }); + }), +); + +// Bulk action wiring. +prevBtn.addEventListener('click', () => { + const id = ui.trackChanges.previous(); + if (id) void ui.trackChanges.scrollTo(id); +}); +nextBtn.addEventListener('click', () => { + const id = ui.trackChanges.next(); + if (id) void ui.trackChanges.scrollTo(id); +}); +acceptAllBtn.addEventListener('click', () => ui.trackChanges.acceptAll()); +rejectAllBtn.addEventListener('click', () => ui.trackChanges.rejectAll()); + +// One subscription drives the list AND the bulk-action enable state. +scope.add( + ui.trackChanges.observe((snapshot) => render(snapshot)), +); + +function render(snapshot: TrackChangesSlice): void { + const empty = snapshot.items.length === 0; + prevBtn.disabled = empty; + nextBtn.disabled = empty; + acceptAllBtn.disabled = empty; + rejectAllBtn.disabled = empty; + + list.innerHTML = ''; + if (empty) { + const li = document.createElement('li'); + li.className = 'empty'; + li.textContent = 'No tracked changes. Switch to Suggest mode and edit the document.'; + list.appendChild(li); + return; + } + + for (const { id, change } of snapshot.items) { + const kind = change.type === 'insert' ? 'insertion' : change.type === 'delete' ? 'deletion' : 'format'; + const author = change.author ?? change.authorEmail ?? 'Unknown'; + const li = document.createElement('li'); + li.className = `card${id === snapshot.activeId ? ' active' : ''}`; + li.dataset.id = id; + li.innerHTML = ` + ${kind} +
${escape(author)}
+ ${change.excerpt ? `
"${escape(change.excerpt)}"
` : ''} +
+ + +
+ `; + li.addEventListener('click', () => void ui.trackChanges.scrollTo(id)); + li.querySelector('[data-action="accept"]')?.addEventListener('click', (e) => { + e.stopPropagation(); + ui.trackChanges.accept(id); + }); + li.querySelector('[data-action="reject"]')?.addEventListener('click', (e) => { + e.stopPropagation(); + ui.trackChanges.reject(id); + }); + list.appendChild(li); + } +} + +function escape(s: string): string { + return s.replace(/[&<>"]/g, (ch) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[ch]!); +} + +const teardown = () => { + ui.destroy(); + superdoc.destroy(); +}; +window.addEventListener('beforeunload', teardown); +if (import.meta.hot) import.meta.hot.dispose(teardown); diff --git a/examples/editor/custom-ui/track-changes/vanilla/src/style.css b/examples/editor/custom-ui/track-changes/vanilla/src/style.css new file mode 100644 index 0000000000..de3678f3bc --- /dev/null +++ b/examples/editor/custom-ui/track-changes/vanilla/src/style.css @@ -0,0 +1,51 @@ +:root { + --bg: #fff; + --bg-muted: #f7f7f8; + --border: #e4e4e7; + --text: #18181b; + --text-muted: #71717a; + --accent: #2563eb; + --accent-soft: #eff6ff; + --insert: #16a34a; + --insert-soft: #dcfce7; + --delete: #dc2626; + --delete-soft: #fee2e2; +} + +* { box-sizing: border-box; } +body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 14px; color: var(--text); background: var(--bg-muted); } +button { font: inherit; cursor: pointer; } + +.app { display: grid; grid-template-columns: 1fr 340px; height: 100vh; } +.editor-area { overflow: auto; padding: 8px 12px; } +.sidebar { background: var(--bg); border-left: 1px solid var(--border); overflow-y: auto; padding: 12px 16px; } + +.mode { display: flex; gap: 4px; margin-bottom: 8px; } +.mode button { padding: 5px 12px; border: 1px solid var(--border); border-radius: 4px; background: transparent; } +.mode button.active { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); } +.mode button:disabled { color: var(--text-muted); cursor: not-allowed; opacity: 0.5; } + +.sidebar-head { margin-bottom: 12px; } +.sidebar-head h2 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin: 0 0 8px; } +.bulk { display: flex; flex-wrap: wrap; gap: 4px; } +.bulk button { padding: 4px 10px; font-size: 12px; border: 1px solid var(--border); border-radius: 4px; background: transparent; } +.bulk button.primary { border-color: var(--accent); color: var(--accent); } +.bulk button:disabled { opacity: 0.5; cursor: not-allowed; } + +.changes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; } +.changes .empty { color: var(--text-muted); font-size: 12px; } +.card { border: 1px solid var(--border); border-radius: 6px; padding: 10px 12px; background: var(--bg); cursor: pointer; } +.card:hover { border-color: var(--accent); } +.card.active { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); } + +.kind { display: inline-block; font-size: 11px; padding: 2px 6px; border-radius: 3px; text-transform: capitalize; margin-bottom: 4px; } +.kind.insertion { background: var(--insert-soft); color: var(--insert); } +.kind.deletion { background: var(--delete-soft); color: var(--delete); } +.kind.format { background: var(--bg-muted); color: var(--text-muted); } + +.author { font-size: 12px; color: var(--text-muted); margin-bottom: 4px; } +.excerpt { border-left: 2px solid var(--accent); padding-left: 8px; font-style: italic; color: var(--text-muted); font-size: 12px; margin: 4px 0; } +.actions { display: flex; gap: 6px; margin-top: 8px; } +.actions button { padding: 4px 10px; font-size: 12px; border: 1px solid var(--border); border-radius: 4px; background: transparent; } +.actions button.primary { border-color: var(--insert); color: var(--insert); } +.actions button.danger { border-color: var(--delete); color: var(--delete); } diff --git a/examples/editor/custom-ui/track-changes/vanilla/tsconfig.json b/examples/editor/custom-ui/track-changes/vanilla/tsconfig.json new file mode 100644 index 0000000000..6d42803dda --- /dev/null +++ b/examples/editor/custom-ui/track-changes/vanilla/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "types": ["vite/client"] + }, + "include": ["src"] +} diff --git a/examples/editor/custom-ui/track-changes/vanilla/vite.config.ts b/examples/editor/custom-ui/track-changes/vanilla/vite.config.ts new file mode 100644 index 0000000000..c049f46e10 --- /dev/null +++ b/examples/editor/custom-ui/track-changes/vanilla/vite.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({}); diff --git a/examples/manifest.json b/examples/manifest.json index 246264f471..8d188f9acf 100644 --- a/examples/manifest.json +++ b/examples/manifest.json @@ -139,6 +139,16 @@ "docs": "https://docs.superdoc.dev/editor/custom-ui/comments", "ci": true }, + { + "id": "editor-custom-ui-track-changes-vanilla", + "title": "Custom tracked-changes panel (vanilla)", + "category": "Editor", + "surface": "Custom UI", + "sourceRepo": "superdoc-dev/superdoc", + "sourcePath": "examples/editor/custom-ui/track-changes/vanilla", + "docs": "https://docs.superdoc.dev/editor/custom-ui/track-changes", + "ci": true + }, { "id": "editor-spell-check-typo-js", "title": "Spell check with Typo.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96cc6f5d94..a739e4bda7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1986,6 +1986,19 @@ importers: specifier: npm:rolldown-vite@7.3.1 version: rolldown-vite@7.3.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + examples/editor/custom-ui/track-changes/vanilla: + dependencies: + superdoc: + specifier: workspace:* + version: link:../../../../../packages/superdoc + devDependencies: + typescript: + specifier: 'catalog:' + version: 5.9.3 + vite: + specifier: npm:rolldown-vite@7.3.1 + version: rolldown-vite@7.3.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + examples/editor/spell-check/language-tool-self-hosted: dependencies: react: @@ -2295,7 +2308,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.7)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vue: specifier: 3.5.32 version: 3.5.32(typescript@5.9.3) @@ -2320,7 +2333,7 @@ importers: version: 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) '@vitest/coverage-v8': specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.2.4(vitest@3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.7)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) concurrently: specifier: 'catalog:' version: 9.2.1 @@ -2344,7 +2357,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.7)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) packages/document-api: {} @@ -33252,25 +33265,6 @@ snapshots: vite: rolldown-vite@7.3.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vue: 3.5.32(typescript@5.9.3) - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': - dependencies: - '@ampproject/remapping': 2.3.0 - '@bcoe/v8-coverage': 1.0.2 - ast-v8-to-istanbul: 0.3.12 - debug: 4.4.3(supports-color@5.5.0) - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.2.0 - magic-string: 0.30.21 - magicast: 0.3.5 - std-env: 3.10.0 - test-exclude: 7.0.2 - tinyrainbow: 2.0.0 - vitest: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - transitivePeerDependencies: - - supports-color - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.7)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@ampproject/remapping': 2.3.0