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