diff --git a/.changeset/chilled-falcons-battle.md b/.changeset/chilled-falcons-battle.md
new file mode 100644
index 000000000..1a6f228bb
--- /dev/null
+++ b/.changeset/chilled-falcons-battle.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/virtual-core': patch
+---
+
+fix(virtual-core): scroll to index doesn't scroll to bottom correctly
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index d8e41189f..9b72a44ba 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -28,6 +28,8 @@ jobs:
uses: nrwl/nx-set-shas@v4.3.0
with:
main-branch-name: main
+ - name: Install Playwright browsers
+ run: pnpm exec playwright install chromium
- name: Run Checks
run: pnpm run test:pr
preview:
diff --git a/.gitignore b/.gitignore
index cc376bd6c..2a91fb574 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,8 @@ stats.html
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
+
+# Playwright test artifacts
+test-results/
+playwright-report/
+*.log
diff --git a/knip.json b/knip.json
index 639c5df88..f3a69e7a2 100644
--- a/knip.json
+++ b/knip.json
@@ -1,4 +1,4 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
- "ignoreWorkspaces": ["examples/**"]
+ "ignoreWorkspaces": ["examples/**", "packages/react-virtual/e2e/**"]
}
diff --git a/package.json b/package.json
index ee01fe550..370508b6c 100644
--- a/package.json
+++ b/package.json
@@ -11,8 +11,8 @@
"clean": "pnpm --filter \"./packages/**\" run clean",
"preinstall": "node -e \"if(process.env.CI == 'true') {console.log('Skipping preinstall...'); process.exit(1)}\" || npx -y only-allow pnpm",
"test": "pnpm run test:ci",
- "test:pr": "nx affected --targets=test:sherif,test:knip,test:eslint,test:lib,test:types,test:build,build",
- "test:ci": "nx run-many --targets=test:sherif,test:knip,test:eslint,test:lib,test:types,test:build,build",
+ "test:pr": "nx affected --targets=test:sherif,test:knip,test:eslint,test:lib,test:e2e,test:types,test:build,build",
+ "test:ci": "nx run-many --targets=test:sherif,test:knip,test:eslint,test:lib,test:e2e,test:types,test:build,build",
"test:eslint": "nx affected --target=test:eslint",
"test:format": "pnpm run prettier --check",
"test:sherif": "sherif",
@@ -20,6 +20,7 @@
"test:lib:dev": "pnpm run test:lib && nx watch --all -- pnpm run test:lib",
"test:build": "nx affected --target=test:build --exclude=examples/**",
"test:types": "nx affected --target=test:types --exclude=examples/**",
+ "test:e2e": "nx affected --target=test:e2e --exclude=examples/**",
"test:knip": "knip",
"build": "nx affected --target=build --exclude=examples/**",
"build:all": "nx run-many --target=build --exclude=examples/**",
@@ -39,6 +40,7 @@
},
"devDependencies": {
"@changesets/cli": "^2.29.4",
+ "@playwright/test": "^1.53.1",
"@svitejs/changesets-changelog-github-compact": "^1.2.0",
"@tanstack/config": "^0.18.2",
"@testing-library/jest-dom": "^6.6.3",
diff --git a/packages/react-virtual/e2e/app/index.html b/packages/react-virtual/e2e/app/index.html
new file mode 100644
index 000000000..6d5c94aec
--- /dev/null
+++ b/packages/react-virtual/e2e/app/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/react-virtual/e2e/app/main.tsx b/packages/react-virtual/e2e/app/main.tsx
new file mode 100644
index 000000000..e5273bca5
--- /dev/null
+++ b/packages/react-virtual/e2e/app/main.tsx
@@ -0,0 +1,76 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import { useVirtualizer } from '../../src/index'
+
+function getRandomInt(min: number, max: number) {
+ return Math.floor(Math.random() * (max - min + 1)) + min
+}
+
+const randomHeight = (() => {
+ const cache = new Map()
+ return (id: string) => {
+ const value = cache.get(id)
+ if (value !== undefined) {
+ return value
+ }
+ const v = getRandomInt(25, 100)
+ cache.set(id, v)
+ return v
+ }
+})()
+
+const App = () => {
+ const parentRef = React.useRef(null)
+ const rowVirtualizer = useVirtualizer({
+ count: 1002,
+ getScrollElement: () => parentRef.current,
+ estimateSize: () => 50,
+ debug: true,
+ })
+
+ return (
+
+
+
+
+
+ )
+}
+
+ReactDOM.createRoot(document.getElementById('root')!).render()
diff --git a/packages/react-virtual/e2e/app/test/scroll.spec.ts b/packages/react-virtual/e2e/app/test/scroll.spec.ts
new file mode 100644
index 000000000..65ba73b3a
--- /dev/null
+++ b/packages/react-virtual/e2e/app/test/scroll.spec.ts
@@ -0,0 +1,33 @@
+import { expect, test } from '@playwright/test'
+
+const check = () => {
+ const item = document.querySelector('[data-testid="item-1000"]')
+ const container = document.querySelector('#scroll-container')
+
+ if (!item || !container) throw new Error('Elements not found')
+
+ const itemRect = item.getBoundingClientRect()
+ const containerRect = container.getBoundingClientRect()
+ const scrollTop = container.scrollTop
+
+ const top = itemRect.top + scrollTop - containerRect.top
+ const botttom = top + itemRect.height
+
+ const containerBottom = scrollTop + container.clientHeight
+
+ return Math.abs(botttom - containerBottom)
+}
+
+test('scrolls to index 1000', async ({ page }) => {
+ await page.goto('/')
+ await page.click('#scroll-to-1000')
+
+ // Wait for scroll effect (including retries)
+ await page.waitForTimeout(1000)
+
+ await expect(page.locator('[data-testid="item-1000"]')).toBeVisible()
+
+ const delta = await page.evaluate(check)
+ console.log('bootom element detla', delta)
+ expect(delta).toBeLessThan(1.01)
+})
diff --git a/packages/react-virtual/e2e/app/tsconfig.json b/packages/react-virtual/e2e/app/tsconfig.json
new file mode 100644
index 000000000..ad08d6c2b
--- /dev/null
+++ b/packages/react-virtual/e2e/app/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "esModuleInterop": true,
+ "jsx": "react-jsx",
+ "target": "ESNext",
+ "moduleResolution": "Bundler",
+ "module": "ESNext",
+ "resolveJsonModule": true,
+ "allowJs": true,
+ "skipLibCheck": true
+ },
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/react-virtual/e2e/app/vite.config.ts b/packages/react-virtual/e2e/app/vite.config.ts
new file mode 100644
index 000000000..a498bf932
--- /dev/null
+++ b/packages/react-virtual/e2e/app/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+export default defineConfig({
+ root: __dirname,
+ plugins: [react()],
+})
diff --git a/packages/react-virtual/package.json b/packages/react-virtual/package.json
index fef3ac781..f311b1cac 100644
--- a/packages/react-virtual/package.json
+++ b/packages/react-virtual/package.json
@@ -29,7 +29,8 @@
"test:lib": "vitest",
"test:lib:dev": "pnpm run test:lib --watch",
"test:build": "publint --strict",
- "build": "vite build"
+ "build": "vite build",
+ "test:e2e": "playwright test"
},
"type": "module",
"types": "dist/esm/index.d.ts",
diff --git a/packages/react-virtual/playwright.config.ts b/packages/react-virtual/playwright.config.ts
new file mode 100644
index 000000000..6e9f9a5fb
--- /dev/null
+++ b/packages/react-virtual/playwright.config.ts
@@ -0,0 +1,17 @@
+import { defineConfig } from '@playwright/test'
+
+const PORT = 5173
+const baseURL = `http://localhost:${PORT}`
+
+export default defineConfig({
+ testDir: './e2e/app/test',
+ use: {
+ baseURL,
+ },
+ webServer: {
+ command: `VITE_SERVER_PORT=${PORT} vite build --config e2e/app/vite.config.ts && VITE_SERVER_PORT=${PORT} vite preview --config e2e/app/vite.config.ts --port ${PORT}`,
+ url: baseURL,
+ reuseExistingServer: !process.env.CI,
+ stdout: 'pipe',
+ },
+})
diff --git a/packages/react-virtual/tsconfig.json b/packages/react-virtual/tsconfig.json
index 3655b9d05..effe33b1b 100644
--- a/packages/react-virtual/tsconfig.json
+++ b/packages/react-virtual/tsconfig.json
@@ -3,5 +3,10 @@
"compilerOptions": {
"jsx": "react"
},
- "include": ["src", "eslint.config.js", "vite.config.ts"]
+ "include": [
+ "src",
+ "eslint.config.js",
+ "vite.config.ts",
+ "playwright.config.ts"
+ ]
}
diff --git a/packages/virtual-core/src/index.ts b/packages/virtual-core/src/index.ts
index fc6449839..b4794e06e 100644
--- a/packages/virtual-core/src/index.ts
+++ b/packages/virtual-core/src/index.ts
@@ -359,7 +359,6 @@ export class Virtualizer<
scrollElement: TScrollElement | null = null
targetWindow: (Window & typeof globalThis) | null = null
isScrolling = false
- private scrollToIndexTimeoutId: number | null = null
measurementsCache: Array = []
private itemSizeCache = new Map()
private pendingMeasuredCacheIndexes: Array = []
@@ -904,7 +903,7 @@ export class Virtualizer<
toOffset -= size
}
- const maxOffset = this.getTotalSize() - size
+ const maxOffset = this.getTotalSize() + this.options.scrollMargin - size
return Math.max(Math.min(maxOffset, toOffset), 0)
}
@@ -943,19 +942,10 @@ export class Virtualizer<
private isDynamicMode = () => this.elementsCache.size > 0
- private cancelScrollToIndex = () => {
- if (this.scrollToIndexTimeoutId !== null && this.targetWindow) {
- this.targetWindow.clearTimeout(this.scrollToIndexTimeoutId)
- this.scrollToIndexTimeoutId = null
- }
- }
-
scrollToOffset = (
toOffset: number,
{ align = 'start', behavior }: ScrollToOffsetOptions = {},
) => {
- this.cancelScrollToIndex()
-
if (behavior === 'smooth' && this.isDynamicMode()) {
console.warn(
'The `smooth` scroll behavior is not fully supported with dynamic size.',
@@ -972,50 +962,62 @@ export class Virtualizer<
index: number,
{ align: initialAlign = 'auto', behavior }: ScrollToIndexOptions = {},
) => {
- index = Math.max(0, Math.min(index, this.options.count - 1))
-
- this.cancelScrollToIndex()
-
if (behavior === 'smooth' && this.isDynamicMode()) {
console.warn(
'The `smooth` scroll behavior is not fully supported with dynamic size.',
)
}
- const offsetAndAlign = this.getOffsetForIndex(index, initialAlign)
- if (!offsetAndAlign) return
+ index = Math.max(0, Math.min(index, this.options.count - 1))
- const [offset, align] = offsetAndAlign
+ let attempts = 0
+ const maxAttempts = 10
- this._scrollToOffset(offset, { adjustments: undefined, behavior })
+ const tryScroll = (currentAlign: ScrollAlignment) => {
+ if (!this.targetWindow) return
- if (behavior !== 'smooth' && this.isDynamicMode() && this.targetWindow) {
- this.scrollToIndexTimeoutId = this.targetWindow.setTimeout(() => {
- this.scrollToIndexTimeoutId = null
+ const offsetInfo = this.getOffsetForIndex(index, currentAlign)
+ if (!offsetInfo) {
+ console.warn('Failed to get offset for index:', index)
+ return
+ }
+ const [offset, align] = offsetInfo
+ this._scrollToOffset(offset, { adjustments: undefined, behavior })
+
+ this.targetWindow.requestAnimationFrame(() => {
+ const currentOffset = this.getScrollOffset()
+ const afterInfo = this.getOffsetForIndex(index, align)
+ if (!afterInfo) {
+ console.warn('Failed to get offset for index:', index)
+ return
+ }
- const elementInDOM = this.elementsCache.has(
- this.options.getItemKey(index),
- )
+ if (!approxEqual(afterInfo[0], currentOffset)) {
+ scheduleRetry(align)
+ }
+ })
+ }
- if (elementInDOM) {
- const result = this.getOffsetForIndex(index, align)
- if (!result) return
- const [latestOffset] = result
+ const scheduleRetry = (align: ScrollAlignment) => {
+ if (!this.targetWindow) return
- const currentScrollOffset = this.getScrollOffset()
- if (!approxEqual(latestOffset, currentScrollOffset)) {
- this.scrollToIndex(index, { align, behavior })
- }
- } else {
- this.scrollToIndex(index, { align, behavior })
+ attempts++
+ if (attempts < maxAttempts) {
+ if (process.env.NODE_ENV !== 'production' && this.options.debug) {
+ console.info('Schedule retry', attempts, maxAttempts)
}
- })
+ this.targetWindow.requestAnimationFrame(() => tryScroll(align))
+ } else {
+ console.warn(
+ `Failed to scroll to index ${index} after ${maxAttempts} attempts.`,
+ )
+ }
}
+
+ tryScroll(initialAlign)
}
scrollBy = (delta: number, { behavior }: ScrollToOffsetOptions = {}) => {
- this.cancelScrollToIndex()
-
if (behavior === 'smooth' && this.isDynamicMode()) {
console.warn(
'The `smooth` scroll behavior is not fully supported with dynamic size.',
diff --git a/packages/virtual-core/src/utils.ts b/packages/virtual-core/src/utils.ts
index 1bb4615c2..c11b3d38c 100644
--- a/packages/virtual-core/src/utils.ts
+++ b/packages/virtual-core/src/utils.ts
@@ -83,7 +83,7 @@ export function notUndefined(value: T | undefined, msg?: string): T {
}
}
-export const approxEqual = (a: number, b: number) => Math.abs(a - b) <= 1
+export const approxEqual = (a: number, b: number) => Math.abs(a - b) < 1.01
export const debounce = (
targetWindow: Window & typeof globalThis,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1859b3f87..5a1caa0f1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11,6 +11,9 @@ importers:
'@changesets/cli':
specifier: ^2.29.4
version: 2.29.4
+ '@playwright/test':
+ specifier: ^1.53.1
+ version: 1.53.1
'@svitejs/changesets-changelog-github-compact':
specifier: ^1.2.0
version: 1.2.0(encoding@0.1.13)
@@ -3181,6 +3184,11 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
+ '@playwright/test@1.53.1':
+ resolution: {integrity: sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==}
+ engines: {node: '>=18'}
+ hasBin: true
+
'@publint/pack@0.1.2':
resolution: {integrity: sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==}
engines: {node: '>=18'}
@@ -5218,6 +5226,11 @@ packages:
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+ fsevents@2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -6533,6 +6546,16 @@ packages:
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
+ playwright-core@1.53.1:
+ resolution: {integrity: sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ playwright@1.53.1:
+ resolution: {integrity: sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
postcss-loader@8.1.1:
resolution: {integrity: sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==}
engines: {node: '>= 18.12.0'}
@@ -7967,12 +7990,12 @@ snapshots:
'@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.19(@types/node@22.15.29)(less@4.2.0)(sass@1.71.1)(terser@5.29.1))
ansi-colors: 4.1.3
autoprefixer: 10.4.18(postcss@8.4.35)
- babel-loader: 9.1.3(@babel/core@7.26.10)(webpack@5.94.0)
+ babel-loader: 9.1.3(@babel/core@7.26.10)(webpack@5.94.0(esbuild@0.20.1))
babel-plugin-istanbul: 6.1.1
browserslist: 4.25.0
- copy-webpack-plugin: 11.0.0(webpack@5.94.0)
+ copy-webpack-plugin: 11.0.0(webpack@5.94.0(esbuild@0.20.1))
critters: 0.0.22
- css-loader: 6.10.0(webpack@5.94.0)
+ css-loader: 6.10.0(webpack@5.94.0(esbuild@0.20.1))
esbuild-wasm: 0.20.1
fast-glob: 3.3.2
http-proxy-middleware: 2.0.8(@types/express@4.17.22)
@@ -7981,11 +8004,11 @@ snapshots:
jsonc-parser: 3.2.1
karma-source-map-support: 1.4.0
less: 4.2.0
- less-loader: 11.1.0(less@4.2.0)(webpack@5.94.0)
- license-webpack-plugin: 4.0.2(webpack@5.94.0)
+ less-loader: 11.1.0(less@4.2.0)(webpack@5.94.0(esbuild@0.20.1))
+ license-webpack-plugin: 4.0.2(webpack@5.94.0(esbuild@0.20.1))
loader-utils: 3.2.1
magic-string: 0.30.8
- mini-css-extract-plugin: 2.8.1(webpack@5.94.0)
+ mini-css-extract-plugin: 2.8.1(webpack@5.94.0(esbuild@0.20.1))
mrmime: 2.0.0
open: 8.4.2
ora: 5.4.1
@@ -7993,13 +8016,13 @@ snapshots:
picomatch: 4.0.1
piscina: 4.4.0
postcss: 8.4.35
- postcss-loader: 8.1.1(postcss@8.4.35)(typescript@5.2.2)(webpack@5.94.0)
+ postcss-loader: 8.1.1(postcss@8.4.35)(typescript@5.2.2)(webpack@5.94.0(esbuild@0.20.1))
resolve-url-loader: 5.0.0
rxjs: 7.8.1
sass: 1.71.1
- sass-loader: 14.1.1(sass@1.71.1)(webpack@5.94.0)
+ sass-loader: 14.1.1(sass@1.71.1)(webpack@5.94.0(esbuild@0.20.1))
semver: 7.6.0
- source-map-loader: 5.0.0(webpack@5.94.0)
+ source-map-loader: 5.0.0(webpack@5.94.0(esbuild@0.20.1))
source-map-support: 0.5.21
terser: 5.29.1
tree-kill: 1.2.2
@@ -8008,10 +8031,10 @@ snapshots:
vite: 5.4.19(@types/node@22.15.29)(less@4.2.0)(sass@1.71.1)(terser@5.29.1)
watchpack: 2.4.0
webpack: 5.94.0(esbuild@0.20.1)
- webpack-dev-middleware: 6.1.2(webpack@5.94.0)
+ webpack-dev-middleware: 6.1.2(webpack@5.94.0(esbuild@0.20.1))
webpack-dev-server: 4.15.1(webpack@5.94.0(esbuild@0.20.1))
webpack-merge: 5.10.0
- webpack-subresource-integrity: 5.1.0(webpack@5.94.0)
+ webpack-subresource-integrity: 5.1.0(webpack@5.94.0(esbuild@0.20.1))
optionalDependencies:
esbuild: 0.20.1
ng-packagr: 17.3.0(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.2.2))(tslib@2.8.1)(typescript@5.2.2)
@@ -9842,6 +9865,10 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
+ '@playwright/test@1.53.1':
+ dependencies:
+ playwright: 1.53.1
+
'@publint/pack@0.1.2': {}
'@react-hookz/web@25.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
@@ -11140,7 +11167,7 @@ snapshots:
axobject-query@4.1.0: {}
- babel-loader@9.1.3(@babel/core@7.26.10)(webpack@5.94.0):
+ babel-loader@9.1.3(@babel/core@7.26.10)(webpack@5.94.0(esbuild@0.20.1)):
dependencies:
'@babel/core': 7.26.10
find-cache-dir: 4.0.0
@@ -11497,7 +11524,7 @@ snapshots:
dependencies:
is-what: 3.14.1
- copy-webpack-plugin@11.0.0(webpack@5.94.0):
+ copy-webpack-plugin@11.0.0(webpack@5.94.0(esbuild@0.20.1)):
dependencies:
fast-glob: 3.3.3
glob-parent: 6.0.2
@@ -11538,7 +11565,7 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
- css-loader@6.10.0(webpack@5.94.0):
+ css-loader@6.10.0(webpack@5.94.0(esbuild@0.20.1)):
dependencies:
icss-utils: 5.1.0(postcss@8.5.4)
postcss: 8.5.4
@@ -12219,6 +12246,9 @@ snapshots:
fs.realpath@1.0.0: {}
+ fsevents@2.3.2:
+ optional: true
+
fsevents@2.3.3:
optional: true
@@ -12853,7 +12883,7 @@ snapshots:
picocolors: 1.1.1
shell-quote: 1.8.2
- less-loader@11.1.0(less@4.2.0)(webpack@5.94.0):
+ less-loader@11.1.0(less@4.2.0)(webpack@5.94.0(esbuild@0.20.1)):
dependencies:
klona: 2.0.6
less: 4.2.0
@@ -12892,7 +12922,7 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
- license-webpack-plugin@4.0.2(webpack@5.94.0):
+ license-webpack-plugin@4.0.2(webpack@5.94.0(esbuild@0.20.1)):
dependencies:
webpack-sources: 3.3.0
optionalDependencies:
@@ -13090,7 +13120,7 @@ snapshots:
min-indent@1.0.1: {}
- mini-css-extract-plugin@2.8.1(webpack@5.94.0):
+ mini-css-extract-plugin@2.8.1(webpack@5.94.0(esbuild@0.20.1)):
dependencies:
schema-utils: 4.3.2
tapable: 2.2.2
@@ -13663,7 +13693,15 @@ snapshots:
mlly: 1.7.4
pathe: 2.0.3
- postcss-loader@8.1.1(postcss@8.4.35)(typescript@5.2.2)(webpack@5.94.0):
+ playwright-core@1.53.1: {}
+
+ playwright@1.53.1:
+ dependencies:
+ playwright-core: 1.53.1
+ optionalDependencies:
+ fsevents: 2.3.2
+
+ postcss-loader@8.1.1(postcss@8.4.35)(typescript@5.2.2)(webpack@5.94.0(esbuild@0.20.1)):
dependencies:
cosmiconfig: 9.0.0(typescript@5.2.2)
jiti: 1.21.7
@@ -14013,7 +14051,7 @@ snapshots:
safer-buffer@2.1.2: {}
- sass-loader@14.1.1(sass@1.71.1)(webpack@5.94.0):
+ sass-loader@14.1.1(sass@1.71.1)(webpack@5.94.0(esbuild@0.20.1)):
dependencies:
neo-async: 2.6.2
optionalDependencies:
@@ -14284,7 +14322,7 @@ snapshots:
source-map-js@1.2.1: {}
- source-map-loader@5.0.0(webpack@5.94.0):
+ source-map-loader@5.0.0(webpack@5.94.0(esbuild@0.20.1)):
dependencies:
iconv-lite: 0.6.3
source-map-js: 1.2.1
@@ -14923,7 +14961,7 @@ snapshots:
schema-utils: 4.3.2
webpack: 5.94.0(esbuild@0.20.1)
- webpack-dev-middleware@6.1.2(webpack@5.94.0):
+ webpack-dev-middleware@6.1.2(webpack@5.94.0(esbuild@0.20.1)):
dependencies:
colorette: 2.0.20
memfs: 3.5.3
@@ -14981,7 +15019,7 @@ snapshots:
webpack-sources@3.3.0: {}
- webpack-subresource-integrity@5.1.0(webpack@5.94.0):
+ webpack-subresource-integrity@5.1.0(webpack@5.94.0(esbuild@0.20.1)):
dependencies:
typed-assert: 1.0.9
webpack: 5.94.0(esbuild@0.20.1)