diff --git a/.prettierrc.mjs b/.prettierrc.mjs index f43a0b25..f47819e2 100644 --- a/.prettierrc.mjs +++ b/.prettierrc.mjs @@ -21,6 +21,18 @@ const config = { bracketSameLine: true, svelteAllowShorthand: true } + }, + { + files: '**/routes/examples/**/*.svelte', + options: { + printWidth: 60 + } + }, + { + files: 'src/routes/examples/**/*.svelte', + options: { + printWidth: 60 + } } ] }; diff --git a/.vscode/settings.json b/.vscode/settings.json index 21800944..df288805 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,7 @@ "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" @@ -19,7 +19,14 @@ "prettier.tabWidth": 4, "prettier.trailingComma": "none", "prettier.printWidth": 100, + "prettier.documentSelectors": [ + "*.svelte", + "*.md", + "**/examples/**/*.svelte", + "src/routes/examples/**/*.svelte" + ], "svelte.plugin.svelte.format.enable": false, "eslint.probe": ["javascript", "typescript", "svelte"], - "eslint.experimental.useFlatConfig": true + "eslint.useFlatConfig": true, + "eslint.workingDirectories": [{ "mode": "auto" }] } diff --git a/config/sidebar.ts b/config/sidebar.ts index 69018e6f..f6fa45bf 100644 --- a/config/sidebar.ts +++ b/config/sidebar.ts @@ -11,11 +11,11 @@ export default { { title: 'Why SveltePlot?', to: '/why-svelteplot' + }, + { + title: 'Examples', + to: '/examples' } - // { - // title: 'Introduction', - // to: '/introduction' - // } ] }, { diff --git a/package.json b/package.json index 1a977403..4b512110 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelteplot", - "version": "0.2.8", + "version": "0.2.9", "license": "ISC", "author": { "name": "Gregor Aisch", @@ -18,7 +18,8 @@ "test:unit": "vitest", "prepack": "npx svelte-package", "release-next": "npm version prerelease --preid next && npm publish && git push && git push --tags && sleep 1 && npm dist-tag add svelteplot@$(npm view . version) next", - "docs": "npm run build && cd build && rsync --recursive . vis4.net:svelteplot/alpha0/" + "docs": "npm run build && cd build && rsync --recursive . vis4.net:svelteplot/alpha0/", + "screenshots": "node screenshot-examples.js" }, "exports": { ".": { @@ -83,6 +84,7 @@ "jsdom": "^26.1.0", "prettier": "^3.5.3", "prettier-plugin-svelte": "^3.4.0", + "puppeteer": "^24.9.0", "remark-code-extra": "^1.0.1", "remark-code-frontmatter": "^1.0.0", "resize-observer-polyfill": "^1.5.1", @@ -92,6 +94,7 @@ "svelte-highlight": "^7.8.3", "svg-path-parser": "^1.1.0", "topojson-client": "^3.1.0", + "ts-essentials": "^10.0.4", "tslib": "^2.8.1", "typedoc": "^0.28.5", "typedoc-plugin-markdown": "^4.6.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16156d65..c9817632 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -165,6 +165,9 @@ importers: prettier-plugin-svelte: specifier: ^3.4.0 version: 3.4.0(prettier@3.5.3)(svelte@5.33.2) + puppeteer: + specifier: ^24.9.0 + version: 24.9.0(typescript@5.8.3) remark-code-extra: specifier: ^1.0.1 version: 1.0.1 @@ -192,6 +195,9 @@ importers: topojson-client: specifier: ^3.1.0 version: 3.1.0 + ts-essentials: + specifier: ^10.0.4 + version: 10.0.4(typescript@5.8.3) tslib: specifier: ^2.8.1 version: 2.8.1 @@ -1463,6 +1469,11 @@ packages: '@polka/url@1.0.0-next.24': resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==} + '@puppeteer/browsers@2.10.5': + resolution: {integrity: sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==} + engines: {node: '>=18'} + hasBin: true + '@rollup/plugin-babel@5.3.1': resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} engines: {node: '>= 10.0.0'} @@ -1748,6 +1759,9 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -1847,6 +1861,9 @@ packages: '@types/unist@3.0.2': resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@typescript-eslint/eslint-plugin@7.7.0': resolution: {integrity: sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==} engines: {node: ^18.18.0 || >=20.0.0} @@ -2169,6 +2186,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + async@3.2.5: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} @@ -2184,6 +2205,9 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + b4a@1.6.7: + resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} + babel-plugin-macros@3.1.0: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} @@ -2209,6 +2233,40 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bare-events@2.5.4: + resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} + + bare-fs@4.1.5: + resolution: {integrity: sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.6.1: + resolution: {integrity: sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.6.5: + resolution: {integrity: sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==} + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + + basic-ftp@5.0.5: + resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} + engines: {node: '>=10.0.0'} + binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -2228,6 +2286,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -2310,6 +2371,15 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chromium-bidi@5.1.0: + resolution: {integrity: sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==} + peerDependencies: + devtools-protocol: '*' + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clone-deep@0.2.4: resolution: {integrity: sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==} engines: {node: '>=0.10.0'} @@ -2375,6 +2445,15 @@ packages: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -2475,6 +2554,10 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -2488,6 +2571,15 @@ packages: supports-color: optional: true + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decimal.js@10.5.0: resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} @@ -2531,6 +2623,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2549,6 +2645,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + devtools-protocol@0.0.1439962: + resolution: {integrity: sha512-jJF48UdryzKiWhJ1bLKr7BFWUQCEIT5uCNbDLqkQJBtkFxYzILJH44WN0PDKMIlGDN7Utb8vyUY85C3w4R/t2g==} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -2574,12 +2673,18 @@ packages: emoji-regex-xs@1.0.0: resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emojilib@2.4.0: resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} emoticon@4.0.1: resolution: {integrity: sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==} + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + enhanced-resolve@5.17.1: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} @@ -2588,6 +2693,10 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -2650,6 +2759,11 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + eslint-compat-utils@0.5.1: resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} engines: {node: '>=12'} @@ -2761,6 +2875,11 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2768,6 +2887,9 @@ packages: resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==} engines: {node: '>=6.0.0'} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -2784,6 +2906,9 @@ packages: fault@2.0.1: resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.4.4: resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} peerDependencies: @@ -2869,6 +2994,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2880,6 +3009,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -2891,6 +3024,10 @@ packages: get-tsconfig@4.7.6: resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==} + get-uri@6.0.4: + resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==} + engines: {node: '>= 14'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -3048,6 +3185,10 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -3101,6 +3242,10 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -3218,6 +3363,9 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + jsdom@26.1.0: resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} engines: {node: '>=18'} @@ -3355,6 +3503,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} @@ -3547,6 +3699,9 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mixin-object@2.0.1: resolution: {integrity: sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==} engines: {node: '>=0.10.0'} @@ -3573,6 +3728,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -3641,6 +3800,14 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -3687,6 +3854,9 @@ packages: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -3766,9 +3936,23 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + property-information@6.4.0: resolution: {integrity: sha512-9t5qARVofg2xQqKtytzt+lZ4d1Qvj8t5B8fEwXK6qOfgRLgH/b13QlgEyDh033NOS31nXeFbYv7CLUDG1CeifQ==} + proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -3777,6 +3961,15 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + puppeteer-core@24.9.0: + resolution: {integrity: sha512-HFdCeH/wx6QPz8EncafbCqJBqaCG1ENW75xg3cLFMRUoqZDgByT6HSueiumetT2uClZxwqj0qS4qMVZwLHRHHw==} + engines: {node: '>=18'} + + puppeteer@24.9.0: + resolution: {integrity: sha512-L0pOtALIx8rgDt24Y+COm8X52v78gNtBOW6EmUcEPci0TYD72SAuaXKqasRIx4JXxmg2Tkw5ySKcpPOwN8xXnQ==} + engines: {node: '>=18'} + hasBin: true + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -3862,6 +4055,10 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -3952,6 +4149,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + serialize-javascript@4.0.0: resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==} @@ -4019,6 +4221,18 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.4: + resolution: {integrity: sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -4048,12 +4262,22 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + streamx@2.22.0: + resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + string.prototype.matchall@4.0.10: resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} @@ -4074,6 +4298,10 @@ packages: resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} engines: {node: '>=4'} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + strip-comments@2.0.1: resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==} engines: {node: '>=10'} @@ -4141,6 +4369,12 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tar-fs@3.0.9: + resolution: {integrity: sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} @@ -4154,6 +4388,9 @@ packages: engines: {node: '>=10'} hasBin: true + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -4228,6 +4465,14 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-essentials@10.0.4: + resolution: {integrity: sha512-lwYdz28+S4nicm+jFi6V58LaAIpxzhg9rLdgNC1VsdP/xiFBseGhF1M/shwCk6zMmwahBZdXcl34LVHrEang3A==} + peerDependencies: + typescript: '>=4.5.0' + peerDependenciesMeta: + typescript: + optional: true + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -4267,6 +4512,9 @@ packages: typed-array-length@1.0.4: resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + typed-query-selector@2.12.0: + resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} + typedoc-plugin-markdown@4.6.3: resolution: {integrity: sha512-86oODyM2zajXwLs4Wok2mwVEfCwCnp756QyhLGX2IfsdRYr1DXLCgJgnLndaMUjJD7FBhnLk2okbNE9PdLxYRw==} engines: {node: '>= 18'} @@ -4602,6 +4850,10 @@ packages: workbox-window@7.0.0: resolution: {integrity: sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA==} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -4617,6 +4869,18 @@ packages: utf-8-validate: optional: true + ws@8.18.2: + resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -4624,6 +4888,10 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -4636,6 +4904,17 @@ packages: engines: {node: '>= 14'} hasBin: true + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -4643,6 +4922,9 @@ packages: zimmerframe@1.1.2: resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + zod@3.25.34: + resolution: {integrity: sha512-lZHvSc2PpWdcfpHlyB33HA9nqP16GpC9IpiG4lYq9jZCJVLZNnWd6Y1cj79bcLSBKTkxepfpjckPv5Y5VOPlwA==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -4844,7 +5126,7 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-compilation-targets': 7.25.2 '@babel/helper-plugin-utils': 7.27.1 - debug: 4.4.0 + debug: 4.4.1 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -5972,6 +6254,19 @@ snapshots: '@polka/url@1.0.0-next.24': {} + '@puppeteer/browsers@2.10.5': + dependencies: + debug: 4.4.1 + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.7.2 + tar-fs: 3.0.9 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-buffer + - supports-color + '@rollup/plugin-babel@5.3.1(@babel/core@7.25.2)(@types/babel__core@7.20.5)(rollup@2.79.1)': dependencies: '@babel/core': 7.25.2 @@ -6328,6 +6623,8 @@ snapshots: dependencies: '@testing-library/dom': 10.4.0 + '@tootallnate/quickjs-emscripten@0.23.0': {} + '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -6432,6 +6729,11 @@ snapshots: '@types/unist@3.0.2': {} + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 20.10.8 + optional: true + '@typescript-eslint/eslint-plugin@7.7.0(@typescript-eslint/parser@7.7.0(eslint@9.27.0(jiti@1.21.0))(typescript@5.8.3))(eslint@9.27.0(jiti@1.21.0))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -6440,12 +6742,12 @@ snapshots: '@typescript-eslint/type-utils': 7.7.0(eslint@9.27.0(jiti@1.21.0))(typescript@5.8.3) '@typescript-eslint/utils': 7.7.0(eslint@9.27.0(jiti@1.21.0))(typescript@5.8.3) '@typescript-eslint/visitor-keys': 7.7.0 - debug: 4.4.0 + debug: 4.4.1 eslint: 9.27.0(jiti@1.21.0) graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 - semver: 7.6.3 + semver: 7.7.2 ts-api-utils: 1.3.0(typescript@5.8.3) optionalDependencies: typescript: 5.8.3 @@ -6475,7 +6777,7 @@ snapshots: '@typescript-eslint/types': 7.7.0 '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.8.3) '@typescript-eslint/visitor-keys': 7.7.0 - debug: 4.4.0 + debug: 4.4.1 eslint: 9.27.0(jiti@1.21.0) optionalDependencies: typescript: 5.8.3 @@ -6508,7 +6810,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.8.3) '@typescript-eslint/utils': 7.7.0(eslint@9.27.0(jiti@1.21.0))(typescript@5.8.3) - debug: 4.4.0 + debug: 4.4.1 eslint: 9.27.0(jiti@1.21.0) ts-api-utils: 1.3.0(typescript@5.8.3) optionalDependencies: @@ -6535,11 +6837,11 @@ snapshots: dependencies: '@typescript-eslint/types': 7.7.0 '@typescript-eslint/visitor-keys': 7.7.0 - debug: 4.4.0 + debug: 4.4.1 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.3 + semver: 7.7.2 ts-api-utils: 1.3.0(typescript@5.8.3) optionalDependencies: typescript: 5.8.3 @@ -6569,7 +6871,7 @@ snapshots: '@typescript-eslint/types': 7.7.0 '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.8.3) eslint: 9.27.0(jiti@1.21.0) - semver: 7.6.3 + semver: 7.7.2 transitivePeerDependencies: - supports-color - typescript @@ -6908,6 +7210,10 @@ snapshots: assertion-error@2.0.1: {} + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + async@3.2.5: {} at-least-node@1.0.0: {} @@ -6916,6 +7222,8 @@ snapshots: axobject-query@4.1.0: {} + b4a@1.6.7: {} + babel-plugin-macros@3.1.0: dependencies: '@babel/runtime': 7.23.8 @@ -6950,6 +7258,33 @@ snapshots: balanced-match@1.0.2: {} + bare-events@2.5.4: + optional: true + + bare-fs@4.1.5: + dependencies: + bare-events: 2.5.4 + bare-path: 3.0.0 + bare-stream: 2.6.5(bare-events@2.5.4) + optional: true + + bare-os@3.6.1: + optional: true + + bare-path@3.0.0: + dependencies: + bare-os: 3.6.1 + optional: true + + bare-stream@2.6.5(bare-events@2.5.4): + dependencies: + streamx: 2.22.0 + optionalDependencies: + bare-events: 2.5.4 + optional: true + + basic-ftp@5.0.5: {} + binary-extensions@2.2.0: {} brace-expansion@1.1.11: @@ -6972,6 +7307,8 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) + buffer-crc32@0.2.13: {} + buffer-from@1.1.2: {} builtin-modules@3.3.0: {} @@ -7056,6 +7393,18 @@ snapshots: dependencies: readdirp: 4.0.1 + chromium-bidi@5.1.0(devtools-protocol@0.0.1439962): + dependencies: + devtools-protocol: 0.0.1439962 + mitt: 3.0.1 + zod: 3.25.34 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clone-deep@0.2.4: dependencies: for-own: 0.1.5 @@ -7112,6 +7461,15 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 + cosmiconfig@9.0.0(typescript@5.8.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.8.3 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -7203,6 +7561,8 @@ snapshots: d3-timer@3.0.1: {} + data-uri-to-buffer@6.0.2: {} + data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -7212,6 +7572,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.1: + dependencies: + ms: 2.1.3 + decimal.js@10.5.0: {} decode-named-character-reference@1.0.2: @@ -7249,6 +7613,12 @@ snapshots: defu@6.1.4: {} + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + dequal@2.0.3: {} destr@2.0.3: {} @@ -7262,6 +7632,8 @@ snapshots: dependencies: dequal: 2.0.3 + devtools-protocol@0.0.1439962: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -7284,10 +7656,16 @@ snapshots: emoji-regex-xs@1.0.0: {} + emoji-regex@8.0.0: {} + emojilib@2.4.0: {} emoticon@4.0.1: {} + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + enhanced-resolve@5.17.1: dependencies: graceful-fs: 4.2.11 @@ -7295,6 +7673,8 @@ snapshots: entities@4.5.0: {} + env-paths@2.2.1: {} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -7429,10 +7809,18 @@ snapshots: escape-string-regexp@5.0.0: {} + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + eslint-compat-utils@0.5.1(eslint@9.27.0(jiti@1.21.0)): dependencies: eslint: 9.27.0(jiti@1.21.0) - semver: 7.6.3 + semver: 7.7.2 eslint-config-prettier@10.1.5(eslint@9.27.0(jiti@1.21.0)): dependencies: @@ -7455,7 +7843,7 @@ snapshots: globals: 15.9.0 ignore: 5.3.1 minimatch: 9.0.5 - semver: 7.6.3 + semver: 7.7.2 eslint-plugin-svelte@3.9.0(eslint@9.27.0(jiti@1.21.0))(svelte@5.33.2): dependencies: @@ -7582,10 +7970,22 @@ snapshots: extend@3.0.2: {} + extract-zip@2.0.1: + dependencies: + debug: 4.4.1 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + fast-deep-equal@3.1.3: {} fast-equals@5.2.2: {} + fast-fifo@1.3.2: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -7606,6 +8006,10 @@ snapshots: dependencies: format: 0.2.2 + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + fdir@6.4.4(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -7685,6 +8089,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -7705,6 +8111,10 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@5.2.0: + dependencies: + pump: 3.0.2 + get-stream@6.0.1: {} get-symbol-description@1.0.0: @@ -7716,6 +8126,14 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-uri@6.0.4: + dependencies: + basic-ftp: 5.0.5 + data-uri-to-buffer: 6.0.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -7879,6 +8297,11 @@ snapshots: internmap@2.0.3: {} + ip-address@9.0.5: + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -7927,6 +8350,8 @@ snapshots: is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -8028,6 +8453,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsbn@1.1.0: {} + jsdom@26.1.0: dependencies: cssstyle: 4.2.1 @@ -8153,6 +8580,8 @@ snapshots: dependencies: yallist: 3.1.1 + lru-cache@7.18.3: {} + lunr@2.3.9: {} lz-string@1.5.0: {} @@ -8556,6 +8985,8 @@ snapshots: dependencies: brace-expansion: 2.0.1 + mitt@3.0.1: {} + mixin-object@2.0.1: dependencies: for-in: 0.1.8 @@ -8578,6 +9009,8 @@ snapshots: natural-compare@1.4.0: {} + netmask@2.0.2: {} + no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -8660,6 +9093,24 @@ snapshots: dependencies: p-limit: 3.1.0 + pac-proxy-agent@7.2.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.3 + debug: 4.4.1 + get-uri: 6.0.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.0.2 + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -8707,6 +9158,8 @@ snapshots: pathval@2.0.0: {} + pend@1.2.0: {} + perfect-debounce@1.0.0: {} picocolors@1.1.1: {} @@ -8768,12 +9221,63 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 + progress@2.0.3: {} + property-information@6.4.0: {} + proxy-agent@6.5.0: + dependencies: + agent-base: 7.1.3 + debug: 4.4.1 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + proxy-from-env@1.1.0: {} + + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + punycode.js@2.3.1: {} punycode@2.3.1: {} + puppeteer-core@24.9.0: + dependencies: + '@puppeteer/browsers': 2.10.5 + chromium-bidi: 5.1.0(devtools-protocol@0.0.1439962) + debug: 4.4.1 + devtools-protocol: 0.0.1439962 + typed-query-selector: 2.12.0 + ws: 8.18.2 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate + + puppeteer@24.9.0(typescript@5.8.3): + dependencies: + '@puppeteer/browsers': 2.10.5 + chromium-bidi: 5.1.0(devtools-protocol@0.0.1439962) + cosmiconfig: 9.0.0(typescript@5.8.3) + devtools-protocol: 0.0.1439962 + puppeteer-core: 24.9.0 + typed-query-selector: 2.12.0 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - typescript + - utf-8-validate + queue-microtask@1.2.3: {} randombytes@2.1.0: @@ -8906,6 +9410,8 @@ snapshots: mdast-util-to-markdown: 2.1.0 unified: 11.0.4 + require-directory@2.1.1: {} + require-from-string@2.0.2: {} resize-observer-polyfill@1.5.1: {} @@ -9009,6 +9515,8 @@ snapshots: semver@7.6.3: {} + semver@7.7.2: {} + serialize-javascript@4.0.0: dependencies: randombytes: 2.1.0 @@ -9100,6 +9608,21 @@ snapshots: slash@3.0.0: {} + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.3 + debug: 4.4.1 + socks: 2.8.4 + transitivePeerDependencies: + - supports-color + + socks@2.8.4: + dependencies: + ip-address: 9.0.5 + smart-buffer: 4.2.0 + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -9121,10 +9644,25 @@ snapshots: sprintf-js@1.0.3: {} + sprintf-js@1.1.3: {} + stackback@0.0.2: {} std-env@3.9.0: {} + streamx@2.22.0: + dependencies: + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + optionalDependencies: + bare-events: 2.5.4 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + string.prototype.matchall@4.0.10: dependencies: call-bind: 1.0.5 @@ -9166,6 +9704,10 @@ snapshots: is-obj: 1.0.1 is-regexp: 1.0.0 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strip-comments@2.0.1: {} strip-final-newline@2.0.0: {} @@ -9241,6 +9783,22 @@ snapshots: tapable@2.2.1: {} + tar-fs@3.0.9: + dependencies: + pump: 3.0.2 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.1.5 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-buffer + + tar-stream@3.1.7: + dependencies: + b4a: 1.6.7 + fast-fifo: 1.3.2 + streamx: 2.22.0 + temp-dir@2.0.0: {} tempy@0.6.0: @@ -9257,6 +9815,10 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + text-decoder@1.2.3: + dependencies: + b4a: 1.6.7 + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -9314,6 +9876,10 @@ snapshots: dependencies: typescript: 5.8.3 + ts-essentials@10.0.4(typescript@5.8.3): + optionalDependencies: + typescript: 5.8.3 + tslib@2.8.1: {} tsx@4.16.5: @@ -9366,6 +9932,8 @@ snapshots: for-each: 0.3.3 is-typed-array: 1.1.12 + typed-query-selector@2.12.0: {} + typedoc-plugin-markdown@4.6.3(typedoc@0.28.5(typescript@5.8.3)): dependencies: typedoc: 0.28.5(typescript@5.8.3) @@ -9572,7 +10140,7 @@ snapshots: vite-plugin-pwa@0.19.0(vite@6.3.5(@types/node@20.10.8)(jiti@1.21.0)(sass@1.89.0)(terser@5.26.0)(tsx@4.16.5)(yaml@2.7.1))(workbox-build@7.0.0(@types/babel__core@7.20.5))(workbox-window@7.0.0): dependencies: - debug: 4.4.0 + debug: 4.4.1 fast-glob: 3.3.2 pretty-bytes: 6.1.1 vite: 6.3.5(@types/node@20.10.8)(jiti@1.21.0)(sass@1.89.0)(terser@5.26.0)(tsx@4.16.5)(yaml@2.7.1) @@ -9830,22 +10398,51 @@ snapshots: '@types/trusted-types': 2.0.7 workbox-core: 7.0.0 + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrappy@1.0.2: {} ws@8.18.0: {} + ws@8.18.2: {} + xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} + y18n@5.0.8: {} + yallist@3.1.1: {} yaml@1.10.2: {} yaml@2.7.1: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + yocto-queue@0.1.0: {} zimmerframe@1.1.2: {} + zod@3.25.34: {} + zwitch@2.0.4: {} diff --git a/screenshot-examples.js b/screenshot-examples.js new file mode 100644 index 00000000..fe6dc989 --- /dev/null +++ b/screenshot-examples.js @@ -0,0 +1,206 @@ +import fs from 'fs/promises'; +import path from 'path'; +import puppeteer from 'puppeteer'; +import { exec } from 'child_process'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +// Configuration +const EXAMPLES_DIR = path.join(__dirname, 'src', 'routes', 'examples'); +const OUTPUT_DIR = path.join(__dirname, 'static', 'examples'); +const SCREENSHOT_WIDTH = 600; +const DEVICE_PIXEL_RATIO = 2; + +// Start the development server and return server instance and local URL +const startServer = () => { + console.log('Starting development server...'); + const server = exec('pnpm dev'); + + // Wait for the server to start and extract the local URL + return new Promise((resolve) => { + let serverUrl = null; + + server.stdout.on('data', (data) => { + console.log(`Server: ${data}`); + + // Extract the local URL using regex + const localUrlMatch = data.toString().match(/Local:\s+(http:\/\/localhost:\d+\/)/i); + if (localUrlMatch && localUrlMatch[1]) { + serverUrl = localUrlMatch[1].trim(); + console.log(`Detected server URL: ${serverUrl}`); + } + + // Server is ready when we see this message and have a URL + if ((data.includes('Local:') || data.includes('Server running at')) && serverUrl) { + console.log('Server started successfully'); + resolve({ server, url: serverUrl }); + } + }); + + server.stderr.on('data', (data) => { + console.error(`Server error: ${data}`); + }); + }); +}; + +// Recursively get all Svelte files (excluding layout, _index, [...key] files) +const getSvelteFiles = async (dir) => { + const entries = await fs.readdir(dir, { withFileTypes: true }); + + const files = await Promise.all( + entries.map(async (entry) => { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory() && !entry.name.startsWith('[')) { + return getSvelteFiles(fullPath); + } else if ( + entry.isFile() && + entry.name.endsWith('.svelte') && + !entry.name.startsWith('+') && + !entry.name.startsWith('_') + ) { + return [fullPath]; + } + + return []; + }) + ); + + return files.flat(); +}; + +// Convert file path to URL path +const filePathToUrlPath = (filePath) => { + const relativePath = path.relative(EXAMPLES_DIR, filePath); + return relativePath.replace('.svelte', ''); +}; + +// Create output directory if it doesn't exist +const ensureDirectoryExists = async (dirPath) => { + try { + await fs.mkdir(dirPath, { recursive: true }); + } catch (error) { + // Ignore if directory already exists + if (error.code !== 'EEXIST') { + throw error; + } + } +}; + +// Take screenshot of a page in specific theme +const takeScreenshot = async (page, urlPath, outputPath, isDarkMode = false) => { + const themeSuffix = isDarkMode ? '.dark' : ''; + const finalOutputPath = outputPath.replace('.png', `${themeSuffix}.png`); + + // Wait for the Plot component to be rendered + await page.waitForSelector('.content > figure.svelteplot', { timeout: 10000 }); + + // Toggle dark mode if needed + if (isDarkMode) { + // Toggle dark mode by setting the HTML class + // SveltePress uses 'html.dark' for dark mode + await page.evaluate(() => { + document.documentElement.classList.add('dark'); + // Force any theme-aware components to re-render + window.dispatchEvent(new Event('theme-change')); + }); + + // Wait a bit for theme to apply + await new Promise((resolve) => setTimeout(resolve, 300)); + } + + // Get the Plot SVG element + const elementHandle = await page.evaluateHandle(() => + document.querySelector('.content > figure.svelteplot > .plot-body > svg') + ); + + // Take a screenshot of the element + const boundingBox = await elementHandle.boundingBox(); + + if (!boundingBox) { + console.error( + `Could not get bounding box for example: ${urlPath} (${isDarkMode ? 'dark' : 'light'} mode)` + ); + return false; + } + + // Take the screenshot + await page.screenshot({ + path: finalOutputPath, + clip: { + x: boundingBox.x, + y: boundingBox.y, + width: boundingBox.width, + height: boundingBox.height + } + }); + + console.log(`Saved screenshot to: ${finalOutputPath}`); + return true; +}; + +// Take screenshots of all example pages +const screenshotExamples = async () => { + // Start the server and get the URL + const { server, url: serverUrl } = await startServer(); + + try { + // Launch the browser + console.log('Launching browser...'); + const browser = await puppeteer.launch({ + defaultViewport: { + width: SCREENSHOT_WIDTH, + height: 800, + deviceScaleFactor: DEVICE_PIXEL_RATIO + }, + // Launch Chrome in headless mode + headless: 'new' + }); + + // Get all example Svelte files + const svelteFiles = await getSvelteFiles(EXAMPLES_DIR); + console.log(`Found ${svelteFiles.length} example files to screenshot`); + + // Process each file + for (const filePath of svelteFiles) { + const urlPath = filePathToUrlPath(filePath); + const outputPath = path.join(OUTPUT_DIR, urlPath + '.png'); + const outputDir = path.dirname(outputPath); + + console.log(`Processing: ${urlPath}`); + + // Create output directory structure + await ensureDirectoryExists(outputDir); + + // Open the page + const page = await browser.newPage(); + await page.goto(`${serverUrl}examples/${urlPath}`, { + waitUntil: 'networkidle0', + timeout: 60000 + }); + + // Take light mode screenshot + await takeScreenshot(page, urlPath, outputPath, false); + + // Take dark mode screenshot + await takeScreenshot(page, urlPath, outputPath, true); + + await page.close(); + } + + // Close the browser + await browser.close(); + + console.log('All screenshots completed successfully!'); + } catch (error) { + console.error('Error:', error); + } finally { + // Kill the server + server.kill(); + process.exit(0); + } +}; + +// Run the script +screenshotExamples().catch(console.error); diff --git a/src/lib/Plot.svelte b/src/lib/Plot.svelte index 665c5412..53750c28 100644 --- a/src/lib/Plot.svelte +++ b/src/lib/Plot.svelte @@ -91,89 +91,101 @@ - - {#snippet children({ - hasProjection, - hasExplicitAxisX, - hasExplicitAxisY, - hasExplicitGridX, - hasExplicitGridY, - options, - scales, - ...restProps - })} - console.warn(err)}> - - {#if !hasProjection && !hasExplicitAxisX} - {#if options.axes && (options.x.axis === 'top' || options.x.axis === 'both')} - + + + {#snippet children({ + hasProjection, + hasExplicitAxisX, + hasExplicitAxisY, + hasExplicitGridX, + hasExplicitGridY, + options, + scales, + ...restProps + })} + console.warn(err)}> + + {#if !hasProjection && !hasExplicitAxisX} + {#if options.axes && (options.x.axis === 'top' || options.x.axis === 'both')} + + {/if} + {#if options.axes && (options.x.axis === 'bottom' || options.x.axis === 'both')} + + {/if} {/if} - {#if options.axes && (options.x.axis === 'bottom' || options.x.axis === 'both')} - + {#if !hasProjection && !hasExplicitAxisY} + {#if options.axes && (options.y.axis === 'left' || options.y.axis === 'both')} + + {/if} + {#if options.axes && (options.y.axis === 'right' || options.y.axis === 'both')} + + {/if} {/if} - {/if} - {#if !hasProjection && !hasExplicitAxisY} - {#if options.axes && (options.y.axis === 'left' || options.y.axis === 'both')} - + + {#if !hasExplicitGridX && (options.grid || options.x.grid)} + {/if} - {#if options.axes && (options.y.axis === 'right' || options.y.axis === 'both')} - + {#if !hasExplicitGridY && (options.grid || options.y.grid)} + {/if} - {/if} - - {#if !hasExplicitGridX && (options.grid || options.x.grid)} - - {/if} - {#if !hasExplicitGridY && (options.grid || options.y.grid)} - - {/if} - - {#if options.frame} - - {/if} - {@render parentChildren?.({ - options, - scales, - ...restProps - })} - {#snippet failed(error, reset)} - - {#each error.message.split('\n') as line, i (i)} - {line} - {/each} - {/snippet} - - {/snippet} - {#snippet facetAxes()} - + + {#if options.frame} + + {/if} + {@render parentChildren?.({ + options, + scales, + ...restProps + })} + {#snippet failed(error, reset)} + + {#each error.message.split('\n') as line, i (i)} + {line} + {/each} + {/snippet} + + {/snippet} + {#snippet facetAxes()} + + {/snippet} + + {#snippet failed(error)} +
Error: {error.message}
{/snippet} - + diff --git a/src/lib/core/Plot.svelte b/src/lib/core/Plot.svelte index b830decc..2c6daae1 100644 --- a/src/lib/core/Plot.svelte +++ b/src/lib/core/Plot.svelte @@ -73,7 +73,7 @@ locale: 'en-US', numberFormat: { style: 'decimal', - notation: 'compact', + // notation: 'compact', compactDisplay: 'short' }, markerDotRadius: 3, diff --git a/src/lib/helpers/autoTicks.ts b/src/lib/helpers/autoTicks.ts index eff9de52..6f747204 100644 --- a/src/lib/helpers/autoTicks.ts +++ b/src/lib/helpers/autoTicks.ts @@ -1,6 +1,6 @@ import type { RawValue, ScaleType } from '$lib/types.js'; import { maybeTimeInterval } from './time.js'; -import { range as rangei } from 'd3-array'; +import { extent, range as rangei } from 'd3-array'; export function maybeInterval(interval: null | number | string | ((d: T) => T)) { if (interval == null) return; @@ -38,11 +38,11 @@ export function autoTicks( scaleFn, count: number ) { - return ticks - ? ticks - : interval - ? maybeInterval(interval, type).range(domain[0], domain[1]) - : typeof scaleFn.ticks === 'function' - ? scaleFn.ticks(count) - : []; + if (ticks) return ticks; + if (interval) { + const [lo, hi] = extent(domain); + const I = maybeInterval(interval, type); + return I.range(lo, I.offset(hi)); + } + return typeof scaleFn.ticks === 'function' ? scaleFn.ticks(count) : []; } diff --git a/src/lib/helpers/scales.ts b/src/lib/helpers/scales.ts index 2d5be986..87d3a411 100644 --- a/src/lib/helpers/scales.ts +++ b/src/lib/helpers/scales.ts @@ -29,6 +29,7 @@ import type { import isDataRecord from './isDataRecord.js'; import { createProjection } from './projection.js'; +import { maybeInterval } from './autoTicks.js'; /** * compute the plot scales @@ -302,7 +303,7 @@ export function createScale( const valueArray = type === 'quantile' || type === 'quantile-cont' ? allDataValues.toSorted() : valueArr; - const domain = scaleOptions.domain + let domain = scaleOptions.domain ? isOrdinal ? scaleOptions.domain : extent(scaleOptions.zero ? [0, ...scaleOptions.domain] : scaleOptions.domain) @@ -317,6 +318,18 @@ export function createScale( : valueArray : extent(scaleOptions.zero ? [0, ...valueArray] : valueArray); + if (scaleOptions.interval) { + if (isOrdinal) { + domain = domainFromInterval(domain, scaleOptions.interval, name); + } else { + if (markTypes.size > 0) { + console.warn( + 'Setting interval via axis options is only supported for ordinal scales' + ); + } + } + } + if (!scaleOptions.scale) { throw new Error(`No scale function defined for ${name}`); } @@ -350,6 +363,13 @@ export function createScale( }; } +function domainFromInterval(domain: RawValue[], interval: string | number, name: ScaleName) { + const interval_ = maybeInterval(interval); + const [lo, hi] = extent(domain); + const out = interval_.range(lo, interval_.offset(hi)); + return name === 'y' ? out.toReversed() : out; +} + /** * Infer a scale type based on the scale name, the data values mapped to it and * the mark types that are bound to the scale diff --git a/src/lib/marks/AxisX.svelte b/src/lib/marks/AxisX.svelte index db799c94..df156afb 100644 --- a/src/lib/marks/AxisX.svelte +++ b/src/lib/marks/AxisX.svelte @@ -2,6 +2,7 @@ Renders a horizontal axis with labels and tick marks --> + +
+ {#each examples as page, i (i)} + +
+ {#if page.screenshot} + {page.title}{/if} +
+

+ {page.title} +

+
+ {/each} +
+ + diff --git a/src/routes/examples/+layout.ts b/src/routes/examples/+layout.ts new file mode 100644 index 00000000..976ef042 --- /dev/null +++ b/src/routes/examples/+layout.ts @@ -0,0 +1,32 @@ +import { loadDatasets, loadJSON } from '$lib/helpers/data.js'; +import type { PageLoad } from './$types.js'; + +export const ssr = true; + +export const load: PageLoad = async ({ fetch }) => { + return { + data: { + world: await loadJSON(fetch, 'countries-110m'), + us: await loadJSON(fetch, 'us-counties-10m'), + ...(await loadDatasets( + [ + 'aapl', + 'beagle', + 'bls', + 'co2', + 'crimea', + 'driving', + 'penguins', + 'riaa', + 'stateage', + 'tdf', + 'rightwing', + 'stocks', + 'unemployment', + 'sftemp' + ], + fetch + )) + } + }; +}; diff --git a/src/routes/examples/+page.svelte b/src/routes/examples/+page.svelte new file mode 100644 index 00000000..48d0e854 --- /dev/null +++ b/src/routes/examples/+page.svelte @@ -0,0 +1,116 @@ + + + + +

+ Sometimes it's easiest to learn a new framework by + digging into examples. +

+ + + +
+ {#each Object.entries(paths) as [group, groupPages] (group)} +
+

+ {pages[ + groupPages.find((p) => + p.endsWith('/_index.svelte') + ) + ].title} +

+
    + {#each groupPages.filter((p) => !p.endsWith('/_index.svelte')) as page (page)} +
  • + {pages[page].title} +
  • + {/each} +
+
+ {/each} +
+ + diff --git a/src/routes/examples/[group]/+layout.svelte b/src/routes/examples/[group]/+layout.svelte new file mode 100644 index 00000000..57c22f0e --- /dev/null +++ b/src/routes/examples/[group]/+layout.svelte @@ -0,0 +1,25 @@ + + +
+
+ +
+
+ + diff --git a/src/routes/examples/[group]/+page.svelte b/src/routes/examples/[group]/+page.svelte new file mode 100644 index 00000000..d7e324be --- /dev/null +++ b/src/routes/examples/[group]/+page.svelte @@ -0,0 +1,77 @@ + + +{#if subPages.length} + {#if indexKey} + Examples + + + + {/if} +{:else} +

Not found

+{/if} diff --git a/src/routes/examples/[group]/[page]/+page.svelte b/src/routes/examples/[group]/[page]/+page.svelte new file mode 100644 index 00000000..c072337a --- /dev/null +++ b/src/routes/examples/[group]/[page]/+page.svelte @@ -0,0 +1,137 @@ + + + + + {@html $isDark ? codeStyleDark : codeStyleLight} + + +{#if plotKey} + +

{mod.title}

+ + {#if mod.description}

{@html mod.description}

{/if} + + +
+
+ +
+
+{:else} +

Not found

+{/if} + + diff --git a/src/routes/examples/axis/_index.svelte b/src/routes/examples/axis/_index.svelte new file mode 100644 index 00000000..d1adcc11 --- /dev/null +++ b/src/routes/examples/axis/_index.svelte @@ -0,0 +1,10 @@ + + +

Axis examples

+ +

+ Here are examples related to the AxisX and AxisY marks + as well as axis options on the Plot component +

diff --git a/src/routes/examples/axis/datawrapper-ticks.svelte b/src/routes/examples/axis/datawrapper-ticks.svelte new file mode 100644 index 00000000..97bb2136 --- /dev/null +++ b/src/routes/examples/axis/datawrapper-ticks.svelte @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/src/routes/examples/axis/tick-count.svelte b/src/routes/examples/axis/tick-count.svelte new file mode 100644 index 00000000..f317f353 --- /dev/null +++ b/src/routes/examples/axis/tick-count.svelte @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/src/routes/examples/axis/tick-interval.svelte b/src/routes/examples/axis/tick-interval.svelte new file mode 100644 index 00000000..25e50fa5 --- /dev/null +++ b/src/routes/examples/axis/tick-interval.svelte @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/src/routes/examples/axis/tick-spacing.svelte b/src/routes/examples/axis/tick-spacing.svelte new file mode 100644 index 00000000..594f0567 --- /dev/null +++ b/src/routes/examples/axis/tick-spacing.svelte @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/src/routes/examples/dot/0-scatterplot.svelte b/src/routes/examples/dot/0-scatterplot.svelte new file mode 100644 index 00000000..7e90469b --- /dev/null +++ b/src/routes/examples/dot/0-scatterplot.svelte @@ -0,0 +1,16 @@ + + + + + + + diff --git a/src/routes/examples/dot/1-colored-scatterplot.svelte b/src/routes/examples/dot/1-colored-scatterplot.svelte new file mode 100644 index 00000000..e2e37dfc --- /dev/null +++ b/src/routes/examples/dot/1-colored-scatterplot.svelte @@ -0,0 +1,17 @@ + + + + + + + diff --git a/src/routes/examples/dot/2-symbol-channel.svelte b/src/routes/examples/dot/2-symbol-channel.svelte new file mode 100644 index 00000000..cbffdf88 --- /dev/null +++ b/src/routes/examples/dot/2-symbol-channel.svelte @@ -0,0 +1,22 @@ + + + + + + + diff --git a/src/routes/examples/dot/_index.svelte b/src/routes/examples/dot/_index.svelte new file mode 100644 index 00000000..5ac0b223 --- /dev/null +++ b/src/routes/examples/dot/_index.svelte @@ -0,0 +1,5 @@ + + +

Dot examples

diff --git a/src/routes/examples/geo/_index.svelte b/src/routes/examples/geo/_index.svelte new file mode 100644 index 00000000..a90cb520 --- /dev/null +++ b/src/routes/examples/geo/_index.svelte @@ -0,0 +1,10 @@ + + +

Geo examples

+ +

+ You can use the Geo mark for creating + maps in SveltePlot. +

diff --git a/src/routes/examples/geo/us-choropleth-canvas.svelte b/src/routes/examples/geo/us-choropleth-canvas.svelte new file mode 100644 index 00000000..ba3381c3 --- /dev/null +++ b/src/routes/examples/geo/us-choropleth-canvas.svelte @@ -0,0 +1,45 @@ + + + + + + d.properties.unemployment} /> + diff --git a/src/routes/examples/geo/us-choropleth.svelte b/src/routes/examples/geo/us-choropleth.svelte new file mode 100644 index 00000000..328bc44d --- /dev/null +++ b/src/routes/examples/geo/us-choropleth.svelte @@ -0,0 +1,46 @@ + + + + + + d.properties.unemployment} + title={(d) => + `${d.properties.name}\n${d.properties.unemployment}%`} /> + diff --git a/src/routes/examples/grid/_index.svelte b/src/routes/examples/grid/_index.svelte new file mode 100644 index 00000000..4653cdb7 --- /dev/null +++ b/src/routes/examples/grid/_index.svelte @@ -0,0 +1,10 @@ + + +

Grid examples

+ +

+ Here are examples related to the grid marks. +

diff --git a/src/routes/examples/grid/clipped-gridlines.svelte b/src/routes/examples/grid/clipped-gridlines.svelte new file mode 100644 index 00000000..9b7bbbfe --- /dev/null +++ b/src/routes/examples/grid/clipped-gridlines.svelte @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/src/routes/examples/line/_index.svelte b/src/routes/examples/line/_index.svelte new file mode 100644 index 00000000..eb0dcfa6 --- /dev/null +++ b/src/routes/examples/line/_index.svelte @@ -0,0 +1,10 @@ + + +

Line examples

+ +

+ Here are examples related to the line mark. +

diff --git a/src/routes/examples/line/apple-stock.svelte b/src/routes/examples/line/apple-stock.svelte new file mode 100644 index 00000000..8a504894 --- /dev/null +++ b/src/routes/examples/line/apple-stock.svelte @@ -0,0 +1,15 @@ + + + + + + + diff --git a/src/routes/examples/line/gradient-line.svelte b/src/routes/examples/line/gradient-line.svelte new file mode 100644 index 00000000..2958a9bb --- /dev/null +++ b/src/routes/examples/line/gradient-line.svelte @@ -0,0 +1,40 @@ + + + + + + + + + + diff --git a/src/routes/examples/line/line-grouping.svelte b/src/routes/examples/line/line-grouping.svelte new file mode 100644 index 00000000..36a14542 --- /dev/null +++ b/src/routes/examples/line/line-grouping.svelte @@ -0,0 +1,17 @@ + + + + + + d.Date.getFullYear()} /> + diff --git a/src/routes/examples/line/tour-de-france.svelte b/src/routes/examples/line/tour-de-france.svelte new file mode 100644 index 00000000..6f3bc448 --- /dev/null +++ b/src/routes/examples/line/tour-de-france.svelte @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/src/routes/marks/axis/+page.md b/src/routes/marks/axis/+page.md index 48b7c2f8..a2efd740 100644 --- a/src/routes/marks/axis/+page.md +++ b/src/routes/marks/axis/+page.md @@ -260,10 +260,14 @@ You can explicitly add an x axis using the `AxisX` mark component. The `AxisX` c - `tickSize` - size of the tick marks in pixels (default: 6) - `tickFontSize` - font size for tick labels (default: 11) - `tickPadding` - padding between tick lines and labels (default: 3) +- `tickSpacing` - approximate pixel space between generated ticks +- `tickCount` - approximate number of ticks to generate - `tickFormat` - custom formatter for tick labels (can be 'auto', Intl.DateTimeFormatOptions, Intl.NumberFormatOptions, or custom function) - `tickClass` - function to assign custom classes to ticks based on their values - `automatic` - internal flag, set to true for implicit axes +For compatibility reasons AxisX also supports the "magic" **ticks** option which can be an alias for **data** (array), **interval** (string) or **tickCount** (number) depending on the type of value you're passing. + The `AxisX` component also inherits all styling properties from the base mark component (fill, stroke, strokeWidth, opacity, etc.). ## AxisY @@ -280,10 +284,14 @@ The `AxisY` component provides extensive customization options for y-axis presen - `tickSize` - size of the tick marks in pixels (default: 6) - `tickFontSize` - font size for tick labels (default: 11) - `tickPadding` - padding between tick lines and labels (default: 3) +- `tickSpacing` - approximate pixel space between generated ticks +- `tickCount` - approximate number of ticks to generate - `tickFormat` - custom formatter for tick labels (can be 'auto', Intl.DateTimeFormatOptions, Intl.NumberFormatOptions, or custom function) - `tickClass` - function to assign custom classes to ticks based on their values - `automatic` - internal flag, set to true for implicit axes +For compatibility reasons AxisY also supports the "magic" **ticks** option which can be an alias for **data** (array), **interval** (string) or **tickCount** (number) depending on the type of value you're passing. + The `AxisY` component also inherits all styling properties from the base mark component (fill, stroke, strokeWidth, opacity, etc.). ## Advanced use diff --git a/src/routes/marks/bar/+page.md b/src/routes/marks/bar/+page.md index 5b9f2333..ab85e244 100644 --- a/src/routes/marks/bar/+page.md +++ b/src/routes/marks/bar/+page.md @@ -7,33 +7,63 @@ title: Bar mark import StackedBarPlot from './StackedBarPlot.svelte'; -Bars are cool. They come in two flavors: [BarY](#BarY) for vertical bars (columns) and [BarX](#BarX) for horizontal bars. - -Here's a very simple bar chart: +Bars are useful to show quantitative data for different categories. They come in two flavors: [BarX](#BarX) for horizontal bars (y axis requires band scale) and [BarY](#BarY) for vertical bars aka. columns (x axis requires band scale). ```svelte live - - + + ``` ```svelte - - + + + + +``` + +[fork](https://svelte.dev/playground/7a0d38cf74be4a9985feb7bef0456008?version=5) + +SveltePlot automatically infers a band scale for the y axis in the above example. but since our data is missing a value for 2023, the value `"2023"` is entirely missing from the band scale domain. We could fix this by passing the domain value manually, or by using the `interval` option of the y axis: + +```svelte live + + + + + + +``` + +``` + + ``` -You can create stacked bar charts by defining a fill channel which will be used for grouping the series by the implicit [stack transform](/transforms/stack): +You can create stacked bar charts by defining a fill channel which will be used for grouping the series by the implicit [stack transform](/transforms/stack). In the following example we're first grouping the penguins dataset by island to then stack them by species: ```svelte live - - v ** 2)} - fill="steelblue" /> - + + + ``` ```svelte - v ** 2)} - fill="steelblue" /> - + + ``` [fork](https://svelte.dev/playground/8b9fb6c1946d4579a3dc9da32f6c983c?version=5) -For stacked bar charts, provide a `fill` channel that will be used for grouping the series: - -```svelte - - - -``` - ## Insets You can create bullet bars using the `inset` option and two `BarX` layers: diff --git a/src/routes/marks/line/+page.md b/src/routes/marks/line/+page.md index c55720ce..75c6587d 100644 --- a/src/routes/marks/line/+page.md +++ b/src/routes/marks/line/+page.md @@ -278,6 +278,8 @@ BLS Demo: ``` +[fork](https://svelte.dev/playground/31a8033153784f33848a7c388a67a82e?version=5) + ## Line The following channels are supported: diff --git a/src/routes/marks/rect/+page.md b/src/routes/marks/rect/+page.md index cae4503e..03b9610d 100644 --- a/src/routes/marks/rect/+page.md +++ b/src/routes/marks/rect/+page.md @@ -2,7 +2,85 @@ title: Rect mark --- -The Rect mark can be used to add rectangles to the plot, defined by x1, y1, x2, and y2 coordinates: +The Rect mark can be used to add rectangles to the plot, defined by x1, y1, x2, and y2 coordinates. It is useful in cases where both the x and y axis are using quantitative scales. + +:::tip +**Tip:** If one of your axes is a band scale, you may want to use the [Bar](/marks/bar) marks instead, and if both axes are band scales you probably need the [Cell](/marks/cell) mark. +::: + +In it's purest form, the `` mark will just add rectangles at the given coordinates: + +```svelte live + + + + + +``` + +```svelte + + + +``` + +[fork](https://svelte.dev/playground/7a6b0ae12c624ffeb52448adac644b5b?version=5) + +If your data does not come with x1/x2 and y1/y2 pairs but x/y coordinates, you can use the implicit interval transform: + +```svelte live + + + + + +``` + +```svelte + + + +``` The interval transform may be used to convert a single value in x or y (or both) into an extent. For example, the chart below shows the observed daily maximum temperature in Seattle for the year 2015. The day-in-month and month-in-year numbers are expanded to unit intervals by setting the [interval option](/transforms/interval) to 1. @@ -55,6 +133,8 @@ The interval transform may be used to convert a single value in x or y (or both) ``` +## Rect + ## RectX RectX can be used for range annotations: diff --git a/src/routes/tests/rect/+page.md b/src/routes/tests/rect/+page.md new file mode 100644 index 00000000..a4088f44 --- /dev/null +++ b/src/routes/tests/rect/+page.md @@ -0,0 +1,51 @@ +```svelte live + + + + + + +``` + +```svelte live + + + + + + +``` diff --git a/src/tests/axisX.test.svelte b/src/tests/axisX.test.svelte new file mode 100644 index 00000000..44a0e8c0 --- /dev/null +++ b/src/tests/axisX.test.svelte @@ -0,0 +1,14 @@ + + + + + diff --git a/src/tests/axisX.test.ts b/src/tests/axisX.test.ts new file mode 100644 index 00000000..ab174fed --- /dev/null +++ b/src/tests/axisX.test.ts @@ -0,0 +1,156 @@ +import { describe, it, expect } from 'vitest'; +import { render } from '@testing-library/svelte'; +import AxisXTest from './axisX.test.svelte'; + +describe('AxisX mark', () => { + it('default axis', () => { + const { container } = render(AxisXTest, { + props: { + plotArgs: { width: 400, x: { domain: [0, 100] } } + } + }); + + const ticks = container.querySelectorAll('g.axis-x > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBeGreaterThan(2); + expect(tickValues).toStrictEqual(['0', '20', '40', '60', '80', '100']); + }); + + it('custom tick values via axis.data', () => { + const { container } = render(AxisXTest, { + props: { + plotArgs: { width: 400, x: { domain: [0, 100] } }, + axisArgs: { data: [0, 20, 80] } + } + }); + + const ticks = container.querySelectorAll('g.axis-x > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '20', '80']); + }); + + it('custom tick values via x scale ticks options', () => { + const { container } = render(AxisXTest, { + props: { + plotArgs: { width: 400, x: { domain: [0, 100], ticks: [0, 20, 80] } } + } + }); + + const ticks = container.querySelectorAll('g.axis-x > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '20', '80']); + }); + + it('custom tick values via axis.ticks', () => { + const { container } = render(AxisXTest, { + props: { + plotArgs: { width: 400, x: { domain: [0, 100] } }, + axisArgs: { ticks: [0, 20, 80] } + } + }); + + const ticks = container.querySelectorAll('g.axis-x > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '20', '80']); + }); + + it('tick count via axis tickCount option', () => { + const { container } = render(AxisXTest, { + props: { + plotArgs: { width: 400, x: { domain: [0, 100] } }, + axisArgs: { tickCount: 3 } + } + }); + + const ticks = container.querySelectorAll('g.axis-x > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '50', '100']); + }); + + it('tick count via axis.ticks', () => { + const { container } = render(AxisXTest, { + props: { + plotArgs: { width: 400, x: { domain: [0, 100] } }, + axisArgs: { ticks: 3 } + } + }); + + const ticks = container.querySelectorAll('g.axis-x > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '50', '100']); + }); + + it('tick spacing via axis options', () => { + const { container } = render(AxisXTest, { + props: { + plotArgs: { width: 400, x: { domain: [0, 100] } }, + axisArgs: { tickSpacing: 200 } + } + }); + + const ticks = container.querySelectorAll('g.axis-x > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '50', '100']); + }); + + it('tick spacing via scale options', () => { + const { container } = render(AxisXTest, { + props: { + plotArgs: { width: 400, x: { domain: [0, 100], tickSpacing: 200 } } + } + }); + + const ticks = container.querySelectorAll('g.axis-x > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '50', '100']); + }); + + it('tick interval via scale options', () => { + const { container } = render(AxisXTest, { + props: { + plotArgs: { width: 400, x: { domain: [0, 100], interval: 30 } } + } + }); + + const ticks = container.querySelectorAll('g.axis-x > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(tickValues).toStrictEqual(['0', '30', '60', '90', '120']); + }); + + it('tick interval via axis options', () => { + const { container } = render(AxisXTest, { + props: { + plotArgs: { width: 400, x: { domain: [0, 100] } }, + axisArgs: { interval: 30 } + } + }); + + const ticks = container.querySelectorAll('g.axis-x > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(tickValues).toStrictEqual(['0', '30', '60', '90', '120']); + }); + + it('tick interval via axis.ticks', () => { + const { container } = render(AxisXTest, { + props: { + plotArgs: { + width: 400, + x: { domain: [new Date(2000, 0, 1), new Date(2002, 0, 1)] } + }, + axisArgs: { ticks: '6 months' } + } + }); + + const ticks = container.querySelectorAll('g.axis-x > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(5); + expect(tickValues).toStrictEqual(['Jan2000', 'Jul', 'Jan2001', 'Jul', 'Jan2002']); + }); +}); diff --git a/src/tests/axisY.test.svelte b/src/tests/axisY.test.svelte new file mode 100644 index 00000000..9fa595a7 --- /dev/null +++ b/src/tests/axisY.test.svelte @@ -0,0 +1,14 @@ + + + + + diff --git a/src/tests/axisY.test.ts b/src/tests/axisY.test.ts new file mode 100644 index 00000000..f2b2e8fe --- /dev/null +++ b/src/tests/axisY.test.ts @@ -0,0 +1,139 @@ +import { describe, it, expect } from 'vitest'; +import { render } from '@testing-library/svelte'; +import AxisYTest from './axisY.test.svelte'; + +describe('AxisY mark', () => { + it('default axis', () => { + const { container } = render(AxisYTest, { + props: { + plotArgs: { height: 300, y: { domain: [0, 100] } } + } + }); + + const ticks = container.querySelectorAll('g.axis-y > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBeGreaterThan(2); + expect(tickValues).toStrictEqual(['0', '20', '40', '60', '80', '100']); + }); + + it('custom tick values via axis.data', () => { + const { container } = render(AxisYTest, { + props: { + plotArgs: { height: 300, y: { domain: [0, 100] } }, + axisArgs: { data: [0, 20, 80] } + } + }); + + const ticks = container.querySelectorAll('g.axis-y > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '20', '80']); + }); + + it('custom tick values via axis.ticks', () => { + const { container } = render(AxisYTest, { + props: { + plotArgs: { height: 300, y: { domain: [0, 100] } }, + axisArgs: { ticks: [0, 20, 80] } + } + }); + + const ticks = container.querySelectorAll('g.axis-y > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '20', '80']); + }); + + it('custom tick values via x scale ticks options', () => { + const { container } = render(AxisYTest, { + props: { + plotArgs: { height: 300, y: { domain: [0, 100], ticks: [0, 20, 80] } } + } + }); + + const ticks = container.querySelectorAll('g.axis-y > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '20', '80']); + }); + + it('tick count via axis.tickCount', () => { + const { container } = render(AxisYTest, { + props: { + plotArgs: { height: 300, y: { domain: [0, 100] } }, + axisArgs: { tickCount: 3 } + } + }); + + const ticks = container.querySelectorAll('g.axis-y > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '50', '100']); + }); + + it('tick count via axis.ticks', () => { + const { container } = render(AxisYTest, { + props: { + plotArgs: { height: 300, y: { domain: [0, 100] } }, + axisArgs: { ticks: 3 } + } + }); + + const ticks = container.querySelectorAll('g.axis-y > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '50', '100']); + }); + + it('tickSpacing', () => { + const { container } = render(AxisYTest, { + props: { + plotArgs: { height: 300, y: { domain: [0, 100] } }, + axisArgs: { tickSpacing: 200 } + } + }); + + const ticks = container.querySelectorAll('g.axis-y > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '50', '100']); + }); + + it('tickSpacing', () => { + const { container } = render(AxisYTest, { + props: { + plotArgs: { height: 300, y: { domain: [0, 100], tickSpacing: 200 } } + } + }); + + const ticks = container.querySelectorAll('g.axis-y > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(ticks.length).toBe(3); + expect(tickValues).toStrictEqual(['0', '50', '100']); + }); + + it('tick interval via scale options', () => { + const { container } = render(AxisYTest, { + props: { + plotArgs: { height: 300, y: { domain: [0, 100], interval: 30 } } + } + }); + + const ticks = container.querySelectorAll('g.axis-y > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(tickValues).toStrictEqual(['0', '30', '60', '90', '120']); + }); + + it('tick interval via axis options', () => { + const { container } = render(AxisYTest, { + props: { + plotArgs: { height: 300, y: { domain: [0, 100] } }, + axisArgs: { interval: 30 } + } + }); + + const ticks = container.querySelectorAll('g.axis-y > g.tick') as NodeListOf; + const tickValues = Array.from(ticks).map((t) => t.querySelector('text')?.textContent); + expect(tickValues).toStrictEqual(['0', '30', '60', '90', '120']); + }); +}); diff --git a/src/tests/barX.test.ts b/src/tests/barX.test.ts index 5b186d83..19e74f70 100644 --- a/src/tests/barX.test.ts +++ b/src/tests/barX.test.ts @@ -84,6 +84,67 @@ describe('BarX mark', () => { // // check that bar length match data expect(barDims.map((d) => d.w)).toStrictEqual([1, 2, 3, 4, 5].map((m) => barDims[0].w * m)); }); + + const timeseries = [ + { year: 2019, value: 1 }, + { year: 2020, value: 2 }, + { year: 2021, value: 3 }, + { year: 2022, value: 4 }, + { year: 2024, value: 5 } + ]; + + it('skips missing years in band scale domain', () => { + const { container } = render(BarXTest, { + props: { + plotArgs: { + height: 200, + axes: true + }, + barArgs: { + data: timeseries, + x: 'value', + y: 'year' + } + } + }); + + const bars = container.querySelectorAll('g.bar-x > rect') as NodeListOf; + expect(bars.length).toBe(5); + + const yAxisLabels = container.querySelectorAll( + 'g.axis-y .tick text' + ) as NodeListOf; + expect(yAxisLabels.length).toBe(5); + const labels = Array.from(yAxisLabels).map((d) => d.textContent); + expect(labels.sort()).toStrictEqual(['2019', '2020', '2021', '2022', '2024']); + }); + + it('includes missing years in band scale domain if interval is set', () => { + const { container } = render(BarXTest, { + props: { + plotArgs: { + height: 200, + axes: true, + y: { interval: 1 } + }, + barArgs: { + data: timeseries, + x: 'value', + y: 'year' + } + } + }); + + const bars = container.querySelectorAll('g.bar-x > rect') as NodeListOf; + expect(bars.length).toBe(5); + + const yAxisLabels = container.querySelectorAll( + 'g.axis-y .tick text' + ) as NodeListOf; + expect(yAxisLabels.length).toBe(6); + const labels = Array.from(yAxisLabels).map((d) => d.textContent); + expect(labels.sort()).toEqual(['2019', '2020', '2021', '2022', '2023', '2024']); + }); }); function getRectDims(rect: SVGRectElement) { diff --git a/src/tests/barY.test.ts b/src/tests/barY.test.ts index 118b2725..9a10b9fc 100644 --- a/src/tests/barY.test.ts +++ b/src/tests/barY.test.ts @@ -110,6 +110,69 @@ describe('BarY mark', () => { expect(barDims[3].h).toBe(barDims[0].h * 4); expect(barDims[4].h).toBe(barDims[0].h * 5); }); + + const timeseries = [ + { year: 2019, value: 1 }, + { year: 2020, value: 2 }, + { year: 2021, value: 3 }, + { year: 2022, value: 4 }, + { year: 2024, value: 5 } + ]; + + it('skips missing years in band scale domain', () => { + const { container } = render(BarYTest, { + props: { + plotArgs: { + width: 400, + height: 400, + axes: true + }, + barArgs: { + data: timeseries, + y: 'value', + x: 'year' + } + } + }); + + const bars = container.querySelectorAll('g.bar-y > rect') as NodeListOf; + expect(bars.length).toBe(5); + + const xAxisLabels = container.querySelectorAll( + 'g.axis-x .tick text' + ) as NodeListOf; + expect(xAxisLabels.length).toBe(5); + const labels = Array.from(xAxisLabels).map((d) => d.textContent); + expect(labels.sort()).toStrictEqual(['2019', '2020', '2021', '2022', '2024']); + }); + + it('includes missing years in band scale domain if interval is set', () => { + const { container } = render(BarYTest, { + props: { + plotArgs: { + width: 500, + height: 400, + axes: true, + x: { interval: 1 } + }, + barArgs: { + data: timeseries, + y: 'value', + x: 'year' + } + } + }); + + const bars = container.querySelectorAll('g.bar-y > rect') as NodeListOf; + expect(bars.length).toBe(5); + + const xAxisLabels = container.querySelectorAll( + 'g.axis-x .tick text' + ) as NodeListOf; + expect(xAxisLabels.length).toBe(6); + const labels = Array.from(xAxisLabels).map((d) => d.textContent); + expect(labels.sort()).toEqual(['2019', '2020', '2021', '2022', '2023', '2024']); + }); }); function getRectDims(rect: SVGRectElement) { diff --git a/static/examples/axis/datawrapper-ticks.dark.png b/static/examples/axis/datawrapper-ticks.dark.png new file mode 100644 index 00000000..739788e8 Binary files /dev/null and b/static/examples/axis/datawrapper-ticks.dark.png differ diff --git a/static/examples/axis/datawrapper-ticks.png b/static/examples/axis/datawrapper-ticks.png new file mode 100644 index 00000000..850c1630 Binary files /dev/null and b/static/examples/axis/datawrapper-ticks.png differ diff --git a/static/examples/axis/tick-count.dark.png b/static/examples/axis/tick-count.dark.png new file mode 100644 index 00000000..57dd89b0 Binary files /dev/null and b/static/examples/axis/tick-count.dark.png differ diff --git a/static/examples/axis/tick-count.png b/static/examples/axis/tick-count.png new file mode 100644 index 00000000..a2801e9e Binary files /dev/null and b/static/examples/axis/tick-count.png differ diff --git a/static/examples/axis/tick-interval.dark.png b/static/examples/axis/tick-interval.dark.png new file mode 100644 index 00000000..c85ed1a5 Binary files /dev/null and b/static/examples/axis/tick-interval.dark.png differ diff --git a/static/examples/axis/tick-interval.png b/static/examples/axis/tick-interval.png new file mode 100644 index 00000000..a9a23c25 Binary files /dev/null and b/static/examples/axis/tick-interval.png differ diff --git a/static/examples/axis/tick-spacing.dark.png b/static/examples/axis/tick-spacing.dark.png new file mode 100644 index 00000000..e451e2de Binary files /dev/null and b/static/examples/axis/tick-spacing.dark.png differ diff --git a/static/examples/axis/tick-spacing.png b/static/examples/axis/tick-spacing.png new file mode 100644 index 00000000..bea52550 Binary files /dev/null and b/static/examples/axis/tick-spacing.png differ diff --git a/static/examples/dot/0-scatterplot.dark.png b/static/examples/dot/0-scatterplot.dark.png new file mode 100644 index 00000000..3dfc2250 Binary files /dev/null and b/static/examples/dot/0-scatterplot.dark.png differ diff --git a/static/examples/dot/0-scatterplot.png b/static/examples/dot/0-scatterplot.png new file mode 100644 index 00000000..898d2190 Binary files /dev/null and b/static/examples/dot/0-scatterplot.png differ diff --git a/static/examples/dot/1-colored-scatterplot.dark.png b/static/examples/dot/1-colored-scatterplot.dark.png new file mode 100644 index 00000000..d5ac2e6d Binary files /dev/null and b/static/examples/dot/1-colored-scatterplot.dark.png differ diff --git a/static/examples/dot/1-colored-scatterplot.png b/static/examples/dot/1-colored-scatterplot.png new file mode 100644 index 00000000..a0cee5f4 Binary files /dev/null and b/static/examples/dot/1-colored-scatterplot.png differ diff --git a/static/examples/geo/us-choropleth-canvas.dark.png b/static/examples/geo/us-choropleth-canvas.dark.png new file mode 100644 index 00000000..dceedfb8 Binary files /dev/null and b/static/examples/geo/us-choropleth-canvas.dark.png differ diff --git a/static/examples/geo/us-choropleth-canvas.png b/static/examples/geo/us-choropleth-canvas.png new file mode 100644 index 00000000..6c9934b1 Binary files /dev/null and b/static/examples/geo/us-choropleth-canvas.png differ diff --git a/static/examples/geo/us-choropleth.dark.png b/static/examples/geo/us-choropleth.dark.png new file mode 100644 index 00000000..0973d26e Binary files /dev/null and b/static/examples/geo/us-choropleth.dark.png differ diff --git a/static/examples/geo/us-choropleth.png b/static/examples/geo/us-choropleth.png new file mode 100644 index 00000000..55891957 Binary files /dev/null and b/static/examples/geo/us-choropleth.png differ diff --git a/static/examples/grid/clipped-gridlines.dark.png b/static/examples/grid/clipped-gridlines.dark.png new file mode 100644 index 00000000..5ec16f3b Binary files /dev/null and b/static/examples/grid/clipped-gridlines.dark.png differ diff --git a/static/examples/grid/clipped-gridlines.png b/static/examples/grid/clipped-gridlines.png new file mode 100644 index 00000000..21fc17b9 Binary files /dev/null and b/static/examples/grid/clipped-gridlines.png differ diff --git a/static/examples/line/apple-stock.dark.png b/static/examples/line/apple-stock.dark.png new file mode 100644 index 00000000..44ca5ce3 Binary files /dev/null and b/static/examples/line/apple-stock.dark.png differ diff --git a/static/examples/line/apple-stock.png b/static/examples/line/apple-stock.png new file mode 100644 index 00000000..c490a2ba Binary files /dev/null and b/static/examples/line/apple-stock.png differ diff --git a/static/examples/line/gradient-line.dark.png b/static/examples/line/gradient-line.dark.png new file mode 100644 index 00000000..d013adc0 Binary files /dev/null and b/static/examples/line/gradient-line.dark.png differ diff --git a/static/examples/line/gradient-line.png b/static/examples/line/gradient-line.png new file mode 100644 index 00000000..0e8901ca Binary files /dev/null and b/static/examples/line/gradient-line.png differ diff --git a/static/examples/line/line-grouping.dark.png b/static/examples/line/line-grouping.dark.png new file mode 100644 index 00000000..a66722f5 Binary files /dev/null and b/static/examples/line/line-grouping.dark.png differ diff --git a/static/examples/line/line-grouping.png b/static/examples/line/line-grouping.png new file mode 100644 index 00000000..5a2b8c1b Binary files /dev/null and b/static/examples/line/line-grouping.png differ diff --git a/static/examples/line/tour-de-france.dark.png b/static/examples/line/tour-de-france.dark.png new file mode 100644 index 00000000..568293ff Binary files /dev/null and b/static/examples/line/tour-de-france.dark.png differ diff --git a/static/examples/line/tour-de-france.png b/static/examples/line/tour-de-france.png new file mode 100644 index 00000000..8bdd40fb Binary files /dev/null and b/static/examples/line/tour-de-france.png differ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy