diff --git a/.github/instructions/css.instructions.md b/.github/instructions/css.instructions.md new file mode 100644 index 0000000..6162384 --- /dev/null +++ b/.github/instructions/css.instructions.md @@ -0,0 +1,16 @@ +--- +applyTo: '**/*.css,**/*.scss' +--- + +SCSS is used for styling, but avoid using mixins. Use BEM methodology for class naming. + +## CSS Library +We use Optics for our CSS library. It provides a set of components and tokens that should be used +to maintain consistency across the application. Consider if the component you are creating should be a modifier of an existing component before creating a new one. + +## CSS Variables +Avoid adding in fallbacks for CSS variables. See the list of available Optics tokens in the +`.github/docs/optics/tokens.json` file. + +## Specificity +Never use `!important`. Always make the selector more specific to solve those issues. diff --git a/.github/instructions/js.instructions.md b/.github/instructions/js.instructions.md new file mode 100644 index 0000000..52c829e --- /dev/null +++ b/.github/instructions/js.instructions.md @@ -0,0 +1,11 @@ +--- +applyTo: "**/*.js,**/*.mjs,**/*.cjs" +--- + +- Prefer ESM syntax for JavaScript files, and avoid using CommonJS syntax unless the file is already CommonJS. +- Important: Never use semicolons at the end of statements. +- Use single quotes for strings, except when the string contains a single quote, in which case use double quotes. +- Use `const` for variables that are not reassigned, and `let` for variables that are reassigned. + +## Frameworks and Libraries +- Use [Lit](https://lit.dev/) for building web components. diff --git a/.github/instructions/project.instructions.md b/.github/instructions/project.instructions.md new file mode 100644 index 0000000..4c1206d --- /dev/null +++ b/.github/instructions/project.instructions.md @@ -0,0 +1,18 @@ +--- +applyTo: '**/*' +--- +## Project Context +This project is a collection of web components developed by RoleModel Software, designed to enhance web applications with reusable UI elements. The PDF Viewer component leverages PDF.js to provide a customizable and embeddable PDF viewing experience. + +## General +- Do not create summary markdown files after making changes + +## Tech Stack +- Lit for web components +- PDF.js for PDF rendering + +## Response +- Provide evidence-based responses to feedback, focusing on technical accuracy and clarity. +- Maintain a professional and constructive tone in all communications. +- Avoid unnecessary embellishments or emotional language; focus on the task at hand. +— Avoid unnecessary comments; the code should be self-explanatory. diff --git a/.github/workflows/release-package.yml b/.github/workflows/release-package.yml new file mode 100644 index 0000000..0c7bb14 --- /dev/null +++ b/.github/workflows/release-package.yml @@ -0,0 +1,20 @@ +name: Deploy to NPM + +on: + release: + types: [published] + +jobs: + build-npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + - run: yarn install --immutable + - run: yarn build + - run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/README.md b/README.md index f6c1478..235de06 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ -# spider -Shared web components library +# Spider web components +i By RoleModel Software + +# PDF Viewer + +[PDF.js](https://mozilla.github.io/pdf.js) is awesome, but you will notice this paragraph in their setup instructions: +> The viewer is built on the display layer and is the UI for PDF viewer in Firefox and the other browser extensions within the project. It can be a good starting point for building your own viewer. However, we do ask if you plan to embed the viewer in your own site, that it not just be an unmodified version. Please re-skin it or build upon it. + +This component aims to be that skin layer built upon PDF.js packaged in a lovely drop-in web component. diff --git a/data/example.pdf b/data/example.pdf new file mode 100644 index 0000000..d58f176 Binary files /dev/null and b/data/example.pdf differ diff --git a/data/the-veldt.pdf b/data/the-veldt.pdf new file mode 100644 index 0000000..70c88ab Binary files /dev/null and b/data/the-veldt.pdf differ diff --git a/index.html b/index.html index 61c5f56..da0024d 100644 --- a/index.html +++ b/index.html @@ -5,9 +5,10 @@ Spider Web Components - + + - + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0113e08 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1432 @@ +{ + "name": "spider", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "spider", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "lit": "^3.3.2", + "pdfjs-dist": "^5.4.624" + }, + "devDependencies": { + "vite": "^7.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.5.1.tgz", + "integrity": "sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", + "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0" + } + }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.89", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.89.tgz", + "integrity": "sha512-7GjmkMirJHejeALCqUnZY3QwID7bbumOiLrqq2LKgxrdjdmxWQBTc6rcASa2u8wuWrH7qo4/4n/VNrOwCoKlKg==", + "license": "MIT", + "optional": true, + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.89", + "@napi-rs/canvas-darwin-arm64": "0.1.89", + "@napi-rs/canvas-darwin-x64": "0.1.89", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.89", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.89", + "@napi-rs/canvas-linux-arm64-musl": "0.1.89", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.89", + "@napi-rs/canvas-linux-x64-gnu": "0.1.89", + "@napi-rs/canvas-linux-x64-musl": "0.1.89", + "@napi-rs/canvas-win32-arm64-msvc": "0.1.89", + "@napi-rs/canvas-win32-x64-msvc": "0.1.89" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.89", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.89.tgz", + "integrity": "sha512-CXxQTXsjtQqKGENS8Ejv9pZOFJhOPIl2goenS+aU8dY4DygvkyagDhy/I07D1YLqrDtPvLEX5zZHt8qUdnuIpQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.89", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.89.tgz", + "integrity": "sha512-k29cR/Zl20WLYM7M8YePevRu2VQRaKcRedYr1V/8FFHkyIQ8kShEV+MPoPGi+znvmd17Eqjy2Pk2F2kpM2umVg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.89", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.89.tgz", + "integrity": "sha512-iUragqhBrA5FqU13pkhYBDbUD1WEAIlT8R2+fj6xHICY2nemzwMUI8OENDhRh7zuL06YDcRwENbjAVxOmaX9jg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.89", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.89.tgz", + "integrity": "sha512-y3SM9sfDWasY58ftoaI09YBFm35Ig8tosZqgahLJ2WGqawCusGNPV9P0/4PsrLOCZqGg629WxexQMY25n7zcvA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.89", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.89.tgz", + "integrity": "sha512-NEoF9y8xq5fX8HG8aZunBom1ILdTwt7ayBzSBIwrmitk7snj4W6Fz/yN/ZOmlM1iyzHDNX5Xn0n+VgWCF8BEdA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.89", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.89.tgz", + "integrity": "sha512-UQQkIEzV12/l60j1ziMjZ+mtodICNUbrd205uAhbyTw0t60CrC/EsKb5/aJWGq1wM0agvcgZV72JJCKfLS6+4w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.89", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.89.tgz", + "integrity": "sha512-1/VmEoFaIO6ONeeEMGoWF17wOYZOl5hxDC1ios2Bkz/oQjbJJ8DY/X22vWTmvuUKWWhBVlo63pxLGZbjJU/heA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.89", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.89.tgz", + "integrity": "sha512-ebLuqkCuaPIkKgKH9q4+pqWi1tkPOfiTk5PM1LKR1tB9iO9sFNVSIgwEp+SJreTSbA2DK5rW8lQXiN78SjtcvA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.89", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.89.tgz", + "integrity": "sha512-w+5qxHzplvA4BkHhCaizNMLLXiI+CfP84YhpHm/PqMub4u8J0uOAv+aaGv40rYEYra5hHRWr9LUd6cfW32o9/A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-arm64-msvc": { + "version": "0.1.89", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.89.tgz", + "integrity": "sha512-DmyXa5lJHcjOsDC78BM3bnEECqbK3xASVMrKfvtT/7S7Z8NGQOugvu+L7b41V6cexCd34mBWgMOsjoEBceeB1Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.89", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.89.tgz", + "integrity": "sha512-WMej0LZrIqIncQcx0JHaMXlnAG7sncwJh7obs/GBgp0xF9qABjwoRwIooMWCZkSansapKGNUHhamY6qEnFN7gA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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/lit": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.2.tgz", + "integrity": "sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", + "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.2.tgz", + "integrity": "sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/node-readable-to-web-readable-stream": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/node-readable-to-web-readable-stream/-/node-readable-to-web-readable-stream-0.4.2.tgz", + "integrity": "sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pdfjs-dist": { + "version": "5.4.624", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.624.tgz", + "integrity": "sha512-sm6TxKTtWv1Oh6n3C6J6a8odejb5uO4A4zo/2dgkHuC0iu8ZMAXOezEODkVaoVp8nX1Xzr+0WxFJJmUr45hQzg==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.88", + "node-readable-to-web-readable-stream": "^0.4.2" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/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/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "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", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.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 + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json index abd1506..b745539 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "spider", + "name": "@rolemodel/spider", "description": "Shared high level web components for RoleModel Software and beyond", "packageManager": "yarn@4.12.0", "version": "0.0.1", @@ -33,9 +33,14 @@ "build": "tsc && rsync -a src/assets dist/" }, "dependencies": { - "lit": "^3.3.2" + "@lit/context": "^1.1.6", + "lit": "^3.3.2", + "pdfjs-dist": "^5.4.624" }, "devDependencies": { "vite": "^7.2.4" + }, + "publishConfig": { + "@rolemodel-it-support": "https://npm.pkg.github.com" } } diff --git a/src/assets/application.css b/src/assets/application.css new file mode 100644 index 0000000..293d3b1 --- /dev/null +++ b/src/assets/application.css @@ -0,0 +1,3 @@ +body { + margin: 0; +} diff --git a/src/assets/icons/arrow-left.svg b/src/assets/icons/arrow-left.svg new file mode 100644 index 0000000..7120ac5 --- /dev/null +++ b/src/assets/icons/arrow-left.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/arrow-right.svg b/src/assets/icons/arrow-right.svg new file mode 100644 index 0000000..1192434 --- /dev/null +++ b/src/assets/icons/arrow-right.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/close-sidebar.svg b/src/assets/icons/close-sidebar.svg new file mode 100644 index 0000000..9bba2ad --- /dev/null +++ b/src/assets/icons/close-sidebar.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/close.svg b/src/assets/icons/close.svg new file mode 100644 index 0000000..86c027f --- /dev/null +++ b/src/assets/icons/close.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/download.svg b/src/assets/icons/download.svg new file mode 100644 index 0000000..6affdf1 --- /dev/null +++ b/src/assets/icons/download.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/open-sidebar.svg b/src/assets/icons/open-sidebar.svg new file mode 100644 index 0000000..1da26db --- /dev/null +++ b/src/assets/icons/open-sidebar.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/print.svg b/src/assets/icons/print.svg new file mode 100644 index 0000000..56c9aa2 --- /dev/null +++ b/src/assets/icons/print.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/zoom-in.svg b/src/assets/icons/zoom-in.svg new file mode 100644 index 0000000..a152736 --- /dev/null +++ b/src/assets/icons/zoom-in.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/zoom-out.svg b/src/assets/icons/zoom-out.svg new file mode 100644 index 0000000..45f1bf5 --- /dev/null +++ b/src/assets/icons/zoom-out.svg @@ -0,0 +1 @@ + diff --git a/src/components/pdf-viewer/README.md b/src/components/pdf-viewer/README.md new file mode 100644 index 0000000..4b788d6 --- /dev/null +++ b/src/components/pdf-viewer/README.md @@ -0,0 +1,195 @@ +# PDF Viewer Web Component + +A composable PDF viewer web component built with Lit and PDF.js. + +## Features + +- 📄 Full PDF rendering with PDF.js +- 📝 Text selection and copying +- 🖼️ Thumbnail navigation sidebar +- 🔍 Zoom controls (in/out) +- ⏮️ Page navigation (previous/next) +- 📱 Responsive layout +- 🎨 Customizable through CSS +- 🔧 Composable architecture +- 🎯 Framework agnostic + +## Installation + +Simply copy the entire `pdf-viewer` directory into your project. + +## Usage + +### Basic Usage + +```html + + + + + + + + + +``` + +### Properties + +| Property | Type | Default | Description | +|----------|--------|---------|--------------------------| +| `src` | String | `''` | Path to the PDF file | +| `theme-hue` | Number | `217` | Hue value (0-360) for the theme color | +| `theme-saturation` | Number | `89` | Saturation value (0-100) for the theme color | + +### Theme Customization + +The PDF viewer supports theme customization through HSL (Hue, Saturation, Lightness) values. You can set the primary theme color by adjusting the hue and saturation: + +```html + + + + + + + + + + + +``` + +The component automatically generates a complete color scale based on your theme values: +- `--theme-primary`: Main theme color +- `--theme-primary-light`: Lighter variant +- `--theme-primary-lighter`: Even lighter variant +- `--theme-primary-dark`: Darker variant +- `--theme-primary-darker`: Even darker variant +- `--theme-neutral-*`: Grayscale colors (50-600) +- `--theme-border`: Border color +- `--theme-shadow`: Shadow color + +These CSS variables are automatically applied to: +- Active page indicators +- Focus states on inputs +- Hover states on thumbnails +- Border highlights + +### Events + +The component emits the following events: + +#### `pdf-loaded` +Fired when PDF is successfully loaded. +```javascript +viewer.addEventListener('pdf-loaded', (e) => { + console.log(`Total pages: ${e.detail.totalPages}`) +}) +``` + +#### `page-change` +Fired when the current page changes. +```javascript +viewer.addEventListener('page-change', (e) => { + console.log(`Current page: ${e.detail.pageNumber}`) +}) +``` + +#### `scale-change` +Fired when zoom scale changes. +```javascript +viewer.addEventListener('scale-change', (e) => { + console.log(`Zoom: ${Math.round(e.detail.scale * 100)}%`) +}) +``` + +#### `pdf-error` +Fired when PDF fails to load. +```javascript +viewer.addEventListener('pdf-error', (e) => { + console.error('Failed to load PDF:', e.detail.error) +}) +``` + +### JavaScript API + +```javascript +const viewer = document.querySelector('pdf-viewer') + +// Change PDF source dynamically +viewer.src = '/path/to/different.pdf' +``` + +## Architecture + +The component uses a composable architecture with a root component and child components: + +``` +pdf-viewer (root) +├── toolbar - Navigation and zoom controls +├── sidebar - Thumbnail navigation +│ └── thumbnail +├── canvas - Main PDF display +``` + +### Component Structure + +``` +pdf-viewer/ +├── pdf-viewer.js # Root component +├── pdf-viewer.styles.js # Root styles +├── index.js # Exports +├── toolbar/ # Toolbar component +│ ├── pdf-toolbar.js +│ └── pdf-toolbar.styles.js +├── sidebar/ # Sidebar component +│ ├── pdf-sidebar.js +│ └── pdf-sidebar.styles.js +├── canvas/ # Canvas component +│ ├── pdf-canvas.js +│ └── pdf-canvas.styles.js +└── thumbnail/ # Thumbnail component + ├── pdf-thumbnail.js + └── pdf-thumbnail.styles.js +``` + +## Dependencies + +- [Lit](https://lit.dev/) - Web component framework +- [PDF.js](https://mozilla.github.io/pdf.js/) - PDF rendering library + +## Browser Support + +Works in all modern browsers that support: +- Web Components (Custom Elements v1) +- Shadow DOM v1 +- ES Modules + +## Customization + +The component uses Shadow DOM with CSS custom properties for styling. Each sub-component has its own styles file that can be customized. + +## Context API + +Child components access shared state through a Symbol-based context pattern. See `COMPONENT-ARCHITECTURE.md` for technical details. + +## Text Selection + +The PDF viewer implements a text layer overlay system similar to the official PDF.js viewer, allowing users to select and copy text directly from the PDF: + +- Text is rendered in an invisible layer positioned precisely over the PDF canvas +- Selection highlighting appears when text is selected +- Users can copy selected text to clipboard using standard browser shortcuts (Cmd/Ctrl+C) +- Text layer automatically scales with zoom level +- No additional configuration required - works out of the box + +The text layer is implemented in the `pdf-canvas` component using PDF.js's `getTextContent()` API to extract and position text elements. + +## License + +See the main project LICENSE file. + +## Credits + +Built by RoleModel Software diff --git a/src/components/pdf-viewer/canvas/rm-pdf-canvas.js b/src/components/pdf-viewer/canvas/rm-pdf-canvas.js new file mode 100644 index 0000000..496ff1d --- /dev/null +++ b/src/components/pdf-viewer/canvas/rm-pdf-canvas.js @@ -0,0 +1,125 @@ +import { html } from 'lit' +import { PDFViewerComponent } from '../pdf-viewer-component.js' +import styles from './rm-pdf-canvas.styles.js' +import '../page/rm-pdf-page.js' + +export default class PDFCanvas extends PDFViewerComponent { + static get styles() { + return styles + } + + static get properties() { + return { + pages: { type: Array, state: true } + } + } + + constructor() { + super() + this.pages = [] + this.scrollTimeout = null + } + + async updated() { + if (this.context?.pdfDoc && this.pages.length === 0) { + await this.loadPages() + } + + if (this.context?.shouldScroll) { + this.scrollToPage(this.context.currentPage) + } + } + + async loadPages() { + if (!this.context?.pdfDoc) return + + const { pdfDoc, totalPages } = this.context + const pages = [] + + for (let pageNum = 1; pageNum <= totalPages; pageNum++) { + try { + const page = await pdfDoc.getPage(pageNum) + pages.push({ page, pageNumber: pageNum }) + } catch (error) { + console.error(`Error loading page ${pageNum}:`, error) + } + } + + this.pages = pages + } + + scrollToPage(pageNum) { + const container = this.shadowRoot.querySelector('.canvas-container') + const pageElements = this.shadowRoot.querySelectorAll('rm-pdf-page') + const targetPage = Array.from(pageElements).find( + el => el.pageNumber === pageNum + ) + + if (container && targetPage) { + const containerTop = container.getBoundingClientRect().top + const pageTop = targetPage.getBoundingClientRect().top + const offset = pageTop - containerTop + container.scrollTop - 32 + + container.scrollTo({ + top: offset, + behavior: 'smooth' + }) + + this.context?.setShouldScroll(false) + } + } + + handleScroll() { + if (!this.context?.setCurrentPage) return + + clearTimeout(this.scrollTimeout) + + this.scrollTimeout = setTimeout(() => { + const container = this.shadowRoot.querySelector('.canvas-container') + if (!container) return + + const containerRect = container.getBoundingClientRect() + const centerY = containerRect.top + containerRect.height / 2 + + const pageElements = this.shadowRoot.querySelectorAll('rm-pdf-page') + let currentPage = null + + for (const pageElement of pageElements) { + const rect = pageElement.getBoundingClientRect() + if (rect.top <= centerY && rect.bottom >= centerY) { + currentPage = pageElement.pageNumber + break + } + } + + if (currentPage !== this.context.currentPage) { + this._contextCurrentPage = currentPage + this.context.setCurrentPage(currentPage) + } + }, 150) + } + + firstUpdated() { + const container = this.shadowRoot.querySelector('.canvas-container') + if (container) { + container.addEventListener('scroll', () => this.handleScroll()) + } + } + + render() { + return html` +
+ ${this.pages.map(({ page, pageNumber }) => html` + + + `)} +
+ ` + } +} + +customElements.define('rm-pdf-canvas', PDFCanvas) + diff --git a/src/components/pdf-viewer/canvas/rm-pdf-canvas.styles.js b/src/components/pdf-viewer/canvas/rm-pdf-canvas.styles.js new file mode 100644 index 0000000..278f030 --- /dev/null +++ b/src/components/pdf-viewer/canvas/rm-pdf-canvas.styles.js @@ -0,0 +1,20 @@ +import { css } from 'lit' + +export default css` + :host { + display: block; + width: 100%; + height: 100%; + } + + .canvas-container { + height: 100%; + overflow: auto; + display: flex; + flex-direction: column; + align-items: center; + padding: var(--theme-spacing-lg, 2rem); + background: var(--theme-neutral-100, #f3f3f3); + gap: var(--theme-spacing-md, 1rem); + } +` diff --git a/src/components/pdf-viewer/index.js b/src/components/pdf-viewer/index.js new file mode 100644 index 0000000..518442d --- /dev/null +++ b/src/components/pdf-viewer/index.js @@ -0,0 +1,2 @@ +export { default as PDFViewer } from './rm-pdf-viewer.js' +export { PDFContext } from './rm-pdf-viewer.js' diff --git a/src/components/pdf-viewer/page/rm-pdf-page.js b/src/components/pdf-viewer/page/rm-pdf-page.js new file mode 100644 index 0000000..041c8f7 --- /dev/null +++ b/src/components/pdf-viewer/page/rm-pdf-page.js @@ -0,0 +1,135 @@ +import { html, LitElement } from 'lit' +import * as pdfjsLib from 'pdfjs-dist' +import styles from './rm-pdf-page.styles.js' + +export default class PDFPage extends LitElement { + static get styles() { + return styles + } + + static get properties() { + return { + page: { type: Object }, + scale: { type: Number }, + pageNumber: { type: Number } + } + } + + constructor() { + super() + this.page = null + this.scale = 1 + this.pageNumber = 1 + this._renderTask = null + } + + async updated(changedProperties) { + if (this.page && (changedProperties.has('page') || changedProperties.has('scale'))) { + await this.renderPage() + } + } + + async renderPage() { + if (!this.page) return + + this.#cancelRenderTask() + + const viewport = this.page.getViewport({ scale: this.scale }) + const devicePixelRatio = window.devicePixelRatio || 1 + + const pageWrapper = this.shadowRoot.querySelector('.page-wrapper') + if (!pageWrapper) return + + pageWrapper.style.width = `${viewport.width}px` + pageWrapper.style.height = `${viewport.height}px` + + const canvas = this.shadowRoot.querySelector('canvas') + const canvasContext = canvas.getContext('2d') + + canvas.height = viewport.height * devicePixelRatio + canvas.width = viewport.width * devicePixelRatio + canvas.style.width = `${viewport.width}px` + canvas.style.height = `${viewport.height}px` + + const scaledViewport = this.page.getViewport({ scale: this.scale * devicePixelRatio }) + + try { + this._renderTask = this.page.render({ + canvasContext, + viewport: scaledViewport + }) + + await this._renderTask.promise + this._renderTask = null + + const textLayerDiv = this.shadowRoot.querySelector('.text-layer') + textLayerDiv.style.width = `${viewport.width}px` + textLayerDiv.style.height = `${viewport.height}px` + textLayerDiv.innerHTML = '' + + await this.renderTextLayer(viewport, textLayerDiv) + } catch (error) { + if (error.name !== 'RenderingCancelledException') { + console.error('Error rendering page:', error) + } + this._renderTask = null + } + } + + async renderTextLayer(viewport, textLayerDiv) { + try { + const textContent = await this.page.getTextContent() + + textContent.items.forEach((textItem) => { + const tx = pdfjsLib.Util.transform( + viewport.transform, + textItem.transform + ) + + const fontSize = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3])) + const fontHeight = fontSize + + const textDiv = document.createElement('div') + textDiv.style.position = 'absolute' + textDiv.style.left = `${tx[4]}px` + textDiv.style.top = `${tx[5] - fontHeight}px` + textDiv.style.fontSize = `${fontSize}px` + textDiv.style.fontFamily = textItem.fontName || 'sans-serif' + + if (textItem.str.length > 0) { + const width = textItem.width * viewport.scale + textDiv.style.width = `${width}px` + } + + textDiv.textContent = textItem.str + + textLayerDiv.appendChild(textDiv) + }) + } catch (error) { + console.error('Error rendering text layer:', error) + } + } + + disconnectedCallback() { + super.disconnectedCallback() + this.#cancelRenderTask() + } + + render() { + return html` +
+ +
+
+ ` + } + + #cancelRenderTask() { + if (this._renderTask) { + this._renderTask.cancel() + this._renderTask = null + } + } +} + +customElements.define('rm-pdf-page', PDFPage) diff --git a/src/components/pdf-viewer/page/rm-pdf-page.styles.js b/src/components/pdf-viewer/page/rm-pdf-page.styles.js new file mode 100644 index 0000000..dd9401b --- /dev/null +++ b/src/components/pdf-viewer/page/rm-pdf-page.styles.js @@ -0,0 +1,41 @@ +import { css } from 'lit' + +export default css` + :host { + display: block; + } + + .page-wrapper { + position: relative; + margin-bottom: var(--theme-spacing-md, 1rem); + } + + canvas { + display: block; + box-shadow: 0 var(--theme-spacing-xs, 0.25rem) var(--theme-spacing-sm, 0.5rem) var(--theme-shadow, rgba(0, 0, 0, 0.1)); + } + + .text-layer { + position: absolute; + left: 0; + top: 0; + overflow: hidden; + opacity: 0.2; + line-height: 1; + text-align: initial; + pointer-events: auto; + } + + .text-layer > div { + color: transparent; + position: absolute; + white-space: pre; + cursor: text; + transform-origin: 0% 0%; + user-select: text; + } + + .text-layer > div::selection { + background-color: var(--theme-primary-transparent); + } +` diff --git a/src/components/pdf-viewer/pdf-context.js b/src/components/pdf-viewer/pdf-context.js new file mode 100644 index 0000000..e589bd8 --- /dev/null +++ b/src/components/pdf-viewer/pdf-context.js @@ -0,0 +1,3 @@ +import { createContext } from '@lit/context' + +export const pdfContext = createContext(Symbol('pdf-context')) diff --git a/src/components/pdf-viewer/pdf-viewer-component.js b/src/components/pdf-viewer/pdf-viewer-component.js new file mode 100644 index 0000000..89d5cb0 --- /dev/null +++ b/src/components/pdf-viewer/pdf-viewer-component.js @@ -0,0 +1,22 @@ +import { LitElement } from 'lit' +import { ContextConsumer } from '@lit/context' +import { pdfContext } from './pdf-context.js' + +export class PDFViewerComponent extends LitElement { + static get styles() { + return [] + } + + constructor() { + super() + this._contextConsumer = new ContextConsumer(this, { + context: pdfContext, + callback: (value) => { + this.context = value + this.requestUpdate() + }, + subscribe: true + }) + this.context = null + } +} diff --git a/src/components/pdf-viewer/pdf-viewer.js b/src/components/pdf-viewer/pdf-viewer.js deleted file mode 100644 index 72d2410..0000000 --- a/src/components/pdf-viewer/pdf-viewer.js +++ /dev/null @@ -1,17 +0,0 @@ -import { LitElement, html } from 'lit' -import styles from './pdf-viewer.styles.js' - -export default class PDFViewer extends LitElement { - static get styles() { - return styles; - } - - render() { - return html` -
We are ago
- ` - } -} - -customElements.define('pdf-viewer', PDFViewer); - diff --git a/src/components/pdf-viewer/pdf-viewer.styles.js b/src/components/pdf-viewer/pdf-viewer.styles.js deleted file mode 100644 index 9bbf3ec..0000000 --- a/src/components/pdf-viewer/pdf-viewer.styles.js +++ /dev/null @@ -1,7 +0,0 @@ -import { css } from 'lit'; - -export default css` - .test { - color: red; - } -`; diff --git a/src/components/pdf-viewer/rm-pdf-viewer.js b/src/components/pdf-viewer/rm-pdf-viewer.js new file mode 100644 index 0000000..48311ef --- /dev/null +++ b/src/components/pdf-viewer/rm-pdf-viewer.js @@ -0,0 +1,227 @@ +import { LitElement, html } from 'lit' +import { ContextProvider } from '@lit/context' +import * as pdfjsLib from 'pdfjs-dist' +import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.mjs?url' +import styles from './rm-pdf-viewer.styles.js' +import { pdfContext } from './pdf-context.js' +import { updateThemeColors } from './theme-config.js' +import './toolbar/rm-pdf-toolbar.js' +import './sidebar/rm-pdf-sidebar.js' +import './canvas/rm-pdf-canvas.js' + +pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker + +export default class PDFViewer extends LitElement { + static get properties() { + return { + src: { type: String }, + themeHue: { type: Number, attribute: 'theme-hue' }, + themeSaturation: { type: Number, attribute: 'theme-saturation' }, + pdfDoc: { type: Object, state: true }, + currentPage: { type: Number, state: true }, + totalPages: { type: Number, state: true }, + scale: { type: Number, state: true }, + sidebarCollapsed: { type: Boolean, state: true }, + shouldScroll: { type: Boolean, state: true } + } + } + + static get styles() { + return styles + } + + constructor() { + super() + this.src = '' + this.themeHue = 217 + this.themeSaturation = 89 + this.pdfDoc = null + this.currentPage = 1 + this.totalPages = 0 + this.scale = 1.5 + this.sidebarCollapsed = false + this.shouldScroll = false + + this._provider = new ContextProvider(this, { + context: pdfContext, + initialValue: this._createContextValue() + }) + } + + _createContextValue() { + return { + pdfDoc: this.pdfDoc, + currentPage: this.currentPage, + totalPages: this.totalPages, + scale: this.scale, + sidebarCollapsed: this.sidebarCollapsed, + shouldScroll: this.shouldScroll, + setCurrentPage: (page) => { + this.currentPage = page + }, + setScale: (scale) => { + this.scale = scale + }, + setShouldScroll: (shouldScroll) => { + this.shouldScroll = shouldScroll + }, + nextPage: () => { + if (this.currentPage < this.totalPages) { + this.currentPage++ + } + }, + previousPage: () => { + if (this.currentPage > 1) { + this.currentPage-- + } + }, + zoomIn: () => { + this.scale += 0.25 + }, + zoomOut: () => { + if (this.scale > 0.5) { + this.scale -= 0.25 + } + }, + print: async () => { + await this.printPDF() + }, + download: () => { + this.downloadPDF() + }, + toggleSidebar: () => { + this.sidebarCollapsed = !this.sidebarCollapsed + } + } + } + + async printPDF() { + if (!this.pdfDoc) return + + try { + const iframe = document.createElement('iframe') + iframe.style.display = 'none' + document.body.appendChild(iframe) + + const iframeDoc = iframe.contentDocument || iframe.contentWindow.document + + const style = iframeDoc.createElement('style') + style.textContent = ` + @page { + margin: 0; + size: auto; + } + body { + margin: 0; + } + img { + display: block; + width: 100%; + } + ` + iframeDoc.head.appendChild(style) + + for (let pageNum = 1; pageNum <= this.totalPages; pageNum++) { + const page = await this.pdfDoc.getPage(pageNum) + const viewport = page.getViewport({ scale: 1.5 }) + + const canvas = document.createElement('canvas') + const context = canvas.getContext('2d') + canvas.width = viewport.width + canvas.height = viewport.height + + await page.render({ + canvasContext: context, + viewport: viewport + }).promise + + const img = document.createElement('img') + img.src = canvas.toDataURL() + img.style.width = '100%' + img.style.pageBreakAfter = pageNum < this.totalPages ? 'always' : 'auto' + iframeDoc.body.appendChild(img) + } + + iframe.contentWindow.focus() + iframe.contentWindow.print() + + setTimeout(() => { + document.body.removeChild(iframe) + }, 1000) + } catch (error) { + console.error('Error printing PDF:', error) + } + } + + downloadPDF() { + if (!this.src) return + + try { + const link = document.createElement('a') + link.href = this.src + link.download = this.src.split('/').pop() || 'document.pdf' + link.click() + } catch (error) { + console.error('Error downloading PDF:', error) + } + } + + async updated(changedProperties) { + if ( + changedProperties.has('pdfDoc') || + changedProperties.has('currentPage') || + changedProperties.has('totalPages') || + changedProperties.has('scale') || + changedProperties.has('sidebarCollapsed') + ) { + this._provider.setValue(this._createContextValue()) + } + + if (changedProperties.has('themeHue') || changedProperties.has('themeSaturation')) { + this._updateThemeColors() + } + + if (changedProperties.has('src') && this.src) { + await this.loadPDF() + } + } + + _updateThemeColors() { + updateThemeColors(this, this.themeHue, this.themeSaturation) + } + + async firstUpdated() { + this._updateThemeColors() + if (this.src) { + await this.loadPDF() + } + } + + async loadPDF() { + if (!this.src) return + + try { + const loadingTask = pdfjsLib.getDocument(this.src) + this.pdfDoc = await loadingTask.promise + this.totalPages = this.pdfDoc.numPages + this.currentPage = 1 + } catch (error) { + console.error('Error loading PDF:', error) + } + } + + render() { + return html` +
+ +
+ + +
+
+ ` + } +} + +customElements.define('rm-pdf-viewer', PDFViewer) + diff --git a/src/components/pdf-viewer/rm-pdf-viewer.styles.js b/src/components/pdf-viewer/rm-pdf-viewer.styles.js new file mode 100644 index 0000000..223b481 --- /dev/null +++ b/src/components/pdf-viewer/rm-pdf-viewer.styles.js @@ -0,0 +1,23 @@ +import { css } from 'lit' +export default css` + + :host { + display: block; + width: 100%; + height: 100vh; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: var(--theme-font-size-md, 14px); + } + + .pdf-viewer-container { + display: flex; + flex-direction: column; + height: 100%; + } + + .content-container { + display: flex; + flex: 1; + overflow: hidden; + } +` diff --git a/src/components/pdf-viewer/sidebar/rm-pdf-sidebar.js b/src/components/pdf-viewer/sidebar/rm-pdf-sidebar.js new file mode 100644 index 0000000..28ddc0d --- /dev/null +++ b/src/components/pdf-viewer/sidebar/rm-pdf-sidebar.js @@ -0,0 +1,89 @@ +import { html } from 'lit' +import { PDFViewerComponent } from '../pdf-viewer-component.js' +import styles from './rm-pdf-sidebar.styles.js' +import '../thumbnail/rm-pdf-thumbnail.js' + +export default class PDFSidebar extends PDFViewerComponent { + static get styles() { + return styles + } + + static get properties() { + return { + _lastScrolledPage: { type: Number, state: true } + } + } + + constructor() { + super() + this._lastScrolledPage = 1 + } + + updated() { + if (this.context?.currentPage !== this._lastScrolledPage) { + this._lastScrolledPage = this.context.currentPage + this.scrollToActiveThumbnail() + } + } + + scrollToActiveThumbnail() { + const container = this.shadowRoot.querySelector('.thumbnails-container') + const thumbnails = this.shadowRoot.querySelectorAll('rm-pdf-thumbnail') + const activeThumbnail = Array.from(thumbnails).find( + thumb => thumb.pageNumber === this.context.currentPage + ) + + if (container && activeThumbnail) { + const containerRect = container.getBoundingClientRect() + const thumbnailRect = activeThumbnail.getBoundingClientRect() + const containerTop = containerRect.top + const containerBottom = containerRect.bottom + const thumbnailTop = thumbnailRect.top + const thumbnailBottom = thumbnailRect.bottom + + if (thumbnailTop < containerTop || thumbnailBottom > containerBottom) { + const offset = activeThumbnail.offsetTop - (container.clientHeight / 2) + (thumbnailRect.height / 2) + + container.scrollTo({ + top: offset, + behavior: 'smooth' + }) + } + } + } + + renderThumbnails() { + if (!this.context?.pdfDoc) return html`` + + const { totalPages } = this.context + + if (totalPages === 0) return html`` + + const thumbnails = [] + for (let i = 1; i <= totalPages; i++) { + thumbnails.push(html` + + `) + } + return thumbnails + } + + render() { + if (!this.context) return html`` + + const { sidebarCollapsed } = this.context + + return html` + + ` + } +} + +customElements.define('rm-pdf-sidebar', PDFSidebar) + diff --git a/src/components/pdf-viewer/sidebar/rm-pdf-sidebar.styles.js b/src/components/pdf-viewer/sidebar/rm-pdf-sidebar.styles.js new file mode 100644 index 0000000..79ea534 --- /dev/null +++ b/src/components/pdf-viewer/sidebar/rm-pdf-sidebar.styles.js @@ -0,0 +1,37 @@ +import { css } from 'lit' + +export default css` + :host { + display: block; + } + + .sidebar { + width: 200px; + background: var(--theme-neutral-50, #fafafa); + border-right: var(--theme-border-width-sm, 1px) solid var(--theme-border, #ddd); + overflow: hidden; + height: 100%; + position: relative; + transition: width 0.3s ease; + } + + .sidebar.collapsed { + width: 0; + } + + .thumbnails-container { + padding: var(--theme-spacing-md, 1rem); + display: flex; + flex-direction: column; + gap: var(--theme-spacing-md, 1rem); + opacity: 1; + transition: opacity 0.2s ease; + height: calc(100vh - 81px); + overflow-y: auto; + } + + .sidebar.collapsed .thumbnails-container { + opacity: 0; + pointer-events: none; + } +` diff --git a/src/components/pdf-viewer/theme-config.js b/src/components/pdf-viewer/theme-config.js new file mode 100644 index 0000000..a7ead39 --- /dev/null +++ b/src/components/pdf-viewer/theme-config.js @@ -0,0 +1,36 @@ +export const updateThemeColors = (element, hue = 217, saturation = 89) => { + element.style.setProperty('--theme-primary', `hsl(${hue}, ${saturation}%, 50%)`) + element.style.setProperty('--theme-primary-light', `hsl(${hue}, ${saturation}%, 90%)`) + element.style.setProperty('--theme-primary-lighter', `hsl(${hue}, ${saturation}%, 95%)`) + element.style.setProperty('--theme-primary-dark', `hsl(${hue}, ${saturation}%, 40%)`) + element.style.setProperty('--theme-primary-darker', `hsl(${hue}, ${saturation}%, 30%)`) + element.style.setProperty('--theme-primary-transparent', `hsla(${hue}, ${saturation}%, 50%, 70%)`) + + element.style.setProperty('--theme-neutral-50', 'hsl(0, 0%, 98%)') + element.style.setProperty('--theme-neutral-100', 'hsl(0, 0%, 95%)') + element.style.setProperty('--theme-neutral-200', 'hsl(0, 0%, 87%)') + element.style.setProperty('--theme-neutral-300', 'hsl(0, 0%, 80%)') + element.style.setProperty('--theme-neutral-400', 'hsl(0, 0%, 63%)') + element.style.setProperty('--theme-neutral-500', 'hsl(0, 0%, 37%)') + element.style.setProperty('--theme-neutral-600', 'hsl(0, 0%, 20%)') + + element.style.setProperty('--theme-border', 'hsl(0, 0%, 87%)') + element.style.setProperty('--theme-shadow', 'rgba(0, 0, 0, 0.1)') + + element.style.setProperty('--theme-spacing-xs', '0.25rem') + element.style.setProperty('--theme-spacing-sm', '0.5rem') + element.style.setProperty('--theme-spacing-md', '1rem') + element.style.setProperty('--theme-spacing-lg', '2rem') + element.style.setProperty('--theme-spacing-xl', '3rem') + + element.style.setProperty('--theme-border-radius-sm', '2px') + element.style.setProperty('--theme-border-radius-md', '4px') + element.style.setProperty('--theme-border-radius-lg', '8px') + + element.style.setProperty('--theme-border-width-sm', '1px') + element.style.setProperty('--theme-border-width-md', '2px') + + element.style.setProperty('--theme-font-size-sm', '12px') + element.style.setProperty('--theme-font-size-md', '14px') + element.style.setProperty('--theme-font-size-base', '0.9rem') +} diff --git a/src/components/pdf-viewer/thumbnail/rm-pdf-thumbnail.js b/src/components/pdf-viewer/thumbnail/rm-pdf-thumbnail.js new file mode 100644 index 0000000..406ca1e --- /dev/null +++ b/src/components/pdf-viewer/thumbnail/rm-pdf-thumbnail.js @@ -0,0 +1,93 @@ +import { html } from 'lit' +import { PDFViewerComponent } from '../pdf-viewer-component.js' +import styles from './rm-pdf-thumbnail.styles.js' + +export default class PDFThumbnail extends PDFViewerComponent { + static get properties() { + return { + pageNumber: { type: Number } + } + } + + static get styles() { + return styles + } + + constructor() { + super() + this.pageNumber = 1 + this.scale = 0.3 + this._renderTask = null + } + + async firstUpdated() { + await this.renderThumbnail() + } + + async renderThumbnail() { + if (!this.context?.pdfDoc) return + + this.#cancelRenderTask() + + const pdfDoc = this.context.pdfDoc + + try { + const page = await pdfDoc.getPage(this.pageNumber) + const canvas = this.shadowRoot.querySelector('#thumbnail-canvas') + if (!canvas) return + + const context = canvas.getContext('2d') + const viewport = page.getViewport({ scale: this.scale }) + + canvas.height = viewport.height + canvas.width = viewport.width + + const renderContext = { + canvasContext: context, + viewport: viewport + } + + this._renderTask = page.render(renderContext) + await this._renderTask.promise + this._renderTask = null + } catch (error) { + if (error.name !== 'RenderingCancelledException') { + console.error('Error rendering thumbnail:', error) + } + this._renderTask = null + } + } + + disconnectedCallback() { + super.disconnectedCallback() + this.#cancelRenderTask() + } + + handleClick() { + this.context?.setShouldScroll(true) + this.context?.setCurrentPage(this.pageNumber) + } + + render() { + const isActive = this.context?.currentPage === this.pageNumber + + return html` +
+ +
${this.pageNumber}
+
+ ` + } + + #cancelRenderTask() { + if (this._renderTask) { + this._renderTask.cancel() + this._renderTask = null + } + } +} + +customElements.define('rm-pdf-thumbnail', PDFThumbnail) diff --git a/src/components/pdf-viewer/thumbnail/rm-pdf-thumbnail.styles.js b/src/components/pdf-viewer/thumbnail/rm-pdf-thumbnail.styles.js new file mode 100644 index 0000000..2995c45 --- /dev/null +++ b/src/components/pdf-viewer/thumbnail/rm-pdf-thumbnail.styles.js @@ -0,0 +1,46 @@ +import { css } from 'lit' + +export default css` + :host { + display: block; + cursor: pointer; + } + + ::selection { + background: var(--theme-primary, #4285f4); + color: white; + } + + .thumbnail-container { + padding: var(--theme-spacing-sm, 0.5rem); + border: var(--theme-border-width-md, 2px) solid var(--theme-neutral-200, #efefef); + border-radius: var(--theme-border-radius-md, 4px); + transition: all 0.2s ease; + background: white; + } + + .thumbnail-container:hover { + border-color: var(--theme-primary, #4285f4); + box-shadow: 0 var(--theme-spacing-xs, 0.25rem) var(--theme-spacing-sm, 0.5rem) var(--theme-shadow, rgba(0, 0, 0, 0.1)); + } + + .thumbnail-container.active { + border-color: var(--theme-primary-dark, #1967d2); + background: var(--theme-primary-lighter, #e8f0fe); + } + + canvas { + display: block; + width: 100%; + height: auto; + border-radius: var(--theme-border-radius-sm, 2px); + } + + .page-label { + text-align: center; + margin-top: var(--theme-spacing-xs, 0.25rem); + font-size: var(--theme-font-size-sm, 12px); + color: var(--theme-neutral-400, #5f6368); + font-weight: 500; + } +` diff --git a/src/components/pdf-viewer/toolbar/rm-pdf-toolbar.js b/src/components/pdf-viewer/toolbar/rm-pdf-toolbar.js new file mode 100644 index 0000000..61a0fcd --- /dev/null +++ b/src/components/pdf-viewer/toolbar/rm-pdf-toolbar.js @@ -0,0 +1,125 @@ +import { html } from 'lit' +import { PDFViewerComponent } from '../pdf-viewer-component.js' +import styles from './rm-pdf-toolbar.styles.js' +import closeIcon from '../../../assets/icons/close.svg' +import closeSidebarIcon from '../../../assets/icons/close-sidebar.svg' +import openSidebarIcon from '../../../assets/icons/open-sidebar.svg' +import arrowLeftIcon from '../../../assets/icons/arrow-left.svg' +import arrowRightIcon from '../../../assets/icons/arrow-right.svg' +import printIcon from '../../../assets/icons/print.svg' +import downloadIcon from '../../../assets/icons/download.svg' +import zoomOutIcon from '../../../assets/icons/zoom-out.svg' +import zoomInIcon from '../../../assets/icons/zoom-in.svg' + +export default class PDFToolbar extends PDFViewerComponent { + static get styles() { + return styles + } + + previousPage() { + this.context?.setShouldScroll(true) + this.context?.previousPage() + } + + nextPage() { + this.context?.setShouldScroll(true) + this.context?.nextPage() + } + + zoomIn() { + this.context?.zoomIn() + } + + zoomOut() { + this.context?.zoomOut() + } + + print() { + this.context?.print() + } + + download() { + this.context?.download() + } + + toggleSidebar() { + this.context?.toggleSidebar() + } + + handlePageInput(e) { + const value = parseInt(e.target.value, 10) + if (!isNaN(value) && value >= 1 && value <= this.context.totalPages) { + this.context?.setShouldScroll(true) + this.context?.setCurrentPage(value) + } + } + + handlePageKeydown(e) { + if (e.key === 'Enter') { + e.target.blur() + } + } + + render() { + if (!this.context) return html`` + + const { currentPage, totalPages, scale, sidebarCollapsed } = this.context + + return html` +
+
+ + +
+ + + of ${totalPages} + + +
+ +
+ + ${Math.round(scale * 100)}% + +
+ +
+ + +
+
+ + +
+ ` + } +} + +customElements.define('rm-pdf-toolbar', PDFToolbar) + diff --git a/src/components/pdf-viewer/toolbar/rm-pdf-toolbar.styles.js b/src/components/pdf-viewer/toolbar/rm-pdf-toolbar.styles.js new file mode 100644 index 0000000..7271a4a --- /dev/null +++ b/src/components/pdf-viewer/toolbar/rm-pdf-toolbar.styles.js @@ -0,0 +1,120 @@ +import { css } from 'lit' + +export default css` + + :host { + display: block; + } + + .toolbar { + padding: var(--theme-spacing-sm, 0.5rem); + background: var(--theme-neutral-50, #ffffff); + border-bottom: var(--theme-border-width-sm, 1px) solid var(--theme-border, #ddd); + display: flex; + align-items: center; + flex-wrap: wrap; + justify-content: space-between; + } + + button { + padding: var(--theme-spacing-sm, 0.5rem) var(--theme-spacing-md, 1rem); + border: var(--theme-border-width-sm, 1px) solid var(--theme-neutral-300, #ccc); + background: white; + border-radius: var(--theme-border-radius-md, 4px); + cursor: pointer; + font-size: var(--theme-font-size-base, 0.9rem); + } + + .btn--icon { + padding: var(--theme-spacing-xs, 0.25rem); + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: none; + } + + .sidebar-toggle img { + transition: opacity 0.3s ease, transform 0.3s ease; + } + + .sidebar-toggle .icon-open { + display: none; + } + + .sidebar-toggle .icon-close { + display: block; + } + + .sidebar-toggle.collapsed .icon-open { + display: block; + } + + .sidebar-toggle.collapsed .icon-close { + display: none; + } + + button:hover:not(:disabled) { + background: var(--theme-neutral-100, #e9e9e9); + } + + button:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .page-info { + font-weight: 500; + color: var(--theme-neutral-500, #333); + } + + .page-input { + width: var(--theme-spacing-md, 1rem); + text-align: center; + border: var(--theme-border-width-sm, 1px) solid var(--theme-neutral-300, #ccc); + border-radius: var(--theme-border-radius-md, 4px); + padding: var(--theme-spacing-xs, 0.25rem); + font-size: var(--theme-font-size-base, 0.9rem); + font-weight: 500; + background-color: var(--theme-neutral-50, #fafafa); + + &:hover { + background-color: var(--theme-neutral-100, #f3f3f3); + } + } + + .page-input:focus { + outline: none; + border-color: var(--theme-primary, #0066cc); + font-weight: bold; + } + + input[type="number"]::-webkit-inner-spin-button, + input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } + + input[type="number"] { + -moz-appearance: textfield; + } + + .toolbar__section { + display: flex; + align-items: center; + gap: var(--theme-spacing-xl, 3rem); + } + + .toolbar__section-group { + display: flex; + align-items: center; + gap: var(--theme-spacing-sm, 0.5rem); + } + + .zoom-level { + min-width: var(--theme-spacing-xl, 3rem); + text-align: center; + font-weight: 500; + color: var(--theme-neutral-500, #333); + } +` diff --git a/theme-demo.html b/theme-demo.html new file mode 100644 index 0000000..ea2fa63 --- /dev/null +++ b/theme-demo.html @@ -0,0 +1,131 @@ + + + + + + PDF Viewer - Theme Customization Demo + + + + + +
+

Theme Customization

+
+ + +
+
+ + +
+
+ + + + + + +
+
+ +
+ +
+ + + + diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..b4a81aa --- /dev/null +++ b/vite.config.js @@ -0,0 +1,13 @@ +export default { + optimizeDeps: { + exclude: ['pdfjs-dist'] + }, + server: { + fs: { + strict: false + } + }, + worker: { + format: 'es' + } +} diff --git a/yarn.lock b/yarn.lock index 67afd91..a26277a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -219,7 +219,16 @@ __metadata: languageName: node linkType: hard -"@lit/reactive-element@npm:^2.1.0": +"@lit/context@npm:^1.1.6": + version: 1.1.6 + resolution: "@lit/context@npm:1.1.6" + dependencies: + "@lit/reactive-element": "npm:^1.6.2 || ^2.1.0" + checksum: 10c0/203f761eda19c8b37d77f01d9a0148535c5b28c47b76e28b321cb6e5ed0546c45332512e68b1647ce92ca67690d8c66de31972ec115410b080d69fa25a5d86f3 + languageName: node + linkType: hard + +"@lit/reactive-element@npm:^1.6.2 || ^2.1.0, @lit/reactive-element@npm:^2.1.0": version: 2.1.2 resolution: "@lit/reactive-element@npm:2.1.2" dependencies: @@ -228,6 +237,125 @@ __metadata: languageName: node linkType: hard +"@napi-rs/canvas-android-arm64@npm:0.1.89": + version: 0.1.89 + resolution: "@napi-rs/canvas-android-arm64@npm:0.1.89" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/canvas-darwin-arm64@npm:0.1.89": + version: 0.1.89 + resolution: "@napi-rs/canvas-darwin-arm64@npm:0.1.89" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/canvas-darwin-x64@npm:0.1.89": + version: 0.1.89 + resolution: "@napi-rs/canvas-darwin-x64@npm:0.1.89" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.89": + version: 0.1.89 + resolution: "@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.89" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@napi-rs/canvas-linux-arm64-gnu@npm:0.1.89": + version: 0.1.89 + resolution: "@napi-rs/canvas-linux-arm64-gnu@npm:0.1.89" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/canvas-linux-arm64-musl@npm:0.1.89": + version: 0.1.89 + resolution: "@napi-rs/canvas-linux-arm64-musl@npm:0.1.89" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/canvas-linux-riscv64-gnu@npm:0.1.89": + version: 0.1.89 + resolution: "@napi-rs/canvas-linux-riscv64-gnu@npm:0.1.89" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/canvas-linux-x64-gnu@npm:0.1.89": + version: 0.1.89 + resolution: "@napi-rs/canvas-linux-x64-gnu@npm:0.1.89" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/canvas-linux-x64-musl@npm:0.1.89": + version: 0.1.89 + resolution: "@napi-rs/canvas-linux-x64-musl@npm:0.1.89" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/canvas-win32-arm64-msvc@npm:0.1.89": + version: 0.1.89 + resolution: "@napi-rs/canvas-win32-arm64-msvc@npm:0.1.89" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/canvas-win32-x64-msvc@npm:0.1.89": + version: 0.1.89 + resolution: "@napi-rs/canvas-win32-x64-msvc@npm:0.1.89" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/canvas@npm:^0.1.88": + version: 0.1.89 + resolution: "@napi-rs/canvas@npm:0.1.89" + dependencies: + "@napi-rs/canvas-android-arm64": "npm:0.1.89" + "@napi-rs/canvas-darwin-arm64": "npm:0.1.89" + "@napi-rs/canvas-darwin-x64": "npm:0.1.89" + "@napi-rs/canvas-linux-arm-gnueabihf": "npm:0.1.89" + "@napi-rs/canvas-linux-arm64-gnu": "npm:0.1.89" + "@napi-rs/canvas-linux-arm64-musl": "npm:0.1.89" + "@napi-rs/canvas-linux-riscv64-gnu": "npm:0.1.89" + "@napi-rs/canvas-linux-x64-gnu": "npm:0.1.89" + "@napi-rs/canvas-linux-x64-musl": "npm:0.1.89" + "@napi-rs/canvas-win32-arm64-msvc": "npm:0.1.89" + "@napi-rs/canvas-win32-x64-msvc": "npm:0.1.89" + dependenciesMeta: + "@napi-rs/canvas-android-arm64": + optional: true + "@napi-rs/canvas-darwin-arm64": + optional: true + "@napi-rs/canvas-darwin-x64": + optional: true + "@napi-rs/canvas-linux-arm-gnueabihf": + optional: true + "@napi-rs/canvas-linux-arm64-gnu": + optional: true + "@napi-rs/canvas-linux-arm64-musl": + optional: true + "@napi-rs/canvas-linux-riscv64-gnu": + optional: true + "@napi-rs/canvas-linux-x64-gnu": + optional: true + "@napi-rs/canvas-linux-x64-musl": + optional: true + "@napi-rs/canvas-win32-arm64-msvc": + optional: true + "@napi-rs/canvas-win32-x64-msvc": + optional: true + checksum: 10c0/e1b678b4be10b0963eee20286a1b2ea01bfb9adc36c557a712574bb077f8310a76a1ec4d6105111a9525263920589950021a6678e3b249721e57ac7550e8078b + languageName: node + linkType: hard + "@npmcli/agent@npm:^4.0.0": version: 4.0.0 resolution: "@npmcli/agent@npm:4.0.0" @@ -910,6 +1038,13 @@ __metadata: languageName: node linkType: hard +"node-readable-to-web-readable-stream@npm:^0.4.2": + version: 0.4.2 + resolution: "node-readable-to-web-readable-stream@npm:0.4.2" + checksum: 10c0/8c3d09cac51c5f886e1636fa2a5404d664245c8bdc9a65e102552894963ed1b27207d5b94de59e37045d81cb9e8970cf79e561006df7ee8821cb761e728b3a80 + languageName: node + linkType: hard + "nopt@npm:^9.0.0": version: 9.0.0 resolution: "nopt@npm:9.0.0" @@ -938,6 +1073,21 @@ __metadata: languageName: node linkType: hard +"pdfjs-dist@npm:^5.4.624": + version: 5.4.624 + resolution: "pdfjs-dist@npm:5.4.624" + dependencies: + "@napi-rs/canvas": "npm:^0.1.88" + node-readable-to-web-readable-stream: "npm:^0.4.2" + dependenciesMeta: + "@napi-rs/canvas": + optional: true + node-readable-to-web-readable-stream: + optional: true + checksum: 10c0/cebfc264ff41f7bedb60bfd2c9900be82bc19f143a1f8dd426d1e08c2093116e41c3900451633733c9ee65b18e39972e4f8de3622d443dde7d01c7eeb018dcfe + languageName: node + linkType: hard + "picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" @@ -1132,7 +1282,9 @@ __metadata: version: 0.0.0-use.local resolution: "spider@workspace:." dependencies: + "@lit/context": "npm:^1.1.6" lit: "npm:^3.3.2" + pdfjs-dist: "npm:^5.4.624" vite: "npm:^7.2.4" languageName: unknown linkType: soft