diff --git a/.babelrc.js b/.babelrc.js
deleted file mode 100644
index e4306cb..0000000
--- a/.babelrc.js
+++ /dev/null
@@ -1,8 +0,0 @@
-module.exports = {
- plugins: [
- ['@babel/plugin-transform-class-properties'],
- ['@babel/plugin-transform-object-rest-spread'],
- ['@babel/plugin-transform-runtime', { regenerator: false }],
- ],
- presets: ['@babel/env', '@babel/react', '@babel/preset-typescript'],
-};
diff --git a/.browserslistrc b/.browserslistrc
deleted file mode 100644
index 327a5f7..0000000
--- a/.browserslistrc
+++ /dev/null
@@ -1 +0,0 @@
-> 0.5%, last 2 versions, Firefox ESR, not dead
diff --git a/.changeset/esm-build.md b/.changeset/esm-build.md
new file mode 100644
index 0000000..610237e
--- /dev/null
+++ b/.changeset/esm-build.md
@@ -0,0 +1,18 @@
+---
+'react-spring-lightbox': minor
+---
+
+Add an ES module build and an `exports` map, so `import` and `require` each load their own bundle with matching types.
+
+- The package now ships `dist/index.mjs` (ES module) and `dist/index.cjs` (CommonJS, renamed from `dist/index.cjs.js`), each with a source map. The type declarations are bundled into `dist/index.d.mts` for `import` and `dist/index.d.cts` for `require`. They replace `dist/index.d.ts` and the per-file declarations that 1.9.0 shipped under `dist/components/` and `dist/types/`.
+- `package.json` now sets `"main": "./dist/index.cjs"`, `"module": "./dist/index.mjs"`, and `"types": "./dist/index.d.cts"`. Its new `exports` map has `import` and `require` conditions for the package root and also exposes `./package.json`.
+- Bundlers now load the ES module build. Together with the existing `"sideEffects": false`, that lets them drop the lightbox entirely from an app that imports it without rendering it.
+- `import Lightbox, { ImagesListType } from 'react-spring-lightbox'` works as before, and `require('react-spring-lightbox')` still returns the component itself, not `{ default }`.
+- The CommonJS declarations now use `export =`, which matches `module.exports = Lightbox`. The 1.9.0 declarations used `export default`, so TypeScript projects compiling to CommonJS under `"module": "node16"` or `"nodenext"` expected a `.default` property that does not exist at runtime. `import Lightbox = require('react-spring-lightbox')` now type-checks as well.
+- The declarations import React's types from `react` instead of relying on the global `React` namespace that `@types/react` declares.
+- Release 1.8.0 removed an earlier ES module build because of problems with Next.js and its SWC compiler. The repository's Next.js 16 demo, which uses Turbopack, builds and hot-reloads with this one.
+
+Upgrade notes:
+
+- Imports of files inside `dist/` now fail, because the `exports` map exposes only the package root and `./package.json`. Node.js reports `ERR_PACKAGE_PATH_NOT_EXPORTED`, and bundlers report that the package path is not exported. Import from `react-spring-lightbox` instead, and replace imports of `dist/types/ImagesList` with `import type { ImagesListType } from 'react-spring-lightbox'`.
+- Configuration that names `dist/index.cjs.js` directly needs to name the package instead.
diff --git a/.changeset/node-and-npm-engines.md b/.changeset/node-and-npm-engines.md
new file mode 100644
index 0000000..137dcc1
--- /dev/null
+++ b/.changeset/node-and-npm-engines.md
@@ -0,0 +1,9 @@
+---
+'react-spring-lightbox': minor
+---
+
+Require Node.js `^22.18.0 || ^24.11.0 || >=26.0.0` and npm `>=10.9.0` in `engines`, up from Node.js `>=16` and npm `>=7`.
+
+- Node.js 16, 18, and 20 are past end of life. Installing the package from a git URL runs its `prepare` script, which builds it with `rolldown-plugin-dts`, and that plugin supports only these Node.js versions.
+- The published bundles use no Node.js APIs and need only ES2022 support, so the new minimum comes from the build and from Node.js's support schedule rather than from the code the lightbox runs.
+- On older versions, npm prints `npm warn EBADENGINE Unsupported engine` and installs anyway. With `engine-strict=true`, npm stops with `npm error code EBADENGINE`. Yarn 1 always stops, with `The engine "node" is incompatible with this module` followed by `error Found incompatible module`. Upgrade Node.js, or skip the check with `yarn install --ignore-engines` or `npm install --engine-strict=false`.
diff --git a/.changeset/rolldown-build.md b/.changeset/rolldown-build.md
new file mode 100644
index 0000000..5dc5317
--- /dev/null
+++ b/.changeset/rolldown-build.md
@@ -0,0 +1,13 @@
+---
+'react-spring-lightbox': patch
+---
+
+Build with Rolldown and TypeScript 7 instead of Rollup, Babel, and TypeScript 5, and drop the `@babel/runtime` dependency.
+
+- `dependencies` no longer lists `@babel/runtime`. The 1.9.0 bundle required `@babel/runtime/helpers/defineProperty`, `@babel/runtime/helpers/extends`, and `@babel/runtime/helpers/objectWithoutProperties`, and neither bundle needs them now.
+- Both bundles target ES2022. Class fields, object spread and rest, and optional chaining stay native, where Babel used to turn class fields and object spread into helper calls. Safari 15.6, Chrome 109, Firefox 102, and later run this syntax, the same browsers the Babel build targeted. A parser that cannot read class fields, such as the one in webpack 4, could not read 1.9.0 either, because 1.9.0 already used optional chaining.
+- The CommonJS bundle went from 12.2 kB to 10.2 kB, or from 4.0 kB to 3.7 kB gzipped. The new ES module bundle is 9.4 kB, or 3.5 kB gzipped.
+- Rolldown's Oxc minifier replaces Terser, so minified names and stack traces differ from 1.9.0. Both bundles ship source maps.
+- TypeScript 7 writes the declarations, and `rolldown-plugin-dts` bundles them into one file per module format.
+- No runtime behavior change is intended. The unit tests run the same user flows against both built bundles, and CI runs the Playwright suite in Chromium, Firefox, and WebKit.
+- Linting, formatting, and the git hooks also moved, to oxlint, oxfmt, and husky 9, but none of that ships in the package.
diff --git a/.changeset/ship-changelog.md b/.changeset/ship-changelog.md
new file mode 100644
index 0000000..60e6d5c
--- /dev/null
+++ b/.changeset/ship-changelog.md
@@ -0,0 +1,9 @@
+---
+'react-spring-lightbox': patch
+---
+
+Ship `CHANGELOG.md` in the npm package, so the release notes are in `node_modules/react-spring-lightbox/CHANGELOG.md` after an install.
+
+- `files` in `package.json` now lists `CHANGELOG.md` and `MIGRATION.md` next to `dist`. Through 1.9.0, the package held only `dist/`, `package.json`, `README.md`, and `LICENSE`, so the release notes were only on GitHub.
+- No release has a `MIGRATION.md` yet. The first release that needs a step-by-step migration guide ships one at `node_modules/react-spring-lightbox/MIGRATION.md`.
+- Nothing imports either file, so app bundles don't change.
diff --git a/.changeset/support-react-19.md b/.changeset/support-react-19.md
index 8a2d5cb..bfb89b1 100644
--- a/.changeset/support-react-19.md
+++ b/.changeset/support-react-19.md
@@ -2,4 +2,13 @@
'react-spring-lightbox': minor
---
-Support React 19. The lightbox now depends on `@react-spring/web` 10, which accepts React 19, so npm no longer refuses to install it next to React 19. Two-finger pinch zoom no longer stops after its first step under React 19. React 18 keeps working.
+Support React 19 by moving to `@react-spring/web` 10, and fix two-finger pinch zoom stopping after its first step under React 19.
+
+- `dependencies` now has `@react-spring/web` `^10.1.2`, up from `^9.7.3`. Version 9 declares `react` and `react-dom` peers of `^16.8.0 || ^17.0.0 || ^18.0.0`, and version 10 adds `^19.0.0`. The lightbox's own peers stay `react >=16.8` and `react-dom >=16.8`.
+- With 1.9.0 next to React 19, npm 11 prints `npm warn ERESOLVE overriding peer dependency` and installs `@react-spring/web` 9.7.5 anyway. With `--strict-peer-deps`, npm stops with `npm error code ERESOLVE` and lists `@react-spring/web@"^9.7.3" from react-spring-lightbox@1.9.0` under `Could not resolve dependency:`. This release installs next to React 19 without either message.
+- Under React 19, a two-finger pinch zoomed for one step and then stopped. When the second finger landed, `ImagePager` marked itself as dragging and cancelled the drag. Its drag-end handler cleared that flag only inside an `if (isDragging)` check, which read a stale `false` because React 19 had not yet re-rendered the pager with the new value. The flag stayed set, and the image ignores pinches while the pager is dragging. The drag-end handler now always moves the pages back to their resting positions and clears the flag 100 ms after the gesture ends.
+- React 18 keeps working. The unit tests and the Chromium Playwright tests pass against React 18.3.1 as well as React 19. The peer range still includes React 16.8 and 17, which this release was not tested against.
+
+Upgrade notes:
+
+- If your app also depends on `@react-spring/web` 9, npm installs a second copy for the lightbox, and `npm ls @react-spring/web` lists 9.x at the top level and 10.x under `react-spring-lightbox`. Your bundle then carries both copies. Settings you apply through `Globals.assign`, such as `skipAnimation` in a test setup file, reach only your copy, so the lightbox's springs keep animating. Move your app to `@react-spring/web` 10 so both share one copy.
diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index 10f24a4..0000000
--- a/.eslintignore
+++ /dev/null
@@ -1,34 +0,0 @@
-# See https://help.github.com/ignore-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-example/node_modules/**
-
-# production
-/build
-/dist
-/coverage
-/playwright-report
-/test-results
-/blob-report
-/e2e/harness/images
-/e2e/harness/index.html
-
-# misc
-.DS_Store
-.env
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-package-lock.json*
-package.json*
-CHANGELOG.md*
-README.md*
-
-example/.DS_Store
-example/.env
-example/npm-debug.log*
-example/yarn-debug.log*
-example/yarn-error.log*
-example/next-env.d.ts
-*.css
diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index eb98bd8..0000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/* eslint-disable sort-keys */
-/**
- * Configure ESLint
- *
- * https://eslint.org/docs/user-guide/configuring
- */
-module.exports = {
- env: {
- browser: true,
- es6: true,
- },
- extends: [
- 'plugin:react/recommended',
- 'plugin:import/warnings',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:prettier/recommended',
- ],
- globals: {
- document: true,
- window: true,
- },
- parser: '@typescript-eslint/parser',
- parserOptions: {
- sourceType: 'module',
- },
- plugins: [
- 'prettier',
- 'jsx-a11y',
- 'react',
- 'react-hooks',
- 'import',
- 'sort-destructure-keys',
- '@typescript-eslint',
- ],
- root: true,
- rules: {
- 'prettier/prettier': ['error', { endOfLine: 'auto' }],
- // Enforce React Hooks rules
- // https://www.npmjs.com/package/eslint-plugin-react-hooks
- 'react-hooks/rules-of-hooks': 'error',
- 'react-hooks/exhaustive-deps': 'warn',
-
- 'sort-destructure-keys/sort-destructure-keys': [
- 'error',
- { caseSensitive: false },
- ],
- 'sort-keys': ['error', 'asc', { caseSensitive: false, natural: false }],
- 'sort-vars': [
- 'error',
- {
- ignoreCase: true,
- },
- ],
- 'react/jsx-sort-props': ['error', { ignoreCase: true }],
- '@typescript-eslint/ban-ts-comment': 'off',
- '@typescript-eslint/no-explicit-any': 'off',
- '@typescript-eslint/explicit-module-boundary-types': 'off',
- '@typescript-eslint/member-ordering': [
- 'error',
- {
- default: {
- order: 'alphabetically',
- },
- classes: {
- order: 'as-written',
- },
- },
- ],
- },
- settings: {
- 'import/resolver': {
- node: true,
- 'eslint-import-resolver-typescript': true,
- },
- react: {
- version: 'detect',
- },
- },
-};
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 7fe45af..c864479 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -40,10 +40,12 @@ jobs:
node-version: 24
cache: npm
- run: npm ci
- - run: npm run lint:eslint
- - run: npm run lint:prettier
+ - run: npm run lint:oxlint
+ - run: npm run lint:oxfmt
# Vitest doesn't type-check, so tsc checks the tests along with the source
- run: npm run lint:ts
+ # publint and Are the Types Wrong check that the exports map and both builds' types resolve
+ - run: npm run lint:package
test:
runs-on: ubuntu-latest
diff --git a/.gitignore b/.gitignore
index d8169c0..1ebf00c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,9 +11,9 @@ test-results
blob-report
build
dist
-.rpt2_cache
.next
next-env.d.ts
+*.tsbuildinfo
# misc
.DS_Store
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..2312dc5
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1 @@
+npx lint-staged
diff --git a/.oxfmtrc.json b/.oxfmtrc.json
new file mode 100644
index 0000000..3726676
--- /dev/null
+++ b/.oxfmtrc.json
@@ -0,0 +1,36 @@
+{
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
+ "singleQuote": true,
+ "tabWidth": 4,
+ "semi": true,
+ "trailingComma": "es5",
+ "printWidth": 120,
+ "arrowParens": "always",
+ "singleAttributePerLine": true,
+ "sortPackageJson": false,
+ "sortImports": {
+ "partitionByNewline": false,
+ "newlinesBetween": true,
+ "internalPattern": ["@/", "~/"],
+ "groups": [
+ "type-import",
+ ["value-builtin", "value-external"],
+ "type-internal",
+ "value-internal",
+ ["type-parent", "type-sibling", "type-index"],
+ ["value-parent", "value-sibling", "value-index"],
+ "unknown"
+ ]
+ },
+ "ignorePatterns": ["**/dist/**", "**/node_modules/**"],
+ "overrides": [
+ {
+ "files": ["example/**"],
+ "options": {
+ "sortTailwindcss": {
+ "stylesheet": "./example/styles/globals.css"
+ }
+ }
+ }
+ ]
+}
diff --git a/.oxlintrc.json b/.oxlintrc.json
new file mode 100644
index 0000000..a62bafc
--- /dev/null
+++ b/.oxlintrc.json
@@ -0,0 +1,241 @@
+{
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
+ "plugins": ["eslint", "oxc", "unicorn", "typescript", "import", "react", "vitest", "jsx-a11y"],
+ "jsPlugins": ["./scripts/oxlint-plugin-sort-destructure-keys.mjs"],
+ "options": {
+ "typeAware": true
+ },
+ "env": {
+ "builtin": true,
+ "browser": true,
+ "node": true
+ },
+ "ignorePatterns": ["**/dist/**", "**/node_modules/**"],
+ "settings": {
+ "next": {
+ "rootDir": ["example"]
+ }
+ },
+ "rules": {
+ "@typescript-eslint/consistent-generic-constructors": "error",
+ "array-callback-return": "error",
+ "curly": ["error", "all"],
+ "default-case": "error",
+ "eqeqeq": ["error", "always"],
+ "import/namespace": "off",
+ "import/no-absolute-path": "error",
+ "import/no-amd": "error",
+ "import/no-self-import": "error",
+ "import/no-webpack-loader-syntax": "error",
+ "jsx-a11y/aria-activedescendant-has-tabindex": "error",
+ "jsx-a11y/aria-props": "error",
+ "jsx-a11y/aria-proptypes": "error",
+ "jsx-a11y/aria-role": "error",
+ "jsx-a11y/aria-unsupported-elements": "error",
+ "jsx-a11y/heading-has-content": "error",
+ "jsx-a11y/img-redundant-alt": "error",
+ "jsx-a11y/no-access-key": "error",
+ "jsx-a11y/no-distracting-elements": "error",
+ "jsx-a11y/no-redundant-roles": "error",
+ "jsx-a11y/role-has-required-aria-props": "error",
+ "jsx-a11y/scope": "error",
+ "no-array-constructor": "error",
+ "no-caller": "error",
+ "no-cond-assign": ["error", "except-parens"],
+ "no-console": "error",
+ "no-debugger": "error",
+ "no-duplicate-imports": [
+ "error",
+ {
+ "allowSeparateTypeImports": true
+ }
+ ],
+ "no-else-return": "error",
+ "no-eq-null": "error",
+ "no-eval": "error",
+ "no-extend-native": "error",
+ "no-extra-bind": "error",
+ "no-extra-boolean-cast": "off",
+ "no-extra-label": "error",
+ "no-fallthrough": "error",
+ "no-implicit-coercion": "error",
+ "no-implied-eval": "error",
+ "no-invalid-regexp": "error",
+ "no-iterator": "error",
+ "no-label-var": "error",
+ "no-labels": [
+ "error",
+ {
+ "allowLoop": true,
+ "allowSwitch": false
+ }
+ ],
+ "no-lone-blocks": "error",
+ "no-lonely-if": "error",
+ "no-loop-func": "error",
+ "no-multi-str": "error",
+ "no-new-func": "error",
+ "no-new-wrappers": "error",
+ "no-param-reassign": "error",
+ "no-proto": "error",
+ "no-redeclare": "error",
+ "no-regex-spaces": "error",
+ "no-restricted-globals": [
+ "error",
+ "addEventListener",
+ "blur",
+ "close",
+ "closed",
+ "confirm",
+ "defaultStatus",
+ "defaultstatus",
+ "event",
+ "external",
+ "find",
+ "focus",
+ "frameElement",
+ "frames",
+ "history",
+ "innerHeight",
+ "innerWidth",
+ "length",
+ "location",
+ "locationbar",
+ "menubar",
+ "moveBy",
+ "moveTo",
+ "name",
+ "onblur",
+ "onerror",
+ "onfocus",
+ "onload",
+ "onresize",
+ "onunload",
+ "open",
+ "opener",
+ "opera",
+ "outerHeight",
+ "outerWidth",
+ "pageXOffset",
+ "pageYOffset",
+ "parent",
+ "print",
+ "removeEventListener",
+ "resizeBy",
+ "resizeTo",
+ "screen",
+ "screenLeft",
+ "screenTop",
+ "screenX",
+ "screenY",
+ "scroll",
+ "scrollbars",
+ "scrollBy",
+ "scrollTo",
+ "scrollX",
+ "scrollY",
+ "self",
+ "status",
+ "statusbar",
+ "stop",
+ "toolbar",
+ "top"
+ ],
+ "no-script-url": "error",
+ "no-self-compare": "error",
+ "no-sequences": "error",
+ "no-shadow-restricted-names": "error",
+ "no-template-curly-in-string": "error",
+ "no-throw-literal": "error",
+ "no-unused-expressions": "off",
+ "no-unused-labels": "error",
+ "no-unused-vars": [
+ "error",
+ {
+ "argsIgnorePattern": "^_",
+ "caughtErrorsIgnorePattern": "^_",
+ "varsIgnorePattern": "^_"
+ }
+ ],
+ "no-useless-concat": "error",
+ "no-useless-constructor": "error",
+ "no-useless-escape": "error",
+ "no-useless-rename": [
+ "error",
+ {
+ "ignoreDestructuring": false,
+ "ignoreExport": false,
+ "ignoreImport": false
+ }
+ ],
+ "no-with": "error",
+ "react-hooks/exhaustive-deps": "error",
+ "react-hooks/rules-of-hooks": "error",
+ "react/jsx-boolean-value": "error",
+ "react/jsx-curly-brace-presence": "error",
+ "react/jsx-no-comment-textnodes": "error",
+ "react/jsx-no-duplicate-props": "error",
+ "react/jsx-no-undef": "error",
+ "react/no-danger": "error",
+ "react/no-danger-with-children": "error",
+ "react/no-direct-mutation-state": "error",
+ "react/no-is-mounted": "error",
+ "react/no-unknown-property": "error",
+ "react/require-render-return": "error",
+ "require-yield": "error",
+ "sort-destructure-keys/sort-destructure-keys": "error",
+ "sort-keys": [
+ "error",
+ "asc",
+ {
+ "caseSensitive": false,
+ "natural": false
+ }
+ ],
+ "sort-vars": [
+ "error",
+ {
+ "ignoreCase": true
+ }
+ ],
+ "typescript/no-floating-promises": [
+ "error",
+ {
+ "allowForKnownSafeCalls": [
+ {
+ "from": "package",
+ "name": "start",
+ "package": "@react-spring/core"
+ }
+ ]
+ }
+ ],
+ "typescript/no-unnecessary-condition": "error",
+ "typescript/prefer-optional-chain": "error",
+ "typescript/switch-exhaustiveness-check": "error",
+ "unicode-bom": ["error", "never"],
+ "vitest/expect-expect": [
+ "error",
+ {
+ "assertFunctionNames": ["expect", "expect*", "keepsInPlace", "pinchWith"]
+ }
+ ],
+ "vitest/prefer-to-have-length": "error",
+ "vitest/require-mock-type-parameters": "off"
+ },
+ "overrides": [
+ {
+ "files": ["example/**"],
+ "plugins": ["nextjs"],
+ "rules": {
+ "nextjs/no-img-element": "off"
+ }
+ },
+ {
+ "files": ["scripts/**"],
+ "rules": {
+ "no-console": "off"
+ }
+ }
+ ]
+}
diff --git a/.prettierignore b/.prettierignore
deleted file mode 100644
index 73d4f47..0000000
--- a/.prettierignore
+++ /dev/null
@@ -1,15 +0,0 @@
-node_modules/**
-.next/**
-dist/**
-coverage/**
-playwright-report/**
-test-results/**
-blob-report/**
-e2e/harness/images/**
-example/node_modules/**
-example/.next/**
-.editorconfig
-.eslintignore
-.gitignore
-.prettierignore
-.browserslistrc
diff --git a/.prettierrc.js b/.prettierrc.js
deleted file mode 100644
index a611401..0000000
--- a/.prettierrc.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * Configure Prettier
- *
- * https://prettier.io/docs/en/configuration.html#basic-configuration
- */
-module.exports = {
- endOfLine: 'auto',
- semi: true,
- singleQuote: true,
- tabWidth: 4,
-};
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..9d5eb3d
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["oxc.oxc-vscode"]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 032486e..359d2d8 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,5 +3,13 @@
"javascript.validate.enable": false,
"editor.tabSize": 4,
"editor.detectIndentation": false,
- "eslint.enable": true
+ "[javascript][javascriptreact][typescript][typescriptreact]": {
+ "editor.defaultFormatter": "oxc.oxc-vscode"
+ },
+ "[json][jsonc][yaml][css][html][markdown]": {
+ "editor.defaultFormatter": "oxc.oxc-vscode"
+ },
+ "editor.codeActionsOnSave": {
+ "source.fixAll.oxc": "always"
+ }
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index de00836..6e36a50 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,16 +2,16 @@
## [1.9.0] - 2026-07-29
-- Drop `styled-components` dependency
+- Drop `styled-components` dependency
## [1.8.0] - 2023-10-06
-- Add inline mode SSR support
-- fix next.js + styled-components + SWC client/server classname mismatch error
-- Upgrade several dependencies
- - @react-spring/web ^9.7
- - rollup 3
- - typescript 5
+- Add inline mode SSR support
+- fix next.js + styled-components + SWC client/server classname mismatch error
+- Upgrade several dependencies
+ - @react-spring/web ^9.7
+ - rollup 3
+ - typescript 5
### Potentially Breaking
@@ -21,32 +21,32 @@
### Fixed
-- Allow vertical scrolling with an InlineLightbox on mobile (even while finger is over the image)
+- Allow vertical scrolling with an InlineLightbox on mobile (even while finger is over the image)
## [1.7.0] - 2022-07-28
-- A few small bug fixes in situations where the `items` array may change size
-- Upgrade several dependencies
- - "@react-spring/web": "9.5.2"
- - "rollup": "^2.77.2",
+- A few small bug fixes in situations where the `items` array may change size
+- Upgrade several dependencies
+ - "@react-spring/web": "9.5.2"
+ - "rollup": "^2.77.2",
### Added
-- `Lightbox` can now be used as a slider component embedded in a page like slick-slider by using the `inline` prop
+- `Lightbox` can now be used as a slider component embedded in a page like slick-slider by using the `inline` prop
## [1.6.0] - 2021-06-06
-- Upgrade to `@react-spring/web@9.2.1` stable from `@tim-soft/react-spring-web@9.0.0-beta.36`
+- Upgrade to `@react-spring/web@9.2.1` stable from `@tim-soft/react-spring-web@9.0.0-beta.36`
## [1.5.0] - 2021-02-17
-- Rewrite project with typescript 4
-- Upgrade `react-use-gesture@7.0.15` to `react-use-gesture@9.0.4`
- - This upgrade should fix some miscellaneous bugs such as `unable to spread non iterable instance` and more consistent trackpad support
+- Rewrite project with typescript 4
+- Upgrade `react-use-gesture@7.0.15` to `react-use-gesture@9.0.4`
+ - This upgrade should fix some miscellaneous bugs such as `unable to spread non iterable instance` and more consistent trackpad support
### Added
-- The `images` prop now accepts a list of objects whose properties can be _almost_ any valid React `` prop including `srcset` and `loading` (lazy loading)
+- The `images` prop now accepts a list of objects whose properties can be _almost_ any valid React `
` prop including `srcset` and `loading` (lazy loading)
If you use typescript, the exact type can be imported/used like this
@@ -90,146 +90,146 @@ Which translates to any React `
` prop minus `draggable`, `onClick`, `onDr
### Fixed
-- Use aliased version of react-spring dependencies, fixes "Cannot read property 'ref' of null" error
+- Use aliased version of react-spring dependencies, fixes "Cannot read property 'ref' of null" error
## [1.4.10] - 2020-05-03
### Added
-- Optimize output bundles with Terser
-- Apply `babel-plugin-styled-components` babel plugin to optimize styled-components styles
+- Optimize output bundles with Terser
+- Apply `babel-plugin-styled-components` babel plugin to optimize styled-components styles
## [1.4.9] - 2020-05-02
### Fixed
-- Fix partially off-screen image stage in ie11
+- Fix partially off-screen image stage in ie11
### Added
-- Upgrade to `rollup@2.7.6`, `react-use-gesture@7.0.15` and `@babel/****@7.9.6`
+- Upgrade to `rollup@2.7.6`, `react-use-gesture@7.0.15` and `@babel/****@7.9.6`
## [1.4.8] - 2020-04-08
### Fixed
-- Dropped `lodash.clamp` dependency
-- Call onPrev/onNext callbacks on all paging events, even at the beginning or end of image array to allow for infinite paging
+- Dropped `lodash.clamp` dependency
+- Call onPrev/onNext callbacks on all paging events, even at the beginning or end of image array to allow for infinite paging
## [1.4.7] - 2020-04-05
### Added
-- Lower distance and velocity gesture threshold for a paging between images
-- Allow click to zoom while a paging animation completes
-- Upgrade to `rollup@2.3.3` and `react-use-gesture@7.0.10`
-- Add `sideEffects: false` to `package.json`
+- Lower distance and velocity gesture threshold for a paging between images
+- Allow click to zoom while a paging animation completes
+- Upgrade to `rollup@2.3.3` and `react-use-gesture@7.0.10`
+- Add `sideEffects: false` to `package.json`
## [1.4.6] - 2020-04-04
### Fixed
-- Handle edge case bugs with `singleClickToZoom` option
-- Fix undefined errors in panning drag handler on initial drags
+- Handle edge case bugs with `singleClickToZoom` option
+- Fix undefined errors in panning drag handler on initial drags
## [1.4.5] - 2020-03-27
### Added
-- Add optional `singleClickToZoom` prop which allows single click/tap zooming on images
+- Add optional `singleClickToZoom` prop which allows single click/tap zooming on images
## [1.4.4] - 2020-03-24
### Fixed
-- Add orientationchange event listener for ios devices
+- Add orientationchange event listener for ios devices
## [1.4.3] - 2020-03-23
### Fixed
-- Drop lodash.merge
-- Fix image heights not adjusting on window resize
+- Drop lodash.merge
+- Fix image heights not adjusting on window resize
## [1.4.2] - 2020-03-15
### Fixed
-- Remove need for react-use-measure and @juggle/resize-observer
+- Remove need for react-use-measure and @juggle/resize-observer
## [1.4.1] - 2020-03-13
### Fixed
-- Fix image stage height on Safari
-- Upgrade to react-use-gesture@7.0.5
-- Upgrade to rollup@2.0.6
+- Fix image stage height on Safari
+- Upgrade to react-use-gesture@7.0.5
+- Upgrade to rollup@2.0.6
## [1.4.0] - 2020-03-7
### BREAKING CHANGE
-- Replaced inline styles with styled-components. This library now has a peer dependency on `styled-components@5`
+- Replaced inline styles with styled-components. This library now has a peer dependency on `styled-components@5`
### Fixed
-- Gigantic initial image size in Firefox and MS Edge
-- Click background to close functionality
+- Gigantic initial image size in Firefox and MS Edge
+- Click background to close functionality
### Added
-- Vendor prefixed styles
-- A resize observer polyfill is now included to support MS Edge
+- Vendor prefixed styles
+- A resize observer polyfill is now included to support MS Edge
## [1.2.1] - 2020-03-5
### Added
-- Added `renderImageOverlay` prop, renders a React component within the image stage, useful for creating UI overlays on top of the current image
+- Added `renderImageOverlay` prop, renders a React component within the image stage, useful for creating UI overlays on top of the current image
## [1.2.0] - 2020-02-14
-- Upgrade react-use-gesture v6 -> v7
-- Upgrade all deps
+- Upgrade react-use-gesture v6 -> v7
+- Upgrade all deps
## [1.1.7] - 2019-09-23
### Fixed
-- Improved panning performance
-- Tweaked mousewheel swiping threshold
-- Upgrade to react-use-gesture v6
+- Improved panning performance
+- Tweaked mousewheel swiping threshold
+- Upgrade to react-use-gesture v6
## [1.1.4] - 2019-08-27
### Added
-- Implement mousewheel paging of images
+- Implement mousewheel paging of images
## [1.1.3] - 2019-08-19
### Fixed
-- Prevent vertical dragging from paging images
-- Switch to @react-spring/web package
+- Prevent vertical dragging from paging images
+- Switch to @react-spring/web package
## [1.1.2] - 2019-08-17
### Fixed
-- Properly dispose wheel event listener
+- Properly dispose wheel event listener
## [1.1.1] - 2019-08-14
### Fixed
-- Adjusted "pan out of bounds" threshold
+- Adjusted "pan out of bounds" threshold
## [1.1.0] - 2019-08-14
### Added
-- Implement proper Ctrl + `Mousewheel` and `Trackpad Pinch` zooming
+- Implement proper Ctrl + `Mousewheel` and `Trackpad Pinch` zooming
## [1.0.1] - 2019-08-7
@@ -243,8 +243,8 @@ Upgrade deps and release as stable
### Changed
-- Renamed onClickNext => onNext
-- Renamed onClickPrev => onPrev
+- Renamed onClickNext => onNext
+- Renamed onClickPrev => onPrev
## [0.0.2] - 2019-07-31
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..bef5471
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,95 @@
+# react-spring-lightbox
+
+This file tells coding agents how to work in this repository. `react-spring-lightbox` is a React image lightbox with touch gestures and react-spring animations, published to npm. Read [TESTING.md](TESTING.md) before you write or run tests.
+
+## Repository layout
+
+- `src/` is the library, and `src/index.tsx` is its entry. In `src/components/ImageStage/components/`, `ImagePager` pages between images and `Image` zooms and pans one image.
+- `src/__tests__/` holds the Vitest unit tests. `e2e/` holds the Playwright tests and the page they drive.
+- `example/` is a Next.js demo app that links the library from the parent folder. `example/README.md` explains how to run it.
+- `scripts/` holds the oxlint plugin that sorts destructured keys and the script that dates new `CHANGELOG.md` entries.
+- `rolldown.config.mjs` builds `dist/index.mjs` and `dist/index.cjs`, each with bundled types. The npm package holds `dist/`, `CHANGELOG.md`, `package.json`, `README.md`, and `LICENSE`, and `MIGRATION.md` once it exists.
+
+## Commands
+
+Use npm, not Yarn. `package.json` requires Node.js `^22.18.0 || ^24.11.0 || >=26.0.0`, and CI runs Node.js 24.
+
+| Command | What it does |
+| ------------------ | ------------------------------------------------------------------------------------------------- |
+| `npm install` | Installs dependencies, then builds `dist/` through the `prepare` script |
+| `npm run build` | Builds `dist/` |
+| `npm start` | Rebuilds the JavaScript in `dist/` on every change, without types, for the demo app |
+| `npm run lint` | Runs oxlint, `oxfmt --check`, `tsc` on the source and on `e2e/`, publint, and Are the Types Wrong |
+| `npm run fix` | Applies oxlint fixes, then formats every file with oxfmt |
+| `npm test` | Runs the unit tests with coverage |
+| `npm run test:e2e` | Runs the Playwright tests in five browser projects |
+
+## Code rules
+
+- The repository lints with oxlint and formats with oxfmt. Don't add ESLint, Prettier, or Babel. Rolldown and Oxc build the package without them.
+- oxlint runs type-aware rules through `oxlint-tsgolint`, such as `typescript/no-floating-promises`. It also requires sorted object keys and sorted destructured keys. oxfmt sorts imports.
+- TypeScript is 7.0, the native `tsc`, which has no JavaScript API. Tools that import `typescript` as a library don't work with it.
+- `npm run build` prints ``The `tsgo` generator on TypeScript 7.0 is deprecated and will be removed in the next minor. Upgrade to TypeScript 7.1 or later.`` The build still works. Move to TypeScript 7.1 before you upgrade `rolldown-plugin-dts` past 0.30.
+- Keep `@types/node` on the Node.js major that CI runs. Newer types describe APIs that CI's Node.js doesn't have.
+- The unit tests require 100% line, function, and statement coverage of `src/`. Every change to `src/` needs tests.
+- After you change `src/`, run `npm run build` before `npm test`. Some unit tests load `dist/`, and an old build gives them old code.
+- The repository develops on React 19, and the peer range is `react >=16.8`. After you change rendering, state, or effects, test React 18 as TESTING.md describes.
+- Never run `husky` with arguments. Husky 9 treats its first argument as the hooks directory and writes it to `core.hooksPath`, in git config that every worktree of the clone shares.
+- Don't change the `version` field or add entries to `CHANGELOG.md` by hand. The Version Packages pull request writes both from the changesets.
+- Keep `CLAUDE.md` and `TESTING.md` out of `files` in `package.json`. They're for contributors, and an agent that reads files under `node_modules` might pick up this repository's instructions.
+
+## Changesets
+
+Every pull request commits a changeset, a Markdown file in `.changeset/`. The `changeset` CI job runs `npx changeset status --since=origin/master`. When a pull request adds no changeset, the job fails with `Some packages have been changed but no changesets were found. Run changeset add to resolve this error.`
+
+- If the pull request changes what people install, write a changeset with the bump level in its front matter. That covers `src/`, the build output, the published types, and `package.json` fields such as `dependencies`, `peerDependencies`, `engines`, and `exports`. `npm run ch` asks for the same details interactively.
+- If the change doesn't need a release, such as a change to tests, CI, docs, or `example/`, run `npx changeset add --empty`.
+- Write one changeset for each change a consumer would notice, so that each change gets its own entry.
+- Choose patch, minor, or major by semver. If a change can break some consumers, as a stricter `exports` map or a higher `engines` minimum can, ask the maintainer which level to use.
+- A changeset stays editable until a release uses it up. If your pull request changes something that a pending changeset describes, update that changeset so that the release notes stay true.
+
+A changeset looks like this:
+
+```md
+---
+'react-spring-lightbox': patch
+---
+
+One sentence that says what changed for someone who uses the package.
+
+- A detail with exact versions and names, and any message quoted exactly.
+
+Upgrade notes:
+
+- How to tell that this note applies, and how to fix it.
+```
+
+### Write for someone debugging an upgrade
+
+People and coding agents read `CHANGELOG.md` and the GitHub release notes when an upgrade breaks their app. The package ships `CHANGELOG.md`, so readers can also open it in `node_modules/react-spring-lightbox/`. Write each changeset so that a reader who has only an error message can find the entry, learn the cause, and fix it without reading this repository's code.
+
+- Start with one sentence that says what changed for someone who uses the package.
+- Name exact versions, version ranges, file paths, exports, props, and `package.json` fields, with the old value and the new one.
+- Quote each warning or error that a consumer can see, exactly as the tool prints it, one line per quote. Readers search their logs for these strings, so capture the text by running the tool instead of writing it from memory.
+- For a bug fix, describe the symptom a user saw, when it happened, and what the code did wrong.
+- Say what you tested and what you didn't, such as React, Node.js, or bundler versions.
+- Check each claim with a run before you write it.
+
+### Add upgrade notes when consumers must act
+
+End the changeset with an `Upgrade notes:` paragraph and a bulleted list when a consumer might have to change code, configuration, or dependencies. These changes need upgrade notes:
+
+- A removed or renamed export, prop, file, or `package.json` path
+- A changed default or behavior
+- A higher minimum version of Node.js, npm, React, or a dependency
+- A new warning during install or build
+
+Each note says how to tell whether it applies, such as the error text or a command like `npm ls @react-spring/web`. Each note also gives the fix, with code before and after when code has to change.
+
+### Write a migration guide for larger upgrades
+
+If a release needs more steps than a few upgrade notes can hold, as a major version usually does, also write a guide in `MIGRATION.md`. `files` in `package.json` already lists `MIGRATION.md`, so the guide ships with the package. Give each version its own section, newest first, with numbered steps and code before and after. Link to the section from the changeset with its full `https://github.com/tim-soft/react-spring-lightbox/blob/master/MIGRATION.md` URL, so that the link also works in the GitHub release notes.
+
+### Preview the release notes before you push
+
+`npm run ch:version` writes `CHANGELOG.md` without formatting it, and the `lint` job checks that file with `oxfmt --check` after the release merges. Generate the release notes in a scratch copy, read them, and check their format, as TESTING.md describes.
diff --git a/README.md b/README.md
index c4d4e4c..6c98c9d 100644
--- a/README.md
+++ b/README.md
@@ -21,18 +21,18 @@ React-spring-lightbox is a flexible image gallery lightbox with native-feeling t
## ✨ Features
-- :point_up: `Mousewheel`, swipe or click+drag to page photos
-- :keyboard: Keyboard controls ← → Esc
-- :mouse2: Ctrl + `Mousewheel` or `Trackpad Pinch` to zoom
-- :mag_right: Double/Single-tap or double/single-click to zoom in/out
-- :ok_hand: Pinch to zoom
-- :point_left: Panning on zoomed-in images
-- :checkered_flag: Highly performant spring based animations via [react-spring](https://github.com/react-spring/react-spring)
-- No external CSS
-- Implement your own UI
-- Supports IE 11, Edge, Safari, Chrome, Firefox and Opera
-- Full typescript support
-- Supports any `
` attribute including `loading` (lazy loading), `srcset` and `aria-*`
+- :point_up: `Mousewheel`, swipe or click+drag to page photos
+- :keyboard: Keyboard controls ← → Esc
+- :mouse2: Ctrl + `Mousewheel` or `Trackpad Pinch` to zoom
+- :mag_right: Double/Single-tap or double/single-click to zoom in/out
+- :ok_hand: Pinch to zoom
+- :point_left: Panning on zoomed-in images
+- :checkered_flag: Highly performant spring based animations via [react-spring](https://github.com/react-spring/react-spring)
+- No external CSS
+- Implement your own UI
+- Supports IE 11, Edge, Safari, Chrome, Firefox and Opera
+- Full typescript support
+- Supports any `
` attribute including `loading` (lazy loading), `srcset` and `aria-*`
## Install
diff --git a/TESTING.md b/TESTING.md
new file mode 100644
index 0000000..cb042d4
--- /dev/null
+++ b/TESTING.md
@@ -0,0 +1,116 @@
+# Testing
+
+This file describes how react-spring-lightbox is tested, for people and coding agents. On every pull request to master, CI runs the `changeset`, `lint`, `test`, and `e2e` jobs in `.github/workflows/test.yml`.
+
+## Checks at a glance
+
+| Check | Command | CI job |
+| ------------------------------- | ---------------------------------------------- | ----------- |
+| Unit tests and coverage | `npm test` | `test` |
+| Browser tests | `npm run test:e2e` | `e2e` |
+| Types | `npm run lint:ts` | `lint` |
+| Lint and format | `npm run lint:oxlint` and `npm run lint:oxfmt` | `lint` |
+| Exports and published types | `npm run lint:package` | `lint` |
+| A changeset in the pull request | `npx changeset status --since=origin/master` | `changeset` |
+| Demo app build | `npm run build` in `example/` | none |
+
+## Unit tests
+
+`npm test` runs Vitest once with coverage, and `npm run test:watch` reruns tests as files change. To run one file, pass its path, such as `npx vitest run src/__tests__/paging.test.tsx`. To run only the tests whose names match, add `-t` and part of a name, such as `-t 'a quick flick pages'`.
+
+The tests live in `src/__tests__/`. Most files cover one feature that a user sees, such as `paging.test.tsx` and `zooming.test.tsx`. `scripts/add-release-dates.test.mjs` tests the release-date script.
+
+### Simulated browser
+
+The tests run in jsdom with Vitest's fake timers, and `src/__tests__/setup.ts` drives react-spring's frame loop from those timers. `src/__tests__/helpers.tsx` has the tools that most tests use:
+
+- `lightboxProps()` returns props for an open lightbox over five images, with a spy for each callback.
+- `advance(ms)` moves time forward, and `settle()` waits until every spring rests.
+- `mockLayout()` gives the stage and the images a size, because jsdom has no layout.
+- `shownImage()` and `imageTransform()` read what a user would see.
+
+`src/__tests__/devices.ts` sends input the way a browser does, with the same events in the same order. Its exports include `mouse`, `wheel`, `trackpad`, `touch`, `touchscreen`, `safariPinch`, and `pressKey`.
+
+Test what a user can see, such as the image in view, an image's transform, or a callback, rather than component state.
+
+### Limits of jsdom
+
+jsdom has no layout and no real input. With fake timers, the tests also run an animation frame before the next touch event, while a browser runs input handlers first. A race between input and animation frames can therefore pass in jsdom and fail in a browser, or the reverse. Confirm any bug that depends on gesture timing in the browser tests before you report or fix it.
+
+### Published bundles
+
+`src/__tests__/published-bundle.test.tsx` runs a few user flows against `dist/index.cjs` and `dist/index.mjs`, the files that people install. Run `npm run build` first. Without `dist/`, these tests fail with ``dist/index.cjs is missing. Run `npm run build` first.`` With an old `dist/`, they test the old code.
+
+### Type tests
+
+`src/__tests__/public-types.test.ts` checks the public types with `expectTypeOf`. `npm run lint:ts` checks these assertions. Vitest runs the file too, but at runtime its tests do nothing.
+
+### Known bugs
+
+Each known bug that isn't fixed yet has a `test.fails` test. The test's name describes the correct behavior and ends with the issue number, as in `test.fails('one trackpad swipe pages one image (#52)', ...)`. Vitest reports these tests as expected failures.
+
+- When a fix makes one of these tests pass, Vitest fails the run. Change `test.fails` to `test` in the same pull request.
+- To record a bug that you aren't fixing, add a `test.fails` test the same way.
+
+### Coverage
+
+`npm test` fails when coverage of `src/` drops below 100% of lines, functions, or statements, or when more than two branches go uncovered. The two uncovered branches are the `else if (goToIndex < 0)` checks in `ImagePager`, which can never be false. The report is in `coverage/unit/index.html`.
+
+## Browser tests
+
+`npm run test:e2e` runs Playwright. Install its browsers once with `npx playwright install chromium firefox webkit`.
+
+Playwright starts Vite on port 5174 with `e2e/vite.config.mts`. Vite serves `e2e/harness/`, a page that renders the lightbox from `src/`, so these tests don't need a build. Each test opens the page with query flags that pick a setup, such as `/?index=2&singleClickToZoom`, and `e2e/harness/main.tsx` reads them.
+
+Outside CI, Playwright reuses any server that already listens on port 5174, even one started from another checkout. Stop other servers on that port before you run the tests.
+
+- The five projects are `chromium`, `firefox`, `webkit`, `mobile-chromium` (a Pixel 7), and `mobile-webkit` (an iPhone 15).
+- To run one project, pass `--project`, such as `npx playwright test --project=chromium`. To run one file, pass its path, such as `npx playwright test e2e/zooming.spec.ts`.
+- Some tests send real mouse, wheel, or multi-finger touch input through Chromium's DevTools protocol, with `chromiumInput()` from `e2e/support.ts`. Those tests run only in `chromium` or `mobile-chromium`.
+- After a gesture, call `settle(page)` from `e2e/support.ts`. It waits until the images, the pages, and the container stop changing transform and opacity.
+- With `E2E_COVERAGE=true`, Chromium records which lines of `src/` run, and the run writes `coverage/e2e/lcov.info`.
+- Failed tests leave their output in `test-results/`. CI uploads the HTML report as the `playwright-report` artifact.
+
+### WebKit failures on a local machine
+
+WebKit can fail on a local machine where CI passes. In November 2026, 36 `webkit` and `mobile-webkit` tests failed on one macOS machine on an unmodified master, most of them by timing out while opening the lightbox. Before you blame a change for a WebKit failure, run the same projects on master and compare the failing test titles. CI on Ubuntu decides whether WebKit passes.
+
+## Lint, types, and the package
+
+- `npm run lint:ts` runs `tsc --noEmit` on the root project and on `e2e/`. Vitest doesn't type-check, so this is the only type check of the tests.
+- `npm run lint:package` runs publint, then Are the Types Wrong. `attw --pack .` packs the package, which runs `prepare` and builds it. Then it checks that each `exports` condition resolves to a file and to types that match it.
+- `npm run lint` runs oxlint, `oxfmt --check`, and both checks above.
+- CI doesn't build the demo app. After you change props, exports, or the build output, run `npm install` and `npm run build` in `example/`. Next.js type-checks the app while it builds.
+
+## Test a change against React 18
+
+The repository develops on React 19, and the peer range starts at React 16.8. To run the suites against React 18.3.1, commit your change, then run them in a copy that installs React 18:
+
+```bash
+DIR=$(mktemp -d)
+git archive HEAD | tar -x -C "$DIR"
+cd "$DIR"
+npm pkg set devDependencies.react=18.3.1 devDependencies.react-dom=18.3.1 'devDependencies.@types/react=^18.3.0' 'devDependencies.@types/react-dom=^18.3.0'
+rm package-lock.json
+npm install
+npx vitest run
+npx playwright test --project=chromium
+```
+
+`npm install` builds `dist/` in the copy, so the published-bundle tests run against React 18 too. Husky prints `.git can't be found` there, which is expected, because the copy isn't a git checkout. Stop other servers on port 5174 first, or Playwright tests another checkout's React.
+
+## Preview the release notes
+
+The Version Packages pull request runs `npm run ch:version`, which writes `CHANGELOG.md` from the changesets without formatting it. After that pull request merges, the `lint` job checks `CHANGELOG.md` with `oxfmt --check`. To read the release notes that your changesets produce and check their format, generate them in a scratch copy:
+
+```bash
+DIR=$(mktemp -d)
+git archive HEAD | tar -x -C "$DIR"
+cp .changeset/*.md "$DIR/.changeset/"
+ln -s "$PWD/node_modules" "$DIR/node_modules"
+cd "$DIR"
+npm run ch:version
+npx oxfmt --check CHANGELOG.md
+```
+
+The new release is the first section of `CHANGELOG.md` in the copy. The `cp` line brings in changesets that you haven't committed yet. In a real release, each entry also starts with its commit hash.
diff --git a/e2e/accessibility.spec.ts b/e2e/accessibility.spec.ts
index 53c3aa4..c9fc365 100644
--- a/e2e/accessibility.spec.ts
+++ b/e2e/accessibility.spec.ts
@@ -1,4 +1,5 @@
import type { Page } from '@playwright/test';
+
import { expect, openHarness, sampleFrames, settle, test } from './support';
// What keyboard, screen reader and reduced-motion users need from a modal.
@@ -13,12 +14,7 @@ const openWithKeyboard = async (page: Page) => {
};
const focusIsInLightbox = (page: Page) =>
- page.evaluate(
- () =>
- !!document.activeElement?.closest(
- '[data-testid="lightbox-container"]',
- ),
- );
+ page.evaluate(() => Boolean(document.activeElement?.closest('[data-testid="lightbox-container"]')));
test.describe('with a keyboard', () => {
test.skip(({ isMobile }) => isMobile, 'Keyboard input');
@@ -29,9 +25,7 @@ test.describe('with a keyboard', () => {
await page.keyboard.press('Escape');
await expect(page.getByTestId('lightbox-container')).toHaveCount(0);
- await expect(
- page.getByRole('button', { name: 'Open Image 3' }),
- ).toBeFocused();
+ await expect(page.getByRole('button', { name: 'Open Image 3' })).toBeFocused();
});
test('focus moves into the lightbox when it opens', async ({ page }) => {
@@ -42,14 +36,8 @@ test.describe('with a keyboard', () => {
expect(focusInside).toBe(true);
});
- test('Tab cycles through the lightbox, never reaching the page behind', async ({
- browserName,
- page,
- }) => {
- test.skip(
- browserName === 'webkit',
- 'WebKit on macOS tabs only to text fields by default',
- );
+ test('Tab cycles through the lightbox, never reaching the page behind', async ({ browserName, page }) => {
+ test.skip(browserName === 'webkit', 'WebKit on macOS tabs only to text fields by default');
await openWithKeyboard(page);
const stayedInside: boolean[] = [];
for (let press = 0; press < 6; press += 1) {
@@ -70,18 +58,12 @@ test.describe('with a keyboard', () => {
});
});
-test('opens without animating when the user prefers reduced motion', async ({
- page,
-}) => {
+test('opens without animating when the user prefers reduced motion', async ({ page }) => {
await page.emulateMedia({ reducedMotion: 'reduce' });
await openHarness(page, 'closed');
await page.getByRole('button', { name: 'Open Image 3' }).click();
- const [firstFrame] = await sampleFrames(
- page.getByTestId('lightbox-container'),
- 'opacity',
- 1,
- );
+ const [firstFrame] = await sampleFrames(page.getByTestId('lightbox-container'), 'opacity', 1);
test.fail(true, 'The springs ignore prefers-reduced-motion');
expect(firstFrame).toBe(1);
diff --git a/e2e/coverage.ts b/e2e/coverage.ts
index f84a335..bf9c6d1 100644
--- a/e2e/coverage.ts
+++ b/e2e/coverage.ts
@@ -1,7 +1,8 @@
-import path from 'node:path';
-import { CoverageReport } from 'monocart-coverage-reports';
import type { CoverageReportOptions } from 'monocart-coverage-reports';
+import { CoverageReport } from 'monocart-coverage-reports';
+import path from 'node:path';
+
// With E2E_COVERAGE set, the e2e tests record which lines of the library's
// source Chromium runs, and write coverage/e2e/lcov.info for Codecov
@@ -9,8 +10,7 @@ export const measuringCoverage = Boolean(process.env.E2E_COVERAGE);
export const coverageOptions: CoverageReportOptions = {
// Vite serves the library's modules from their paths on disk, under /@fs/
- entryFilter: (entry) =>
- entry.url.includes('/src/') && !entry.url.includes('/node_modules/'),
+ entryFilter: (entry) => entry.url.includes('/src/') && !entry.url.includes('/node_modules/'),
name: 'E2E coverage',
outputDir: 'coverage/e2e',
reports: ['lcovonly', 'console-summary'],
@@ -18,7 +18,9 @@ export const coverageOptions: CoverageReportOptions = {
// Vite's source maps name only the file, so find it beside the served module
sourcePath: (fileName, { distFile }) => {
const [, servedPath] = distFile?.split('/@fs/') ?? [];
- if (!servedPath) return fileName;
+ if (!servedPath) {
+ return fileName;
+ }
const file = path.join(path.dirname(`/${servedPath}`), fileName);
return path.relative(process.cwd(), file);
},
@@ -26,7 +28,9 @@ export const coverageOptions: CoverageReportOptions = {
/** Playwright's global setup: clears coverage from earlier runs, then reports this run's once it ends */
export default () => {
- if (!measuringCoverage) return undefined;
+ if (!measuringCoverage) {
+ return undefined;
+ }
new CoverageReport(coverageOptions).cleanCache();
return async () => {
await new CoverageReport(coverageOptions).generate();
diff --git a/e2e/harness/index.html b/e2e/harness/index.html
index ee5bda5..9e863e6 100644
--- a/e2e/harness/index.html
+++ b/e2e/harness/index.html
@@ -2,7 +2,10 @@