From c5af4457e258dacffba89c5b3762ef63cec87fdc Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 24 Jun 2026 10:49:58 +0800 Subject: [PATCH 01/10] feat(lightbox): scaffold libs/lightbox TS project (vite + tsc + vitest) --- libs/lightbox/.gitignore | 1 + libs/lightbox/package-lock.json | 1290 +++++++++++++++++++++++++++++++ libs/lightbox/package.json | 18 + libs/lightbox/tsconfig.json | 19 + libs/lightbox/vite.config.ts | 23 + libs/lightbox/vitest.config.ts | 8 + 6 files changed, 1359 insertions(+) create mode 100644 libs/lightbox/.gitignore create mode 100644 libs/lightbox/package-lock.json create mode 100644 libs/lightbox/package.json create mode 100644 libs/lightbox/tsconfig.json create mode 100644 libs/lightbox/vite.config.ts create mode 100644 libs/lightbox/vitest.config.ts diff --git a/libs/lightbox/.gitignore b/libs/lightbox/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/libs/lightbox/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/libs/lightbox/package-lock.json b/libs/lightbox/package-lock.json new file mode 100644 index 0000000..a4532c9 --- /dev/null +++ b/libs/lightbox/package-lock.json @@ -0,0 +1,1290 @@ +{ + "name": "@yggdrasil/lightbox", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@yggdrasil/lightbox", + "version": "1.0.0", + "devDependencies": { + "typescript": "^6.0.3", + "vite": "^8.1.0", + "vitest": "^4.1.9" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.137.0", + "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.137.0.tgz", + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.2.tgz", + "integrity": "sha512-2cZ+7xRS+DBcuJBJKnfzsbleumJhBqSlJVpuzHC0nTqfd3QQ7Vx2/x5YR/D7cBamKSeWplwo82Fn9lqYUDEMfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.2.tgz", + "integrity": "sha512-RkPMJnygxsgOYdkfqgpwY0/Fzm8d0VQe6HGU2/B00Xa9eqdLbrII+DOKAodbJAn3ZL1AJxGHkZRPYazgGY6Ljw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.2.tgz", + "integrity": "sha512-Uiczh6vFhwyfd7WNe7Q7mCA4KxAiLdz7jPE/WGizfRpIieoyFuNVMmM8HqZ9HwudTkY6/AeMQwlNJ9NJijguWw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.2.tgz", + "integrity": "sha512-+TpdtTRgHiJFjCVFbw311SuLk3KfytPOQQn+VlAEv+gBxYPtL7E6JS9e/tk+8CwxhIZvemJKo4rTKgfWNsKkkA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.2.tgz", + "integrity": "sha512-4lv1/tkmi7ueIVHnyreaOeUpiZP26BH9rRy6hoYfR9310A2B9nUEVRDvBx69vx64Nr3eTPPRkyciqJJs+j9Jmw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.2.tgz", + "integrity": "sha512-gBSUVO0eaWgw1JMjK3gB8BMlX2Mk148s2lTiVT3e9vjVxbl7UDfMWWY8CfIaaqiXuM9fVTMxIpUz6CAo/B6Vlw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.2.tgz", + "integrity": "sha512-LjQP/iZLBu8o8PjIfk4x3At0/mT6h282pvz8Z5LAyhGbu/kDezyO7ea62rF5uoqmgnIYqbN/MqJ3Si3Aymi7xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.2.tgz", + "integrity": "sha512-gb6dYKW/1KDorGXyy48glEBJs/sxVSC5pcVrox/pFGV4mvwSFeg2sK5L2tRkVsVlh7kueqOgg4GEcuipJcGuKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.2.tgz", + "integrity": "sha512-JY4w85pU3iAiJVMh5nuk4/Mh9GjMsupe8MrIN53rwxAZW64GKrWeJBuN6SxQg9QTU5uB1cxyhDzW8jqRn1EABw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.2.tgz", + "integrity": "sha512-xvpA7o5KCYLB0Rwscmuylb1/zHHSUx4g4xilm4prC5jP76pEUlzBmMbgpbh7bVDbId4NcfT96gN5i6mE6UDaiw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.2.tgz", + "integrity": "sha512-p/ts6KBLjuk49Bp21XH77poQGt02iNz7ChgHep7tudPOaLinR/De/RHdxF8w8Yj4r/bF/bqXwH6PZrB2sA+Nvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.2.tgz", + "integrity": "sha512-VMu/wmrZ9hJzYlRhbw7jK5PODlugyKZ5mOdX78+lS8OvuFkWNQdz1pFLrI2p3P0pjXOmUZ7B48o5VnMH9QOGtg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.5" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.2.tgz", + "integrity": "sha512-xtUJqs8qEkuSviS0n1tsohaPuz3a1SPhZywOji4Oo+sgrJs8daEDMZ0QtqL0OS7dx8PoVpg2J/ZZycPY5I2+Zg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.2.tgz", + "integrity": "sha512-85YiLQqjUKgSO/Zjnf9e0XIn5Ymrh1fLDWBeAkZqpuBR/3R8TpfoHXuyblqyQrftSSgWO9qpcHN8mkyKsLraoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmmirror.com/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-4.1.9.tgz", + "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/mocker/-/mocker-4.1.9.tgz", + "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", + "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-4.1.9.tgz", + "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.9", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-4.1.9.tgz", + "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.9", + "@vitest/utils": "4.1.9", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-4.1.9.tgz", + "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-4.1.9.tgz", + "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.9", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rolldown": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.1.2.tgz", + "integrity": "sha512-x0CrQQqCXWGeI8dTvFfN/Dnv3yMKT9hv5jFjlOreKAx9wqLq9wz7VvLLHyaAXC90/CpggTu9SisSbsJJTPSjNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.137.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.2", + "@rolldown/binding-darwin-arm64": "1.1.2", + "@rolldown/binding-darwin-x64": "1.1.2", + "@rolldown/binding-freebsd-x64": "1.1.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.2", + "@rolldown/binding-linux-arm64-gnu": "1.1.2", + "@rolldown/binding-linux-arm64-musl": "1.1.2", + "@rolldown/binding-linux-ppc64-gnu": "1.1.2", + "@rolldown/binding-linux-s390x-gnu": "1.1.2", + "@rolldown/binding-linux-x64-gnu": "1.1.2", + "@rolldown/binding-linux-x64-musl": "1.1.2", + "@rolldown/binding-openharmony-arm64": "1.1.2", + "@rolldown/binding-wasm32-wasi": "1.1.2", + "@rolldown/binding-win32-arm64-msvc": "1.1.2", + "@rolldown/binding-win32-x64-msvc": "1.1.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/vite/-/vite-8.1.0.tgz", + "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "~1.1.2", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/vitest/-/vitest-4.1.9.tgz", + "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.9", + "@vitest/mocker": "4.1.9", + "@vitest/pretty-format": "4.1.9", + "@vitest/runner": "4.1.9", + "@vitest/snapshot": "4.1.9", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.9", + "@vitest/browser-preview": "4.1.9", + "@vitest/browser-webdriverio": "4.1.9", + "@vitest/coverage-istanbul": "4.1.9", + "@vitest/coverage-v8": "4.1.9", + "@vitest/ui": "4.1.9", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/libs/lightbox/package.json b/libs/lightbox/package.json new file mode 100644 index 0000000..b9cae50 --- /dev/null +++ b/libs/lightbox/package.json @@ -0,0 +1,18 @@ +{ + "name": "@yggdrasil/lightbox", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "tsc --noEmit && vite build", + "dev": "vite", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest" + }, + "devDependencies": { + "typescript": "^6.0.3", + "vite": "^8.1.0", + "vitest": "^4.1.9" + } +} diff --git a/libs/lightbox/tsconfig.json b/libs/lightbox/tsconfig.json new file mode 100644 index 0000000..fd15971 --- /dev/null +++ b/libs/lightbox/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "skipLibCheck": true, + "esModuleInterop": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "types": [] + }, + "include": ["src"] +} diff --git a/libs/lightbox/vite.config.ts b/libs/lightbox/vite.config.ts new file mode 100644 index 0000000..b6aa91b --- /dev/null +++ b/libs/lightbox/vite.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'path'; + +export default defineConfig({ + build: { + outDir: resolve(__dirname, '../../public/lightbox'), + emptyOutDir: true, + lib: { + entry: resolve(__dirname, 'src/index.ts'), + name: 'Lightbox', + fileName: () => 'lightbox.js', + formats: ['iife'], + }, + rolldownOptions: { + output: { + assetFileNames: 'lightbox.[ext]', + }, + }, + cssCodeSplit: false, + minify: true, + sourcemap: true, + }, +}); diff --git a/libs/lightbox/vitest.config.ts b/libs/lightbox/vitest.config.ts new file mode 100644 index 0000000..7eeb3f8 --- /dev/null +++ b/libs/lightbox/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + include: ['src/**/*.test.ts'], + }, +}); From 44fc641aaac15385790861076f914ac384241bc2 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 24 Jun 2026 10:52:48 +0800 Subject: [PATCH 02/10] feat(lightbox): extract geometry pure fns with vitest coverage --- libs/lightbox/src/geometry.test.ts | 86 ++++++++++++++++++++++++++++++ libs/lightbox/src/geometry.ts | 48 +++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 libs/lightbox/src/geometry.test.ts create mode 100644 libs/lightbox/src/geometry.ts diff --git a/libs/lightbox/src/geometry.test.ts b/libs/lightbox/src/geometry.test.ts new file mode 100644 index 0000000..82e19d2 --- /dev/null +++ b/libs/lightbox/src/geometry.test.ts @@ -0,0 +1,86 @@ +import { describe, it, expect } from 'vitest'; +import { fitCentered, transformFor, originalUrl, type Rect } from './geometry'; + +describe('fitCentered', () => { + // 大图受 maxW=vw*0.92 / maxH=vh*0.88 约束,scale 取较小者 + it('宽图被视口宽度卡住(横向受限)', () => { + // 2000x1000 in 1000x800: maxW=920, maxH=704 + // scale = min(920/2000, 704/1000, 1) = min(0.46, 0.704, 1) = 0.46 + const r = fitCentered(2000, 1000, 1000, 800); + expect(r.w).toBeCloseTo(920, 5); + expect(r.h).toBeCloseTo(460, 5); + expect(r.x).toBeCloseTo((1000 - 920) / 2, 5); // 40 + expect(r.y).toBeCloseTo((800 - 460) / 2, 5); // 170 + }); + + it('高图被视口高度卡住(纵向受限)', () => { + // 1000x2000 in 1000x800: maxW=920, maxH=704 + // scale = min(920/1000, 704/2000, 1) = min(0.92, 0.352, 1) = 0.352 + const r = fitCentered(1000, 2000, 1000, 800); + expect(r.w).toBeCloseTo(352, 5); + expect(r.h).toBeCloseTo(704, 5); + }); + + it('小图不被放大(scale 钳到 1)', () => { + // 300x200 in 1000x800: scale = min(920/300, 704/200, 1) = 1 + const r = fitCentered(300, 200, 1000, 800); + expect(r.w).toBe(300); + expect(r.h).toBe(200); + expect(r.x).toBeCloseTo((1000 - 300) / 2, 5); // 350 + expect(r.y).toBeCloseTo((800 - 200) / 2, 5); // 300 + }); + + it('正方形图按 maxW/maxH 中较小者缩放', () => { + // 1000x1000 in 1000x800: maxW=920, maxH=704 + // scale = min(0.92, 0.704, 1) = 0.704 + const r = fitCentered(1000, 1000, 1000, 800); + expect(r.w).toBeCloseTo(704, 5); + expect(r.h).toBeCloseTo(704, 5); + }); +}); + +describe('transformFor', () => { + it('居中态(base=target):scale=1,translate 到目标左上角', () => { + const target: Rect = { x: 40, y: 170, w: 920, h: 460 }; + const t = transformFor(target, 920, 460); + expect(t).toBe('translate(40px,170px) scale(1,1)'); + }); + + it('缩小态(rect 比 base 小):scale<1', () => { + // 原图位置小,base=居中尺寸 920x460 + const origin: Rect = { x: 100, y: 500, w: 400, h: 200 }; + const t = transformFor(origin, 920, 460); + // 400/920 = 0.43478260869565216(JS 浮点完整精度) + expect(t).toBe('translate(100px,500px) scale(0.43478260869565216,0.43478260869565216)'); + }); + + it('baseW=0 守卫:scale 守卫为 1(不产生 NaN/Infinity)', () => { + const rect: Rect = { x: 0, y: 0, w: 0, h: 0 }; + const t = transformFor(rect, 0, 0); + expect(t).toBe('translate(0px,0px) scale(1,1)'); + }); + + it('字符串格式为 translate(Xpx,Ypx) scale(SX,SY)', () => { + const rect: Rect = { x: 10, y: 20, w: 100, h: 50 }; + const t = transformFor(rect, 200, 100); + expect(t).toMatch(/^translate\(\d+(\.\d+)?px,\d+(\.\d+)?px\) scale\(\d+(\.\d+)?,\d+(\.\d+)?\)$/); + }); +}); + +describe('originalUrl', () => { + it('去 query string', () => { + expect(originalUrl('/uploads/x.webp?w=800')).toBe('/uploads/x.webp'); + }); + + it('无 query 原样返回', () => { + expect(originalUrl('/uploads/x.webp')).toBe('/uploads/x.webp'); + }); + + it('null 输入返回空串', () => { + expect(originalUrl(null)).toBe(''); + }); + + it('空串输入返回空串', () => { + expect(originalUrl('')).toBe(''); + }); +}); diff --git a/libs/lightbox/src/geometry.ts b/libs/lightbox/src/geometry.ts new file mode 100644 index 0000000..7c1c64c --- /dev/null +++ b/libs/lightbox/src/geometry.ts @@ -0,0 +1,48 @@ +export interface Rect { + x: number; + y: number; + w: number; + h: number; +} + +// 计算图片在视口居中、contain 适配后的目标 rect。 +// naturalW/H: 图片真实像素尺寸;vw/vh: 视口尺寸。 +export function fitCentered(naturalW: number, naturalH: number, vw: number, vh: number): Rect { + var maxW = vw * 0.92; + var maxH = vh * 0.88; + var scale = Math.min(maxW / naturalW, maxH / naturalH, 1); + var w = naturalW * scale; + var h = naturalH * scale; + return { + x: (vw - w) / 2, + y: (vh - h) / 2, + w: w, + h: h, + }; +} + +// 把目标 rect 转成 transform 字符串。 +// baseW/baseH 是 img 元素的布局尺寸(=居中目标尺寸),scale 相对它缩放。 +// transform-origin 为 top left(见 CSS),translate 到 rect 左上角后 scale。 +// - 居中态:scale=1(base 就是居中尺寸) +// - originRect 态:scale = originRect.w / base.w(缩小) +export function transformFor(rect: Rect, baseW: number, baseH: number): string { + var sx = baseW > 0 ? rect.w / baseW : 1; + var sy = baseH > 0 ? rect.h / baseH : 1; + return ( + "translate(" + + rect.x + + "px," + + rect.y + + "px) scale(" + + sx + + "," + + sy + + ")" + ); +} + +// 原图 URL = data-src 去 query。data-src 形如 "/uploads/x.webp?w=800"。 +export function originalUrl(dataSrc: string | null): string { + return (dataSrc || "").split("?")[0]; +} From 5f57d375eaef9da2ed70982eecff3f73b49b2168 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 24 Jun 2026 10:57:23 +0800 Subject: [PATCH 03/10] feat(lightbox): migrate DOM logic to TypeScript index.ts with self-start contract --- libs/lightbox/src/index.ts | 551 +++++++++++++++++++++++++++++++++++++ 1 file changed, 551 insertions(+) create mode 100644 libs/lightbox/src/index.ts diff --git a/libs/lightbox/src/index.ts b/libs/lightbox/src/index.ts new file mode 100644 index 0000000..c30537c --- /dev/null +++ b/libs/lightbox/src/index.ts @@ -0,0 +1,551 @@ +import { fitCentered, transformFor, originalUrl, type Rect } from "./geometry"; + +interface LightboxState { + overlay: HTMLDivElement; + img: HTMLImageElement; + caption: HTMLElement; + counter: HTMLDivElement; + prevBtn: HTMLButtonElement | null; + nextBtn: HTMLButtonElement | null; + originNode: HTMLElement; + gallery: HTMLElement[]; + index: number | null; + isSingle: boolean; + openScrollY: number; + origSrc: string; + altText: string; + closing: boolean; + reduced: boolean; + scrollHandler: ((this: Window, ev: Event) => void) | null; + keyHandler: ((this: Document, ev: KeyboardEvent) => void) | null; + target?: Rect; + baseW?: number; + baseH?: number; +} + +declare global { + interface Window { + __initLightbox: (selectors: string | string[]) => void; + __lightboxSelectors?: string[]; + } +} + +export {}; + +(function () { + "use strict"; + + // ============ 工具函数 ============ + + function prefersReducedMotion(): boolean { + return ( + !!window.matchMedia && + window.matchMedia("(prefers-reduced-motion: reduce)").matches + ); + } + + // 读取元素当前在视口里的 rect(用于飞行起点/终点)。 + // 统一映射成 {x,y,w,h}:getBoundingClientRect 返回的 DOMRect 用 + // left/top/width/height,而 fitCentered/transformFor 用 x/y/w/h, + // 这里转成一致格式,避免 .w 读到 undefined。 + function rectOf(el: Element): Rect { + var r = el.getBoundingClientRect(); + return { + x: r.left, + y: r.top, + w: r.width, + h: r.height, + }; + } + + // 注意:fitCentered / transformFor / originalUrl 已抽到 ./geometry.ts(见 Task 2)。 + + // ============ 懒加载 ============ + + // 为单个 .blur-img 容器初始化高清图懒加载。 + // IO 进入视口后把 data-src 写入 src,加载完成加 is-loaded 触发 CSS 淡入。 + function initLazyLoad(container: Element): void { + var raw = container.querySelector(".blur-img-full"); + if (!(raw instanceof HTMLImageElement)) return; + // 用 const + 显式类型锁住窄化结果:var 在闭包内会被放宽回 Element | null, + // 导致 onFullLoaded/IntersectionObserver 回调里访问 .style/.src 报错。 + var fullImg: HTMLImageElement = raw; + if (container.getAttribute("data-blur-init")) return; + container.setAttribute("data-blur-init", "true"); + + var rawSrc = fullImg.getAttribute("data-src"); + if (!rawSrc) return; + // 同上:const 锁住 string 窄化,避免闭包内放宽回 string | null。 + var fullSrc: string = rawSrc; + + var onFullLoaded = function (): void { + // 给容器加 is-loaded,CSS 据此显式隐藏 placeholder。 + // 直接把 full 层 opacity 设为 1(清掉 transition),不依赖 CSS 的 opacity + // 过渡:合成层重绘时机不稳定,可能导致 full 层卡在 opacity:0,直到一次 + // 强制重排才更新。 + container.classList.add("is-loaded"); + fullImg.style.transition = "none"; + fullImg.style.opacity = "1"; + }; + fullImg.addEventListener("load", onFullLoaded); + // 缓存兜底:若设 src 时图片已在缓存(load 几乎立即触发,可能早于监听注册), + // 用 complete 补一次。注意无 src 的 img complete 也为 true,故先判 src。 + if (fullImg.getAttribute("src") && fullImg.complete) { + onFullLoaded(); + } + + if ("IntersectionObserver" in window) { + var io = new IntersectionObserver( + function (entries: IntersectionObserverEntry[]): void { + entries.forEach(function (entry): void { + if (entry.isIntersecting) { + fullImg.src = fullSrc; + io.unobserve(container); + } + }); + }, + { rootMargin: "200px" } + ); + io.observe(container); + } else { + fullImg.src = fullSrc; + } + } + + // ============ 图像收集 ============ + + // 收集所有 selectors命中的 .blur-img 节点。 + // gallery: 正文图(组成图集);singles: 带 lightbox-single class 的单张图(如封面)。 + function collectImages(roots: Element[]): { gallery: HTMLElement[]; singles: HTMLElement[] } { + var gallery: HTMLElement[] = []; + var singles: HTMLElement[] = []; + for (var i = 0; i < roots.length; i++) { + var nodes = roots[i].querySelectorAll(".blur-img"); + for (var j = 0; j < nodes.length; j++) { + var n = nodes[j]; + if (!(n instanceof HTMLElement)) continue; + if (n.classList.contains("lightbox-single")) { + singles.push(n); + } else { + gallery.push(n); + } + } + } + return { gallery: gallery, singles: singles }; + } + + // ============ 灯箱状态与开/关 ============ + + // 当前灯箱状态(同时只允许一个灯箱)。 + var state: LightboxState | null = null; + + function openLightbox(originNode: HTMLElement, gallery: HTMLElement[], index: number | null): void { + if (state) closeLightbox(true); + + var fullImgEl = originNode.querySelector(".blur-img-full"); + if (!(fullImgEl instanceof HTMLImageElement)) return; + var dataSrc = fullImgEl.getAttribute("data-src") || ""; + var origSrc = originalUrl(dataSrc); + var altText = fullImgEl.getAttribute("alt") || ""; + var isSingle = + originNode.classList.contains("lightbox-single") || + gallery.length === 0; + + var vw = window.innerWidth; + var vh = window.innerHeight; + + // 建 DOM + var overlay = document.createElement("div"); + overlay.className = "lightbox-overlay"; + overlay.setAttribute("role", "dialog"); + overlay.setAttribute("aria-modal", "true"); + overlay.setAttribute("aria-label", "图片预览"); + overlay.setAttribute("tabindex", "-1"); + // 创建后立即设 opacity 0:append 到 DOM 时是透明的,避免在图片加载期间 + // (start() 执行前)显示全黑背景造成闪烁。start() 的渐变会把 opacity 升到 1。 + overlay.style.opacity = "0"; + + var img = document.createElement("img"); + img.className = "lightbox-img"; + img.setAttribute("alt", altText); + // 加载前先占 0 尺寸,避免原图(可能数千 px)在加载期间撑大文档 + // 可滚动区、触发非预期的 scroll 事件。start() 拿到 natural 尺寸后再设真实值。 + img.style.width = "0px"; + img.style.height = "0px"; + + var caption = document.createElement("figcaption"); + caption.className = "lightbox-caption"; + caption.textContent = altText; + if (!altText) caption.style.display = "none"; + + var counter = document.createElement("div"); + counter.className = "lightbox-counter"; + if (isSingle || gallery.length === 0) { + counter.style.display = "none"; + } else { + counter.textContent = (index ?? 0) + 1 + " / " + gallery.length; + } + + // 图集模式(>1 张)才加左右导航箭头;单张不显示。 + var prevBtn: HTMLButtonElement | null = null; + var nextBtn: HTMLButtonElement | null = null; + if (!isSingle && gallery.length > 1) { + prevBtn = document.createElement("button"); + prevBtn.className = "lightbox-nav lightbox-prev"; + prevBtn.setAttribute("type", "button"); + prevBtn.setAttribute("aria-label", "上一张"); + prevBtn.textContent = "\u2039"; + + nextBtn = document.createElement("button"); + nextBtn.className = "lightbox-nav lightbox-next"; + nextBtn.setAttribute("type", "button"); + nextBtn.setAttribute("aria-label", "下一张"); + nextBtn.textContent = "\u203a"; + } + + overlay.appendChild(img); + overlay.appendChild(caption); + overlay.appendChild(counter); + if (prevBtn) overlay.appendChild(prevBtn); + if (nextBtn) overlay.appendChild(nextBtn); + document.body.appendChild(overlay); + + state = { + overlay: overlay, + img: img, + caption: caption, + counter: counter, + prevBtn: prevBtn, + nextBtn: nextBtn, + originNode: originNode, + gallery: gallery, + index: index, + isSingle: isSingle, + openScrollY: window.scrollY, + origSrc: origSrc, + altText: altText, + closing: false, + reduced: prefersReducedMotion(), + scrollHandler: null, + keyHandler: null, + }; + + // 焦点移入灯箱 + overlay.focus(); + // 立即绑定交互(不等图片加载):加载期间 Esc/滚动/点背景也须可关闭。 + bindInteractions(); + + // 图片加载后再做动画(naturalW/H 要等加载) + var start = function (): void { + if (!state) return; // 加载前可能已被关闭 + var naturalW = img.naturalWidth || img.clientWidth || 1; + var naturalH = img.naturalHeight || img.clientHeight || 1; + var originRect = rectOf(originNode); + + // 基准 = originRect(文章里图片的实际尺寸)。 + // img 的布局尺寸固定为 originRect,transform 的 scale 相对它缩放: + // 首帧(文章位置)scale=1,居中态 scale=target.w/originRect.w。 + // 这样无论 target 比 originRect 大或小,动画都是「从文章图原样连续缩放」, + // 视觉上是原地展开,不会像「从外面飞来」。灯箱图尺寸恒为视口最大(fitCentered)。 + var target = fitCentered(naturalW, naturalH, vw, vh); + var baseW = originRect.w; + var baseH = originRect.h; + // 存基准与目标,供关闭/滚动关闭复用同一对(保证 scale 连续)。 + state.target = target; + state.baseW = baseW; + state.baseH = baseH; + img.style.width = baseW + "px"; + img.style.height = baseH + "px"; + + // reduced-motion:直接淡入居中 + if (state.reduced) { + img.style.opacity = "0"; + img.style.transform = transformFor(target, baseW, baseH); + img.style.left = "0"; + img.style.top = "0"; + overlay.style.opacity = "0"; + // 下一帧淡入 + requestAnimationFrame(function (): void { + if (!state) return; + overlay.style.transition = "opacity 200ms ease-out"; + img.style.transition = "opacity 200ms ease-out"; + overlay.style.opacity = "1"; + img.style.opacity = "1"; + }); + return; + } + + // 首帧:文章位置 + 原尺寸(scale=1),透明,且关闭 transition + img.style.transition = "none"; + img.style.left = "0"; + img.style.top = "0"; + img.style.transform = transformFor(originRect, baseW, baseH); + img.style.opacity = "0"; + overlay.style.opacity = "0"; + // 强制 reflow,确保首帧的 transform 已提交到渲染层。 + // 否则单层 rAF 里浏览器可能合并首帧与目标帧,动画从错误位置起跳。 + void img.offsetHeight; + + // double-rAF:第一帧绘制首帧(无动画),第二帧才启动 transition 到居中。 + requestAnimationFrame(function (): void { + if (!state) return; + requestAnimationFrame(function (): void { + if (!state) return; + img.style.transition = + "transform 250ms ease-out, opacity 250ms ease-out"; + overlay.style.transition = "opacity 250ms ease-out"; + img.style.transform = transformFor(target, baseW, baseH); + img.style.opacity = "1"; + overlay.style.opacity = "1"; + }); + }); + }; + + if (img.complete && img.naturalWidth) { + start(); + } else { + img.addEventListener("load", start, { once: true }); + } + img.src = origSrc; + } + + function closeLightbox(immediate: boolean): void { + if (!state || state.closing) return; + state.closing = true; + cleanupInteractions(); + + var s = state; + + // 基准 = originRect 尺寸(与打开时一致),scale 相对它缩放。 + var baseW = s.baseW || (s.target ? s.target.w : 1); + var baseH = s.baseH || (s.target ? s.target.h : 1); + var originRect = rectOf(s.originNode); // 实时读,处理期间滚动过的情况 + + if (s.reduced || immediate) { + removeOverlay(); + return; + } + + // 飞回 originRect:scale 从 1 缩到 originRect.w/baseW + s.img.style.transition = + "transform 250ms ease-out, opacity 250ms ease-out"; + s.overlay.style.transition = "opacity 250ms ease-out"; + s.img.style.transform = transformFor(originRect, baseW, baseH); + s.img.style.opacity = "0"; + s.overlay.style.opacity = "0"; + + var done = function (): void { + removeOverlay(); + }; + // 250ms 兜底,避免 transitionend 不触发 + var timer = setTimeout(done, 280); + s.img.addEventListener("transitionend", function (): void { clearTimeout(timer); done(); }, { once: true }); + } + + function removeOverlay(): void { + if (!state) return; + var prev = state.originNode; + if (state.overlay && state.overlay.parentNode) { + state.overlay.parentNode.removeChild(state.overlay); + } + state = null; + // 焦点归还:.blur-img 是 span 不可聚焦,让其内部 full img 获得焦点 + if (prev) { + var f = prev.querySelector(".blur-img-full"); + if (f instanceof HTMLImageElement) { + f.setAttribute("tabindex", "-1"); + f.focus(); + } + } + } + + // ============ 图集切换 ============ + + // 图集切换:淡入淡出,不飞行。newIndex 循环(首尾衔接)。 + function gotoIndex(newIndex: number): void { + if (!state || state.isSingle) return; + var s = state; + if (!s.gallery || s.gallery.length === 0) return; + if (newIndex < 0) newIndex = s.gallery.length - 1; + if (newIndex >= s.gallery.length) newIndex = 0; + if (newIndex === s.index) return; + + var newNode = s.gallery[newIndex]; + var fullImgEl = newNode.querySelector(".blur-img-full"); + if (!(fullImgEl instanceof HTMLImageElement)) return; + var origSrc = originalUrl(fullImgEl.getAttribute("data-src") || ""); + var altText = fullImgEl.getAttribute("alt") || ""; + + // 淡出当前图 + s.img.style.transition = "opacity 150ms ease-out"; + s.img.style.opacity = "0"; + + // 150ms 后换图淡入 + var swap = function (): void { + if (!state) return; // 切换中可能已关闭 + var fade = function (): void { + if (!state) return; + s.img.style.transition = "opacity 150ms ease-out"; + s.img.style.opacity = "1"; + }; + if (s.img.complete && s.img.naturalWidth) { + // 缓存命中,直接淡入 + s.img.src = origSrc; + fade(); + } else { + s.img.addEventListener("load", fade, { once: true }); + s.img.src = origSrc; + } + s.caption.textContent = altText; + s.caption.style.display = altText ? "" : "none"; + s.counter.textContent = newIndex + 1 + " / " + s.gallery.length; + // 更新 originNode 为新图,使后续关闭/滚动关闭飞回新图位置 + s.originNode = newNode; + s.index = newIndex; + s.openScrollY = window.scrollY; // 重置滚动关闭基线 + }; + setTimeout(swap, 150); + } + + // ============ 交互绑定 ============ + + function bindInteractions(): void { + var s = state; + if (!s) return; + + // 点背景关闭(点图片本身不关,因箭头在图上、避免误关) + s.overlay.addEventListener("click", function (ev: MouseEvent): void { + if (state && ev.target === state.overlay) closeLightbox(false); + }); + + // 滚动驱动关闭:任何 scroll 都触发,用 scrollY 偏移算进度。 + // 关键:逐帧读 originNode 实时 rect,文章滚多少图就回多少。 + s.scrollHandler = function (): void { + if (!state) return; + var st = state; + if (st.closing) return; + var dy = Math.abs(window.scrollY - st.openScrollY); + if (st.reduced) { + // reduced-motion:立即关 + closeLightbox(true); + return; + } + var target = st.target; + var baseW = st.baseW || (target ? target.w : 1); + var baseH = st.baseH || (target ? target.h : 1); + var originRect = rectOf(st.originNode); + // 在 originRect 与居中 target 之间按 progress 线性插值 + var progress = Math.min(dy / 120, 1); + if (!target) return; // 无 target 时不插值(忠实原 JS 的兜底语义) + var cur: Rect = { + x: target.x + (originRect.x - target.x) * progress, + y: target.y + (originRect.y - target.y) * progress, + w: target.w + (originRect.w - target.w) * progress, + h: target.h + (originRect.h - target.h) * progress, + }; + st.img.style.transition = "none"; + st.img.style.transform = transformFor(cur, baseW, baseH); + st.img.style.opacity = String(1 - progress); + st.overlay.style.opacity = String(1 - progress); + if (progress >= 1) { + // 已飞回原位:文章停在当前滚动位置,移除灯箱 + st.closing = true; + cleanupInteractions(); + removeOverlay(); + } + }; + window.addEventListener("scroll", s.scrollHandler, { passive: true }); + + // 键盘:Esc 关;图集模式 ←→ 切换 + s.keyHandler = function (ev: KeyboardEvent): void { + if (!state) return; + if (ev.key === "Escape") { + closeLightbox(false); + } else if (!state.isSingle && state.gallery.length > 1) { + if (ev.key === "ArrowLeft") { + ev.preventDefault(); + gotoIndex((state.index ?? 0) - 1); + } else if (ev.key === "ArrowRight") { + ev.preventDefault(); + gotoIndex((state.index ?? 0) + 1); + } + } + }; + document.addEventListener("keydown", s.keyHandler); + + // 图集导航箭头点击(stopPropagation 防止冒泡到 overlay 触发关闭) + if (s.prevBtn) { + s.prevBtn.addEventListener("click", function (ev: MouseEvent): void { + ev.stopPropagation(); + if (state) gotoIndex((state.index ?? 0) - 1); + }); + } + if (s.nextBtn) { + s.nextBtn.addEventListener("click", function (ev: MouseEvent): void { + ev.stopPropagation(); + if (state) gotoIndex((state.index ?? 0) + 1); + }); + } + } + + function cleanupInteractions(): void { + if (!state) return; + if (state.scrollHandler) { + window.removeEventListener("scroll", state.scrollHandler); + state.scrollHandler = null; + } + if (state.keyHandler) { + document.removeEventListener("keydown", state.keyHandler); + state.keyHandler = null; + } + } + + // ============ 初始化入口 ============ + + window.__initLightbox = function (selectors: string | string[]): void { + // selectors 可以是字符串、字符串数组 + var sels = Array.isArray(selectors) ? selectors : [selectors]; + var roots: Element[] = []; + for (var i = 0; i < sels.length; i++) { + var found = document.querySelectorAll(sels[i]); + for (var j = 0; j < found.length; j++) roots.push(found[j]); + } + + // 先对所有图片做懒加载(图集与单张都做) + var collected = collectImages(roots); + var everyone = collected.gallery.concat(collected.singles); + for (var k = 0; k < everyone.length; k++) { + initLazyLoad(everyone[k]); + } + + // 正文图:带 index + var gallery = collected.gallery; + for (var g = 0; g < gallery.length; g++) { + (function (node: HTMLElement, idx: number): void { + node.addEventListener("click", function (e: MouseEvent): void { + e.preventDefault(); + openLightbox(node, gallery, idx); + }); + })(gallery[g], g); + } + // 单张图(封面):index = null,gallery 传空数组表示单张 + for (var si = 0; si < collected.singles.length; si++) { + (function (node: HTMLElement): void { + node.addEventListener("click", function (e: MouseEvent): void { + e.preventDefault(); + openLightbox(node, [], null); + }); + })(collected.singles[si]); + } + }; + + // ============ 自启动 ============ + // 方案 iii:双保险契约,无需轮询。 + // 1) Rust 内联 eval 先跑(常态):设 __lightboxSelectors,此时 __initLightbox 可能未定义 → 只设配置; + // lightbox.js 后加载完 → 读到配置 → 这里自启动。 + // 2) lightbox.js 先加载完:__initLightbox 就绪但无配置 → 不自启动; + // Rust eval 后跑 → 设配置 + 兜底 if(__initLightbox) 显式调用 → 初始化。 + if (Array.isArray(window.__lightboxSelectors)) { + window.__initLightbox(window.__lightboxSelectors); + } +})(); From b4ef906141665101cfe3c9e6158e13bce90013e3 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 24 Jun 2026 11:02:44 +0800 Subject: [PATCH 04/10] refactor(lightbox): move .lightbox-* CSS into lightbox project, wire import --- input.css | 72 --------------------------------- libs/lightbox/src/index.ts | 1 + libs/lightbox/src/style.css | 71 ++++++++++++++++++++++++++++++++ libs/lightbox/src/vite-env.d.ts | 1 + 4 files changed, 73 insertions(+), 72 deletions(-) create mode 100644 libs/lightbox/src/style.css create mode 100644 libs/lightbox/src/vite-env.d.ts diff --git a/input.css b/input.css index 1ffaf9c..0d7a893 100644 --- a/input.css +++ b/input.css @@ -526,78 +526,6 @@ text-underline-offset: 0.3rem; } - /* ========== 统一灯箱(Medium 风格) ========== */ - .lightbox-overlay { - position: fixed; - inset: 0; - z-index: 50; - /* overflow:hidden 裁剪超大的 absolute img,防止原图(可能数千 px) - 撑大文档可滚动区,否则会触发非预期的 scroll 事件。 */ - overflow: hidden; - background: rgba(0, 0, 0, 0.92); - opacity: 1; - } - - /* 灯箱图片:绝对定位,尺寸/位置由 JS 的 transform 控制。 - transform-origin 为 top left,配合 translate+scale 实现原地缩放。 - 显式 width/height 由 JS 设置为原图尺寸,确保布局尺寸确定, - transform 只做视觉缩放,不改变占位。 */ - .lightbox-img { - position: absolute; - left: 0; - top: 0; - transform-origin: top left; - max-width: none; - max-height: none; - object-fit: contain; - opacity: 1; - } - - .lightbox-caption { - position: fixed; - left: 0; - right: 0; - bottom: 2rem; - text-align: center; - color: rgba(255, 255, 255, 0.85); - font-size: 0.95rem; - padding: 0 2rem; - pointer-events: none; - z-index: 2; - } - - .lightbox-counter { - position: fixed; - right: 1.5rem; - bottom: 1.5rem; - color: rgba(255, 255, 255, 0.7); - font-size: 0.85rem; - z-index: 2; - } - - .lightbox-nav { - position: fixed; - top: 50%; - transform: translateY(-50%); - background: transparent; - border: none; - color: rgba(255, 255, 255, 0.7); - font-size: 3rem; - line-height: 1; - width: 3rem; - height: 5rem; - cursor: pointer; - z-index: 2; - border-radius: 0.5rem; - transition: color 0.2s ease-out, background 0.2s ease-out; - } - .lightbox-nav:hover { - color: #fff; - background: rgba(255, 255, 255, 0.1); - } - .lightbox-prev { left: 1rem; } - .lightbox-next { right: 1rem; } - /* 卡片封面:blur-img 结构,固定 4:3 比例,hover 缩放 */ .post-card-cover-blur { margin: 0; diff --git a/libs/lightbox/src/index.ts b/libs/lightbox/src/index.ts index c30537c..1d1478f 100644 --- a/libs/lightbox/src/index.ts +++ b/libs/lightbox/src/index.ts @@ -1,4 +1,5 @@ import { fitCentered, transformFor, originalUrl, type Rect } from "./geometry"; +import "./style.css"; interface LightboxState { overlay: HTMLDivElement; diff --git a/libs/lightbox/src/style.css b/libs/lightbox/src/style.css new file mode 100644 index 0000000..d40aeec --- /dev/null +++ b/libs/lightbox/src/style.css @@ -0,0 +1,71 @@ +/* ========== 统一灯箱(Medium 风格) ========== */ +.lightbox-overlay { + position: fixed; + inset: 0; + z-index: 50; + /* overflow:hidden 裁剪超大的 absolute img,防止原图(可能数千 px) + 撑大文档可滚动区,否则会触发非预期的 scroll 事件。 */ + overflow: hidden; + background: rgba(0, 0, 0, 0.92); + opacity: 1; +} + +/* 灯箱图片:绝对定位,尺寸/位置由 JS 的 transform 控制。 + transform-origin 为 top left,配合 translate+scale 实现原地缩放。 + 显式 width/height 由 JS 设置为原图尺寸,确保布局尺寸确定, + transform 只做视觉缩放,不改变占位。 */ +.lightbox-img { + position: absolute; + left: 0; + top: 0; + transform-origin: top left; + max-width: none; + max-height: none; + object-fit: contain; + opacity: 1; +} + +.lightbox-caption { + position: fixed; + left: 0; + right: 0; + bottom: 2rem; + text-align: center; + color: rgba(255, 255, 255, 0.85); + font-size: 0.95rem; + padding: 0 2rem; + pointer-events: none; + z-index: 2; +} + +.lightbox-counter { + position: fixed; + right: 1.5rem; + bottom: 1.5rem; + color: rgba(255, 255, 255, 0.7); + font-size: 0.85rem; + z-index: 2; +} + +.lightbox-nav { + position: fixed; + top: 50%; + transform: translateY(-50%); + background: transparent; + border: none; + color: rgba(255, 255, 255, 0.7); + font-size: 3rem; + line-height: 1; + width: 3rem; + height: 5rem; + cursor: pointer; + z-index: 2; + border-radius: 0.5rem; + transition: color 0.2s ease-out, background 0.2s ease-out; +} +.lightbox-nav:hover { + color: #fff; + background: rgba(255, 255, 255, 0.1); +} +.lightbox-prev { left: 1rem; } +.lightbox-next { right: 1rem; } diff --git a/libs/lightbox/src/vite-env.d.ts b/libs/lightbox/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/libs/lightbox/src/vite-env.d.ts @@ -0,0 +1 @@ +/// From 3284b03819fab24a426588509cfa40ea0aaf58d9 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 24 Jun 2026 11:04:29 +0800 Subject: [PATCH 05/10] build: register lightbox assets in Dioxus.toml, gitignore public/lightbox --- .gitignore | 1 + Dioxus.toml | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 676d43a..c727651 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ others/ public/style.css public/highlight.css public/tiptap +public/lightbox /static .env docs/superpowers/ diff --git a/Dioxus.toml b/Dioxus.toml index 7abe9e8..94fff66 100644 --- a/Dioxus.toml +++ b/Dioxus.toml @@ -10,9 +10,9 @@ title = "Yggdrasil - Dioxus SSR" watch_path = ["src", "Cargo.toml"] [web.resource] -style = ["/style.css", "/highlight.css", "/tiptap/editor.css"] -script = ["/tiptap/editor.js"] +style = ["/style.css", "/highlight.css", "/tiptap/editor.css", "/lightbox/lightbox.css"] +script = ["/tiptap/editor.js", "/lightbox/lightbox.js"] [web.resource.dev] -style = ["/style.css", "/highlight.css", "/tiptap/editor.css"] -script = ["/tiptap/editor.js"] +style = ["/style.css", "/highlight.css", "/tiptap/editor.css", "/lightbox/lightbox.css"] +script = ["/tiptap/editor.js", "/lightbox/lightbox.js"] From adf9e9cf078482f17bdd53520542f534b6391cf9 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 24 Jun 2026 11:06:03 +0800 Subject: [PATCH 06/10] build: add build-lightbox targets, wire into build/build-linux/dev/test --- Makefile | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 94afbc1..b95e1d9 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,15 @@ -.PHONY: dev build build-linux css css-watch clean build-editor build-editor-incremental highlight-css test +.PHONY: dev build build-linux css css-watch clean build-editor build-editor-incremental build-lightbox build-lightbox-incremental highlight-css test build: @$(MAKE) build-editor + @$(MAKE) build-lightbox @$(MAKE) highlight-css @tailwindcss -i input.css -o public/style.css --minify @dx build --release --debug-symbols=false build-linux: @$(MAKE) build-editor + @$(MAKE) build-lightbox @$(MAKE) highlight-css @tailwindcss -i input.css -o public/style.css --minify @dx build @client --release --debug-symbols=false --wasm-js-cfg false @@ -26,7 +28,16 @@ build-editor: build-editor-incremental: @cd libs/tiptap-editor && npm run build -dev: build-editor-incremental +build-lightbox: + @echo "Building Lightbox..." + @cd libs/lightbox && npm ci --include=dev && npm run build + @echo "Lightbox built." + +# dev 用的增量构建:跳过 npm ci(假设 node_modules 已存在),仅 vite build。 +build-lightbox-incremental: + @cd libs/lightbox && npm run build + +dev: build-editor-incremental build-lightbox-incremental @echo "Cleaning static/..." @rm -rf static/ @echo "Building Tiptap editor (incremental)..." @@ -45,6 +56,7 @@ css-watch: test: @cargo test @cd libs/tiptap-editor && npm test + @cd libs/lightbox && npm test clean: @cargo clean From 0301185a1715d635d4cc8721eacdbc3e0ff85575 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 24 Jun 2026 11:07:24 +0800 Subject: [PATCH 07/10] refactor(post-content): drop lightbox include_str!, use config-driven init --- src/components/post/post_content.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/post/post_content.rs b/src/components/post/post_content.rs index 75fe4e8..032640a 100644 --- a/src/components/post/post_content.rs +++ b/src/components/post/post_content.rs @@ -10,17 +10,22 @@ use dioxus::prelude::*; /// - `content_html`:服务端渲染的文章 HTML 字符串 /// /// 关键行为: -/// - 在 `target_arch = "wasm32"` 环境下执行 `post-content.js`(代码块复制)与 -/// `lightbox.js`(图片灯箱 + 懒加载),并调用各自的初始化函数。 +/// - 在 `target_arch = "wasm32"` 环境下执行 `post-content.js`(代码块复制)。 +/// 灯箱(图片灯箱 + 懒加载)改由 `Dioxus.toml` 全局注入 `lightbox.js`, +/// 这里仅设置其初始化配置 `__lightboxSelectors` 并兜底调用。 #[component] pub fn PostContent(content_html: String) -> Element { #[cfg(target_arch = "wasm32")] use_effect(move || { let _ = js_sys::eval(include_str!("../../../public/js/post-content.js")); - let _ = js_sys::eval(include_str!("../../../public/js/lightbox.js")); let _ = js_sys::eval("window.__initPostContent('.post-content')"); - // 正文图组成图集;封面(.entry-cover,带 lightbox-single class)单张模式。 - let _ = js_sys::eval("window.__initLightbox(['.post-content', '.entry-cover'])"); + // lightbox 改由 Dioxus.toml 全局