diff --git a/.changeset/lazy-otters-shave.md b/.changeset/lazy-otters-shave.md index 8b62691..a4ddcfb 100644 --- a/.changeset/lazy-otters-shave.md +++ b/.changeset/lazy-otters-shave.md @@ -16,4 +16,3 @@ the listeners down:
…
``` - diff --git a/.changeset/soft-pianos-repeat.md b/.changeset/soft-pianos-repeat.md new file mode 100644 index 0000000..a261ace --- /dev/null +++ b/.changeset/soft-pianos-repeat.md @@ -0,0 +1,25 @@ +--- +'@ixirjs/pulse': minor +--- + +Move transform animations off the main thread. + +`animate()` drives transforms through CSS custom properties so independent +animations compose on one element, but Chromium cannot run a custom-property +animation on the compositor, and one such property pins everything animating +alongside it. A dialog morph was a style recalc and a full repaint every frame. + +Three changes, all automatic and with no new options: + +- Transform-animated elements get a `will-change` hint while an animation is in + flight, so they are no longer repainted every frame. Every gesture takes the + same hint for as long as it is writing, and hands it back after. +- When nothing else is composing on an element, the variable keyframes are + folded into real `translate` / `scale` / `rotate` keyframes. The fold reverses + itself the moment anything else touches the element, so composition is + unchanged. +- Props are no longer grouped across the compositability boundary, so a + `width` can no longer drag a sibling `opacity` onto the main thread. + +One visible consequence: an `animate()` call mixing compositable and layout +properties now produces two entries in `controller.animations` instead of one. diff --git a/.gitignore b/.gitignore index 294b385..1998b13 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ Thumbs.db # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* + +# Generated code graph (graphify update .) +graphify-out diff --git a/.size-limit.json b/.size-limit.json index 5dc1964..5fb81b2 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -2,16 +2,16 @@ { "name": "Root public API", "path": "./dist/index.js", - "limit": "19 kB" + "limit": "20 kB" }, { "name": "animate only", "path": "./dist/animate/index.js", - "limit": "8 kB" + "limit": "9 kB" }, { "name": "flip only", "path": "./dist/flip/index.js", - "limit": "7 kB" + "limit": "9 kB" } ] diff --git a/bun.lock b/bun.lock index a4c24dd..8c7af79 100644 --- a/bun.lock +++ b/bun.lock @@ -6,15 +6,9 @@ "name": "@ixirjs/pulse", "devDependencies": { "@changesets/cli": "^2.31.0", - "@chromatic-com/storybook": "^5.1.2", "@eslint/compat": "^2.0.4", "@eslint/js": "^10.0.1", "@size-limit/preset-small-lib": "^12.1.0", - "@storybook/addon-a11y": "^10.3.6", - "@storybook/addon-docs": "^10.3.6", - "@storybook/addon-svelte-csf": "^5.1.2", - "@storybook/addon-vitest": "^10.3.6", - "@storybook/sveltekit": "^10.3.6", "@sveltejs/adapter-auto": "^7.0.1", "@sveltejs/kit": "^2.57.0", "@sveltejs/package": "^2.5.7", @@ -25,7 +19,6 @@ "@vitest/coverage-v8": "^4.1.3", "eslint": "^10.2.0", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-storybook": "^10.3.6", "eslint-plugin-svelte": "^3.17.0", "globals": "^17.4.0", "mdsvex": "^0.12.7", @@ -35,7 +28,6 @@ "prettier-plugin-tailwindcss": "^0.7.2", "publint": "^0.3.18", "size-limit": "^12.1.0", - "storybook": "^10.3.6", "svelte": "^5.55.2", "svelte-check": "^4.4.6", "tailwindcss": "^4.2.2", @@ -51,10 +43,6 @@ }, }, "packages": { - "@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="], - - "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], @@ -103,65 +91,63 @@ "@changesets/write": ["@changesets/write@0.4.0", "", { "dependencies": { "@changesets/types": "^6.1.0", "fs-extra": "^7.0.1", "human-id": "^4.1.1", "prettier": "^2.7.1" } }, "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q=="], - "@chromatic-com/storybook": ["@chromatic-com/storybook@5.1.2", "", { "dependencies": { "@neoconfetti/react": "^1.0.0", "chromatic": "^13.3.4", "filesize": "^10.0.12", "jsonfile": "^6.1.0", "strip-ansi": "^7.1.0" }, "peerDependencies": { "storybook": "^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0" } }, "sha512-H/hgvwC3E+OtseP2OT2QYUJH2VfnzT6wM3pWOkaNV6g7QI+VUdWJbeJ3o2jFqvEPQNqzhQKWDOlvM4lu+7is6g=="], - "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.0", "", { "os": "android", "cpu": "arm" }, "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.0", "", { "os": "android", "cpu": "arm64" }, "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.0", "", { "os": "android", "cpu": "x64" }, "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.0", "", { "os": "linux", "cpu": "x64" }, "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.0", "", { "os": "none", "cpu": "x64" }, "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw=="], "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], @@ -207,12 +193,8 @@ "@manypkg/get-packages": ["@manypkg/get-packages@1.1.3", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@changesets/types": "^4.0.1", "@manypkg/find-root": "^1.1.0", "fs-extra": "^8.1.0", "globby": "^11.0.0", "read-yaml-file": "^1.1.0" } }, "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A=="], - "@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], - "@neoconfetti/react": ["@neoconfetti/react@1.0.0", "", {}, "sha512-klcSooChXXOzIm+SE5IISIAn3bYzYfPjbX7D7HoqZL84oAfgREeSg5vSIaSFH+DaGzzvImTyWe1OyrJ67vik4A=="], - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], @@ -265,32 +247,6 @@ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@storybook/addon-a11y": ["@storybook/addon-a11y@10.3.6", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.3.6" } }, "sha512-cbwXIT5CeHZ9AFbTKQ6YB7Ct6TAl/kKOgALbvzzVtFfRvm51JYygGaiJaB7PbPWn9wgJP2olJcFt+erlEc6cRw=="], - - "@storybook/addon-docs": ["@storybook/addon-docs@10.3.6", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.3.6", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.3.6", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.3.6" } }, "sha512-TvIdADVPtauxW0LzXIpIv7X6GxwetorhyNh+6+7MHC27XSBCWVxxRUwL63YeLlHTuXsIk0quG3b1xgwVRzWOJA=="], - - "@storybook/addon-svelte-csf": ["@storybook/addon-svelte-csf@5.1.2", "", { "dependencies": { "@storybook/csf": "^0.1.13", "dedent": "^1.5.3", "es-toolkit": "^1.26.1", "esrap": "^1.2.2", "magic-string": "^0.30.12", "svelte-ast-print": "^0.4.0", "zimmerframe": "^1.1.2" }, "peerDependencies": { "@storybook/svelte": "^0.0.0-0 || ^8.2.0 || ^9.0.0 || ^9.1.0-0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0", "@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "storybook": "^0.0.0-0 || ^8.2.0 || ^9.0.0 || ^9.1.0-0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-NpImknEb48J7yr/ArTYpvhDSvGUrgm5Nuybu9PCicjSKTACsXX7cln2R19572ORtns399RTE+t20BBOKxSPm2g=="], - - "@storybook/addon-vitest": ["@storybook/addon-vitest@10.3.6", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.3.6", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-HXj7RrPJY+xzoNjL+xZu2oLw1fI5BA87Noh1NAXMPuECHR5R5fuRM/tTsJuIGXHFMO06FjSi/rekDIfCj1fL4w=="], - - "@storybook/builder-vite": ["@storybook/builder-vite@10.3.6", "", { "dependencies": { "@storybook/csf-plugin": "10.3.6", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.3.6", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-gpvR/sE4BcrFtmQZ+Ker7zD23oQzoVeqD9nF6cK6yzY+Q0svJXyX2EPmFG4y+EwygD5/vNzDpP84gGMut8VRwg=="], - - "@storybook/csf": ["@storybook/csf@0.1.13", "", { "dependencies": { "type-fest": "^2.19.0" } }, "sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q=="], - - "@storybook/csf-plugin": ["@storybook/csf-plugin@10.3.6", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.3.6", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-9kBf7VRdRqTSIYo+rPtVn5yjYYyK8kP2QhEYx3oiXvfwy4RexmbJnhk/tXa/lNiTqukA1TqaWQ2+5MqF4fu6YQ=="], - - "@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="], - - "@storybook/icons": ["@storybook/icons@2.0.2", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-KZBCpXsshAIjczYNXR/rlxEtCUX/eAbpFNwKi8bcOomrLA4t/SyPz5RF+lVPO2oZBUE4sAkt43mfJUevQDSEEw=="], - - "@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.3.6", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.3.6" } }, "sha512-/Tu1gPu+Fw+zOnAGmxRmOD30FX3a04LxcTAKflEtdpmtIMVR5bA3qpjy+f5YhoyDCecbXyKmL1OeIU2FIIZHqQ=="], - - "@storybook/svelte": ["@storybook/svelte@10.3.6", "", { "dependencies": { "ts-dedent": "^2.0.0", "type-fest": "~2.19" }, "peerDependencies": { "storybook": "^10.3.6", "svelte": "^5.0.0" } }, "sha512-XE+wNIiztpX6SapuJjYOgZajYWKDMDy/4LVbcqqypOoiYXnO/YOO2p9RdDgD8ta+J88Nap+/qiP7rBbzKOOrOA=="], - - "@storybook/svelte-vite": ["@storybook/svelte-vite@10.3.6", "", { "dependencies": { "@storybook/builder-vite": "10.3.6", "@storybook/svelte": "10.3.6", "magic-string": "^0.30.0", "svelte2tsx": "^0.7.44", "typescript": "^4.9.4 || ^5.0.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "storybook": "^10.3.6", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-R+Z0TMqLe9XI7yJyfUel9qeZCh2pXUCl4C7SVWec53j9q0qEVcWVleh4Yob1p0dL0cBNMfU5bQN6d56nKVrwTA=="], - - "@storybook/sveltekit": ["@storybook/sveltekit@10.3.6", "", { "dependencies": { "@storybook/builder-vite": "10.3.6", "@storybook/svelte": "10.3.6", "@storybook/svelte-vite": "10.3.6" }, "peerDependencies": { "storybook": "^10.3.6", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-pyQxMcJRhirtF2aWUNEfuSM5MCg4FXcis0Xk9duHIizOQ3pT0rPcY533g9EByzmi3Rm9xYOZVRKllla+G0kV1A=="], - "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.9", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA=="], "@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@7.0.1", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ=="], @@ -331,18 +287,10 @@ "@tailwindcss/vite": ["@tailwindcss/vite@4.3.0", "", { "dependencies": { "@tailwindcss/node": "4.3.0", "@tailwindcss/oxide": "4.3.0", "tailwindcss": "4.3.0" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw=="], - "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], - - "@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="], - "@testing-library/svelte-core": ["@testing-library/svelte-core@1.0.0", "", { "peerDependencies": { "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0" } }, "sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ=="], - "@testing-library/user-event": ["@testing-library/user-event@14.6.1", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw=="], - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], - "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], - "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], @@ -357,12 +305,8 @@ "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], - "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="], - "@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="], - "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], - "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], "@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], @@ -393,7 +337,7 @@ "@vitest/coverage-v8": ["@vitest/coverage-v8@4.1.6", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.6", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "@vitest/browser": "4.1.6", "vitest": "4.1.6" }, "optionalPeers": ["@vitest/browser"] }, "sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ=="], - "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + "@vitest/expect": ["@vitest/expect@4.1.6", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.6", "@vitest/utils": "4.1.6", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg=="], "@vitest/mocker": ["@vitest/mocker@4.1.6", "", { "dependencies": { "@vitest/spy": "4.1.6", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ=="], @@ -403,12 +347,10 @@ "@vitest/snapshot": ["@vitest/snapshot@4.1.6", "", { "dependencies": { "@vitest/pretty-format": "4.1.6", "@vitest/utils": "4.1.6", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw=="], - "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + "@vitest/spy": ["@vitest/spy@4.1.6", "", {}, "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg=="], "@vitest/utils": ["@vitest/utils@4.1.6", "", { "dependencies": { "@vitest/pretty-format": "4.1.6", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ=="], - "@webcontainer/env": ["@webcontainer/env@1.1.1", "", {}, "sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng=="], - "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -417,9 +359,7 @@ "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], - "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -429,12 +369,8 @@ "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], - "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], - "ast-v8-to-istanbul": ["ast-v8-to-istanbul@1.0.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg=="], - "axe-core": ["axe-core@4.11.4", "", {}, "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA=="], - "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], @@ -445,20 +381,14 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], - "bytes-iec": ["bytes-iec@3.1.1", "", {}, "sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA=="], - "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], "chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="], - "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], - "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], - "chromatic": ["chromatic@13.3.5", "", { "peerDependencies": { "@chromatic-com/cypress": "^0.*.* || ^1.0.0", "@chromatic-com/playwright": "^0.*.* || ^1.0.0" }, "optionalPeers": ["@chromatic-com/cypress", "@chromatic-com/playwright"], "bin": { "chroma": "dist/bin.js", "chromatic": "dist/bin.js", "chromatic-cli": "dist/bin.js" } }, "sha512-MzPhxpl838qJUo0A55osCF2ifwPbjcIPeElr1d4SHcjnHoIcg7l1syJDrAYK/a+PcCBrOGi06jPNpQAln5hWgw=="], - "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], @@ -467,32 +397,16 @@ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="], - "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], - "dedent-js": ["dedent-js@1.0.1", "", {}, "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ=="], - "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], - "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], - - "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], - - "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], - - "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], - "detect-indent": ["detect-indent@6.1.0", "", {}, "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], @@ -501,17 +415,13 @@ "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], - "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], - "enhanced-resolve": ["enhanced-resolve@5.21.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q=="], "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], - "es-toolkit": ["es-toolkit@1.46.1", "", {}, "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ=="], - - "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + "esbuild": ["esbuild@0.28.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.0", "@esbuild/android-arm": "0.28.0", "@esbuild/android-arm64": "0.28.0", "@esbuild/android-x64": "0.28.0", "@esbuild/darwin-arm64": "0.28.0", "@esbuild/darwin-x64": "0.28.0", "@esbuild/freebsd-arm64": "0.28.0", "@esbuild/freebsd-x64": "0.28.0", "@esbuild/linux-arm": "0.28.0", "@esbuild/linux-arm64": "0.28.0", "@esbuild/linux-ia32": "0.28.0", "@esbuild/linux-loong64": "0.28.0", "@esbuild/linux-mips64el": "0.28.0", "@esbuild/linux-ppc64": "0.28.0", "@esbuild/linux-riscv64": "0.28.0", "@esbuild/linux-s390x": "0.28.0", "@esbuild/linux-x64": "0.28.0", "@esbuild/netbsd-arm64": "0.28.0", "@esbuild/netbsd-x64": "0.28.0", "@esbuild/openbsd-arm64": "0.28.0", "@esbuild/openbsd-x64": "0.28.0", "@esbuild/openharmony-arm64": "0.28.0", "@esbuild/sunos-x64": "0.28.0", "@esbuild/win32-arm64": "0.28.0", "@esbuild/win32-ia32": "0.28.0", "@esbuild/win32-x64": "0.28.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], @@ -519,8 +429,6 @@ "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], - "eslint-plugin-storybook": ["eslint-plugin-storybook@10.3.6", "", { "dependencies": { "@typescript-eslint/utils": "^8.48.0" }, "peerDependencies": { "eslint": ">=8", "storybook": "^10.3.6" } }, "sha512-8udrL+Rmp5LFaZvgRe4J226X1MYls25bWCyHuzR5X8s2qbFTryX+wKC+o/0Ato4A1AvwnDg8OOMPc6yWJ9JpcA=="], - "eslint-plugin-svelte": ["eslint-plugin-svelte@3.17.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.6.1", "@jridgewell/sourcemap-codec": "^1.5.0", "esutils": "^2.0.3", "globals": "^16.0.0", "known-css-properties": "^0.37.0", "postcss": "^8.4.49", "postcss-load-config": "^3.1.4", "postcss-safe-parser": "^7.0.0", "semver": "^7.6.3", "svelte-eslint-parser": "^1.4.0" }, "peerDependencies": { "eslint": "^8.57.1 || ^9.0.0 || ^10.0.0", "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-NyiXHtS3Ni7e532RBwS9OXlMKDIrENg3gY+/+ODjZzQx2xhU3NlJ+nIl1a93iUUQeiJL3lS8KLmY+W8hklzweQ=="], "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], @@ -535,7 +443,7 @@ "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], - "esrap": ["esrap@1.4.9", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g=="], + "esrap": ["esrap@2.2.8", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-MPweq2EvEGj8jwOI7Hgycw/QIHzqA1EbAM8lG7p+FBfZbZq/hQ6h3AMsqnu/djzisH1KVWNzbb7LSgIVtMlPSg=="], "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], @@ -563,8 +471,6 @@ "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - "filesize": ["filesize@10.1.6", "", {}, "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w=="], - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], @@ -597,16 +503,10 @@ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], - - "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], @@ -615,8 +515,6 @@ "is-windows": ["is-windows@1.0.2", "", {}, "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="], - "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], @@ -637,7 +535,7 @@ "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], @@ -679,10 +577,6 @@ "lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="], - "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], - - "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "magicast": ["magicast@0.5.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="], @@ -695,8 +589,6 @@ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], - "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], @@ -713,8 +605,6 @@ "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], - "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "outdent": ["outdent@0.5.0", "", {}, "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q=="], @@ -739,8 +629,6 @@ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], @@ -771,8 +659,6 @@ "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.7.4", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-UKii4RjY05SNt/WQi6/NcOn/LsT0/ILLXsxygjbRg5/YZelsSu5jTqorYHPDGq4nZy5q5hpCu+XdGZ1xaJEQgw=="], - "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], - "prism-svelte": ["prism-svelte@0.4.7", "", {}, "sha512-yABh19CYbM24V7aS7TuPYRNMqthxwbvx6FF/Rw920YbyBWO3tnyPIqRMgHuSVsLmuHkkBS1Akyof463FVdkeDQ=="], "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], @@ -785,36 +671,22 @@ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - "react": ["react@19.2.6", "", {}, "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q=="], - - "react-dom": ["react-dom@19.2.6", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.6" } }, "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g=="], - - "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], - "read-yaml-file": ["read-yaml-file@1.1.0", "", { "dependencies": { "graceful-fs": "^4.1.5", "js-yaml": "^3.6.1", "pify": "^4.0.1", "strip-bom": "^3.0.0" } }, "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA=="], "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], - "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], - - "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], - "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], "rolldown": ["rolldown@1.0.0", "", { "dependencies": { "@oxc-project/types": "=0.129.0", "@rolldown/pluginutils": "1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0", "@rolldown/binding-darwin-arm64": "1.0.0", "@rolldown/binding-darwin-x64": "1.0.0", "@rolldown/binding-freebsd-x64": "1.0.0", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0", "@rolldown/binding-linux-arm64-gnu": "1.0.0", "@rolldown/binding-linux-arm64-musl": "1.0.0", "@rolldown/binding-linux-ppc64-gnu": "1.0.0", "@rolldown/binding-linux-s390x-gnu": "1.0.0", "@rolldown/binding-linux-x64-gnu": "1.0.0", "@rolldown/binding-linux-x64-musl": "1.0.0", "@rolldown/binding-openharmony-arm64": "1.0.0", "@rolldown/binding-wasm32-wasi": "1.0.0", "@rolldown/binding-win32-arm64-msvc": "1.0.0", "@rolldown/binding-win32-x64-msvc": "1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA=="], - "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="], "semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="], @@ -835,8 +707,6 @@ "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "spawndamnit": ["spawndamnit@3.0.1", "", { "dependencies": { "cross-spawn": "^7.0.5", "signal-exit": "^4.0.1" } }, "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg=="], @@ -847,20 +717,14 @@ "std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="], - "storybook": ["storybook@10.3.6", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3", "vite-plus": "^0.1.15" }, "optionalPeers": ["prettier", "vite-plus"], "bin": "./dist/bin/dispatcher.js" }, "sha512-vbSz7g/1rGMC1uAULqMZjALkIuLu2QABqfhRYhyr/11kzyesi+vAmwyJLukZP1FfecxGOgMwOh6GS0YsGpHAvQ=="], - - "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], - "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "svelte": ["svelte@5.55.5", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.4", "esm-env": "^1.2.1", "esrap": "^2.2.4", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-2uCs/LZ9us+AktdzYJM8OcxQ8qnPS1kpaO7syGT/MgO+6Qr1Ybl+TqPq+97u7PHqmmMlye5ZkoyXONy5mjjAbw=="], - "svelte-ast-print": ["svelte-ast-print@0.4.2", "", { "dependencies": { "esrap": "1.2.2", "zimmerframe": "1.1.2" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-hRHHufbJoArFmDYQKCpCvc0xUuIEfwYksvyLYEQyH+1xb5LD5sM/IthfooCdXZQtOIqXz6xm7NmaqdfwG4kh6w=="], - "svelte-check": ["svelte-check@4.4.8", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w=="], "svelte-eslint-parser": ["svelte-eslint-parser@1.6.1", "", { "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", "postcss-selector-parser": "^7.0.0", "semver": "^7.7.2" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-hhvSH6kRj46UzrBVO5TaotD+Iuvruj5ccKBcO4wAhVcPTLmIc/c32D8UllBTYO0on4LzYuM0rNzf1lM/gBlkSQ=="], @@ -873,8 +737,6 @@ "term-size": ["term-size@2.2.1", "", {}, "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg=="], - "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], - "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], "tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], @@ -883,22 +745,16 @@ "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], - "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], - "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="], - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - "type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], - "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], "typescript-eslint": ["typescript-eslint@8.59.3", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.3", "@typescript-eslint/parser": "8.59.3", "@typescript-eslint/typescript-estree": "8.59.3", "@typescript-eslint/utils": "8.59.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg=="], @@ -915,12 +771,8 @@ "universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], - "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "vfile-message": ["vfile-message@2.0.4", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-stringify-position": "^2.0.0" } }, "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ=="], @@ -933,8 +785,6 @@ "vitest-browser-svelte": ["vitest-browser-svelte@2.1.1", "", { "dependencies": { "@testing-library/svelte-core": "^1.0.0" }, "peerDependencies": { "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0", "vitest": "^4.0.0" } }, "sha512-qbunYRSm+N92r9bfTkdDTpBZESLmp4QFz2SluV3n/x8U7ysosfeXYJZ4vXbJ0Y0LzoqqDnV5LHprmFgn4Eo+Ug=="], - "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], @@ -943,16 +793,12 @@ "ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], - "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], - "yaml": ["yaml@1.10.3", "", {}, "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="], - "@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "@changesets/apply-release-plan/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="], "@changesets/write/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="], @@ -969,10 +815,6 @@ "@manypkg/get-packages/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], - "@size-limit/esbuild/esbuild": ["esbuild@0.28.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.0", "@esbuild/android-arm": "0.28.0", "@esbuild/android-arm64": "0.28.0", "@esbuild/android-x64": "0.28.0", "@esbuild/darwin-arm64": "0.28.0", "@esbuild/darwin-x64": "0.28.0", "@esbuild/freebsd-arm64": "0.28.0", "@esbuild/freebsd-x64": "0.28.0", "@esbuild/linux-arm": "0.28.0", "@esbuild/linux-arm64": "0.28.0", "@esbuild/linux-ia32": "0.28.0", "@esbuild/linux-loong64": "0.28.0", "@esbuild/linux-mips64el": "0.28.0", "@esbuild/linux-ppc64": "0.28.0", "@esbuild/linux-riscv64": "0.28.0", "@esbuild/linux-s390x": "0.28.0", "@esbuild/linux-x64": "0.28.0", "@esbuild/netbsd-arm64": "0.28.0", "@esbuild/netbsd-x64": "0.28.0", "@esbuild/openbsd-arm64": "0.28.0", "@esbuild/openbsd-x64": "0.28.0", "@esbuild/openharmony-arm64": "0.28.0", "@esbuild/sunos-x64": "0.28.0", "@esbuild/win32-arm64": "0.28.0", "@esbuild/win32-ia32": "0.28.0", "@esbuild/win32-x64": "0.28.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw=="], - - "@storybook/svelte-vite/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], @@ -985,46 +827,22 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], - - "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - "@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], - - "@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], - - "@vitest/mocker/@vitest/spy": ["@vitest/spy@4.1.6", "", {}, "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg=="], - - "enquirer/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "eslint-plugin-svelte/globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - "fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], - - "jsonfile/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "postcss-load-config/lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="], - "pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "publint/package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], "read-yaml-file/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], - "svelte/esrap": ["esrap@2.2.8", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-MPweq2EvEGj8jwOI7Hgycw/QIHzqA1EbAM8lG7p+FBfZbZq/hQ6h3AMsqnu/djzisH1KVWNzbb7LSgIVtMlPSg=="], - - "svelte-ast-print/esrap": ["esrap@1.2.2", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", "@types/estree": "^1.0.1" } }, "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw=="], - - "svelte-ast-print/zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="], - "svelte-check/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "svelte-eslint-parser/eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], @@ -1035,78 +853,12 @@ "vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - "vitest/@vitest/expect": ["@vitest/expect@4.1.6", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.6", "@vitest/utils": "4.1.6", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg=="], - - "vitest/@vitest/spy": ["@vitest/spy@4.1.6", "", {}, "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg=="], - "@manypkg/find-root/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], - "@manypkg/find-root/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], - - "@manypkg/get-packages/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], - - "@size-limit/esbuild/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA=="], - - "@size-limit/esbuild/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.0", "", { "os": "android", "cpu": "arm" }, "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ=="], - - "@size-limit/esbuild/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.0", "", { "os": "android", "cpu": "arm64" }, "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw=="], - - "@size-limit/esbuild/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.0", "", { "os": "android", "cpu": "x64" }, "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA=="], - - "@size-limit/esbuild/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q=="], - - "@size-limit/esbuild/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ=="], - - "@size-limit/esbuild/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q=="], - - "@size-limit/esbuild/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw=="], - - "@size-limit/esbuild/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw=="], - - "@size-limit/esbuild/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A=="], - - "@size-limit/esbuild/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ=="], - - "@size-limit/esbuild/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg=="], - - "@size-limit/esbuild/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w=="], - - "@size-limit/esbuild/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg=="], - - "@size-limit/esbuild/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ=="], - - "@size-limit/esbuild/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q=="], - - "@size-limit/esbuild/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.0", "", { "os": "linux", "cpu": "x64" }, "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ=="], - - "@size-limit/esbuild/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw=="], - - "@size-limit/esbuild/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.0", "", { "os": "none", "cpu": "x64" }, "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw=="], - - "@size-limit/esbuild/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g=="], - - "@size-limit/esbuild/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA=="], - - "@size-limit/esbuild/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w=="], - - "@size-limit/esbuild/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw=="], - - "@size-limit/esbuild/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA=="], - - "@size-limit/esbuild/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA=="], - - "@size-limit/esbuild/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw=="], - - "@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], - - "enquirer/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "read-yaml-file/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "svelte-check/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - "vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], - "@manypkg/find-root/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "@manypkg/find-root/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], diff --git a/src/lib/animate/core/animate.ts b/src/lib/animate/core/animate.ts index a0c2a55..46eaf15 100644 --- a/src/lib/animate/core/animate.ts +++ b/src/lib/animate/core/animate.ts @@ -22,12 +22,21 @@ import { createController, noopController } from './controller'; import { buildKeyframes, type KeyframeGroup } from '../keyframes/keyframes'; import { normalizeInput } from '../keyframes/normalize'; -import { deregisterTransformAnimation, registerTransformAnimation } from '../properties/properties'; +import { + demoteFoldedTransforms, + deregisterTransformAnimation, + hasActiveTransforms, + registerFoldedTransforms, + registerTransformAnimation, + type FoldedTransform +} from '../properties/properties'; import { ensurePropertiesRegistered, ensureTransformWired, + isTransformOwned, wireTransform } from '../properties/transform-setup'; +import { applyFolds, planFolds } from '../keyframes/fold'; import type { AnimateDefaults, AnimateProps, AnimationController, MotionElement } from '../types'; import { isBrowser, shouldReduceMotion } from '../../shared/browser'; import { formatValue, resolveProp } from '../properties/prop-utils'; @@ -60,23 +69,74 @@ export const animate = ( return noopController(element, defaults); } + // Read before registering: an element with no transform channel in flight is + // one whose transform we can drive directly for the length of this call. + const canFold = needsTransform && !hasActiveTransforms(element); + if (needsTransform) { ensureTransformWired(element); registerTransformAnimation(element, transformBits); } + const folds = canFold ? foldTransforms(element, groups) : []; + const animations = buildAnimations(element, groups, defaults); + if (folds.length > 0) { + registerFoldedTransforms( + element, + folds.map(({ groupIndex, varFrames, targets }) => ({ + animation: animations[groupIndex]!, + varFrames, + targets + })) + ); + } + return createController({ element, - animations: buildAnimations(element, groups, defaults), + animations, defaults, finalStyles, restorations, onTeardown: needsTransform - ? () => deregisterTransformAnimation(element, transformBits) + ? () => { + demoteFoldedTransforms(element); + deregisterTransformAnimation(element, transformBits); + } : undefined }); }; +type PendingFold = Omit & { groupIndex: number }; + +/** + * Collapse this call's transform-variable keyframes into direct + * `translate` / `scale` / `rotate` keyframes wherever possible, so Chromium can + * run them on the compositor instead of recalculating style every frame. + * Mutates `groups`, and returns what the tracker needs to undo the fold. + */ +const foldTransforms = (element: MotionElement, groups: KeyframeGroup[]): PendingFold[] => { + const computed = window.getComputedStyle(element); + const plans = planFolds( + groups, + (name) => computed.getPropertyValue(name).trim(), + // An unset inline value means the template never took (no CSS typed-OM + // support for these properties), leaving nothing to fold onto. + (target) => isTransformOwned(element, target) && !!element.style.getPropertyValue(target) + ); + if (plans.length === 0) return []; + const originals = applyFolds(groups, plans); + return [...originals].map(([groupIndex, keyframes]) => { + const { offset } = groups[groupIndex]!; + return { + groupIndex, + varFrames: (offset ? { ...keyframes, offset } : keyframes) as PropertyIndexedKeyframes, + targets: plans + .filter((plan) => plan.groupIndex === groupIndex) + .map((plan) => [plan.target, element.style.getPropertyValue(plan.target)] as const) + }; + }); +}; + /** * Materialize each keyframe group into a WAAPI `Animation`, applying the shared * effect options (fill / composite / iterations / direction / …) from `defaults`. diff --git a/src/lib/animate/core/controller.ts b/src/lib/animate/core/controller.ts index 5761ef4..078d0cf 100644 --- a/src/lib/animate/core/controller.ts +++ b/src/lib/animate/core/controller.ts @@ -4,6 +4,7 @@ */ import { isBrowser } from '../../shared/browser'; +import { demoteFoldedTransforms } from '../properties/properties'; import { playbackControls } from '../../shared/playback'; import type { AnimateDefaults, AnimationController, MotionElement } from '../types'; import type { CssWrite } from '../keyframes/keyframes'; @@ -188,7 +189,15 @@ export const createController = ({ }, cancel: teardown, stop: () => { - if (isBrowser()) commitComputedStyles(element, finalStyles); + if (isBrowser()) { + // `commitComputedStyles` reads the animated custom properties. While + // folded, those hold their pre-animation values and the live position + // lives on `translate`/`scale` instead, so hand the animation back to + // the variable path first — it keeps its current time, so the values + // read back are the ones on screen. + demoteFoldedTransforms(element); + commitComputedStyles(element, finalStyles); + } teardown(); }, ...playbackControls(forEachAnim) diff --git a/src/lib/animate/core/fold-equivalence.svelte.test.ts b/src/lib/animate/core/fold-equivalence.svelte.test.ts new file mode 100644 index 0000000..1f8d3e6 --- /dev/null +++ b/src/lib/animate/core/fold-equivalence.svelte.test.ts @@ -0,0 +1,73 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { animate } from './animate'; + +const nodes: HTMLElement[] = []; +const mount = (css = ''): HTMLElement => { + const el = document.createElement('div'); + el.style.cssText = `position:fixed;top:50px;left:50px;width:100px;height:80px;${css}`; + document.body.appendChild(el); + nodes.push(el); + return el; +}; +afterEach(() => { + for (const n of nodes.splice(0)) n.remove(); +}); + +const FLIP = { flipX: [120, 0], flipY: [40, 0], flipScaleX: [0.5, 1], flipScaleY: [0.25, 1] }; +const rect = (el: Element) => { + const r = el.getBoundingClientRect(); + return [r.left, r.top, r.width, r.height].map((n) => Math.round(n * 100) / 100); +}; + +describe('fold equivalence', () => { + it('renders the same frame as the variable path', () => { + for (const css of ['', 'transform-origin:0 0;', '--motion-x:15px;--motion-scale:1.5;']) { + const a = mount(css); + const b = mount(css); + const folded = animate(a, FLIP as never, { duration: 400, easing: (t: number) => t }); + const vars = animate(b, FLIP as never, { duration: 400, easing: (t: number) => t }); + // A second animation demotes b onto the variable path. + const nudge = animate(b, { rotate: [0, 0] }, { duration: 400 }); + for (const c of [folded, vars]) { + c.pause(); + c.seek(150); + } + expect(rect(a)).toEqual(rect(b)); + folded.cancel(); + vars.cancel(); + nudge.cancel(); + } + }); + + it('stop() freezes a folded animation where it is on screen', () => { + const el = mount(); + const c = animate(el, FLIP as never, { duration: 400, easing: (t: number) => t }); + c.pause(); + c.seek(100); + const before = rect(el); + c.stop(); + expect(rect(el)).toEqual(before); + expect(parseFloat(el.style.getPropertyValue('--flip-x'))).toBeCloseTo(90, 1); + }); + + it('lands on the end state and cleans up', async () => { + const el = mount(); + const resting = rect(el); + const c = animate(el, { ...FLIP, width: [50, 100] } as never, { duration: 60 }); + expect(c.animations).toHaveLength(2); + await c.finished; + expect(rect(el)).toEqual(resting); + expect(el.style.getPropertyValue('will-change')).toBe(''); + expect(el.getAnimations()).toHaveLength(0); + }); + + it('inline writes to a motion var show up mid-fold', async () => { + const el = mount(); + const c = animate(el, { flipX: [0, 0] }, { duration: 400 }); + const before = rect(el); + el.style.setProperty('--motion-x', '25px'); + await new Promise((r) => setTimeout(r, 0)); + expect(rect(el)[0]).toBeCloseTo(before[0]! + 25, 1); + c.cancel(); + }); +}); diff --git a/src/lib/animate/core/fold.svelte.test.ts b/src/lib/animate/core/fold.svelte.test.ts new file mode 100644 index 0000000..c696173 --- /dev/null +++ b/src/lib/animate/core/fold.svelte.test.ts @@ -0,0 +1,104 @@ +/** + * The transform fold, end to end in a real browser. Runs in the `client` + * project, where `style.translate` and registered custom properties actually + * exist — the unit tests in `keyframes/fold.test.ts` cover the substitution + * itself, this covers whether `animate()` engages it and gives it back. + * + * Nothing here asserts that an animation is composited: Chromium exposes no + * API for that. What is assertable is the shape the compositor requires — + * real transform properties in the effect and no custom properties beside + * them — plus the promise that the fold is invisible to everything else. + */ + +import { afterEach, describe, expect, it } from 'vitest'; +import { animate } from './animate'; +import { measureWithoutAncestorTransforms } from '../properties/properties'; + +let node: HTMLElement | null = null; + +const mount = (): HTMLElement => { + const el = document.createElement('div'); + el.style.cssText = 'position:fixed;top:50px;left:50px;width:100px;height:100px'; + document.body.appendChild(el); + node = el; + return el; +}; + +afterEach(() => { + node?.remove(); + node = null; +}); + +/** Keys WAAPI adds to every computed keyframe that are not animated properties. */ +const KEYFRAME_METADATA = new Set(['offset', 'computedOffset', 'easing', 'composite']); + +/** The property names an effect animates, as WAAPI reports them back. */ +const animatedProps = (animation: Animation): string[] => [ + ...new Set( + (animation.effect as KeyframeEffect) + .getKeyframes() + .flatMap((frame: Keyframe) => Object.keys(frame)) + .filter((key: string) => !KEYFRAME_METADATA.has(key)) + ) +]; + +describe('transform folding', () => { + it('drives real transform properties instead of custom properties', () => { + const el = mount(); + + const controller = animate( + el, + { + flipX: [120, 0], + flipY: [40, 0], + flipScaleX: [0.5, 1], + flipScaleY: [0.5, 1], + opacity: [0, 1] + }, + { duration: 400 } + ); + + const props = animatedProps(controller.animations[0]!); + expect(props).toContain('translate'); + expect(props).toContain('scale'); + expect(props.some((prop) => prop.startsWith('--'))).toBe(false); + controller.cancel(); + }); + + it('hands the element back to the variables when a second animation starts', () => { + const el = mount(); + const first = animate(el, { flipX: [120, 0] }, { duration: 400 }); + expect(animatedProps(first.animations[0]!)).toContain('translate'); + + // A sibling animation owns `--motion-x`; the fold would mask it. + const second = animate(el, { x: [0, 30] }, { duration: 400 }); + + expect(animatedProps(first.animations[0]!)).toEqual(['--flip-x']); + expect(first.animations[0]!.playState).toBe('running'); + first.cancel(); + second.cancel(); + }); + + it('still measures the resting box while folded', () => { + const el = mount(); + const resting = el.getBoundingClientRect(); + + const controller = animate(el, { flipX: [200, 0] }, { duration: 400 }); + + // The element is visibly displaced, but FLIP must still read its slot. + expect(measureWithoutAncestorTransforms(el).left).toBeCloseTo(resting.left, 1); + controller.cancel(); + }); + + it('leaves the transform alone when the caller set their own', () => { + const el = mount(); + el.style.translate = '10px 10px'; + + const controller = animate(el, { flipX: [120, 0] }, { duration: 400 }); + + // Folding here would animate a value we do not own. + expect(animatedProps(controller.animations[0]!)).toEqual(['--flip-x']); + expect(el.style.translate).toBe('10px 10px'); + controller.cancel(); + }); +}); diff --git a/src/lib/animate/keyframes/fold.test.ts b/src/lib/animate/keyframes/fold.test.ts new file mode 100644 index 0000000..851c58a --- /dev/null +++ b/src/lib/animate/keyframes/fold.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it } from 'vitest'; +import { applyFolds, planFolds } from './fold'; +import type { KeyframeGroup } from './keyframes'; + +const group = (keyframes: Record, duration = 400): KeyframeGroup => ({ + timing: { duration, easing: 'linear', delay: 0 }, + keyframes, + compositable: true +}); + +const ownAll = () => true; +const noVars = () => ''; + +describe('planFolds()', () => { + it('collapses a FLIP group into direct translate and scale keyframes', () => { + const groups = [ + group({ + '--flip-x': ['120px', '0px'], + '--flip-y': ['40px', '0px'], + '--flip-scale-x': ['0.5', '1'], + '--flip-scale-y': ['0.5', '1'], + opacity: ['0', '1'] + }) + ]; + + const plans = planFolds(groups, noVars, ownAll); + + expect(plans.map((plan) => plan.target)).toEqual(['translate', 'scale']); + const [translate, scale] = plans; + // Vars with no animation fall back to their template defaults, so the + // stop is a plain transform value with nothing left to resolve. + expect(translate!.stops[0]).toBe('calc(0px + 120px + 0px) calc(0px + 40px + 0px) 0px'); + expect(translate!.stops[1]).toBe('calc(0px + 0px + 0px) calc(0px + 0px + 0px) 0px'); + expect(scale!.stops[0]).toBe('calc(0.5 * 1 * 1) calc(0.5 * 1 * 1)'); + expect(translate!.varKeys).toEqual(['--flip-x', '--flip-y']); + }); + + it('substitutes the live value of vars this call does not animate', () => { + const groups = [group({ '--motion-x': ['0px', '50px'] })]; + + const [plan] = planFolds( + groups, + (name) => (name === '--motion-reorder-x' ? '12px' : ''), + ownAll + ); + + expect(plan!.stops[1]).toBe('calc(50px + 0px + 12px) calc(0px + 0px + 0px) 0px'); + }); + + it('leaves a target alone when its channels have different timings', () => { + const groups = [ + group({ '--motion-x': ['0px', '50px'] }, 400), + group({ '--motion-y': ['0px', '50px'] }, 900) + ]; + + expect(planFolds(groups, noVars, ownAll).map((plan) => plan.target)).toEqual([]); + }); + + it('leaves a target alone when the caller owns its transform', () => { + const groups = [group({ '--motion-x': ['0px', '50px'], '--motion-rotate': ['0deg', '90deg'] })]; + + const plans = planFolds(groups, noVars, (target) => target !== 'translate'); + + expect(plans.map((plan) => plan.target)).toEqual(['rotate']); + }); + + it('ignores targets with no animated channel', () => { + expect(planFolds([group({ opacity: ['0', '1'] })], noVars, ownAll)).toEqual([]); + }); +}); + +describe('applyFolds()', () => { + it('swaps the variable keys for the folded property and reports the originals', () => { + const groups = [group({ '--flip-x': ['10px', '0px'], opacity: ['0', '1'] })]; + const plans = planFolds(groups, noVars, ownAll); + + const originals = applyFolds(groups, plans); + + expect(Object.keys(groups[0]!.keyframes)).toEqual(['opacity', 'translate']); + expect(originals.get(0)).toEqual({ '--flip-x': ['10px', '0px'], opacity: ['0', '1'] }); + }); +}); diff --git a/src/lib/animate/keyframes/fold.ts b/src/lib/animate/keyframes/fold.ts new file mode 100644 index 0000000..8a3ea41 --- /dev/null +++ b/src/lib/animate/keyframes/fold.ts @@ -0,0 +1,141 @@ +/** + * Fold transform-variable keyframes into direct `translate` / `scale` / + * `rotate` keyframes. + * + * `animate()` drives transforms through registered custom properties so that + * independent animations compose on one element — FLIP offsets, a drag, and a + * sibling `animate()` each own their own var and never clobber a shared + * `transform`. Chromium cannot run a custom-property animation on the + * compositor, and one such property in an effect pins the whole effect + * (opacity included) to the main thread. A dialog morph therefore costs a style + * recalc and a full repaint every frame. + * + * When nothing else is composing on the element, the same motion can be + * expressed directly: evaluate the transform template once per keyframe stop, + * substituting each animated var with its stop value and every other var with + * its current computed value. The result is a plain transform keyframe the + * compositor accepts. The fold is reversible — the tracker demotes back to the + * variable form as soon as anything else touches the element — so the + * composition contract still holds. + */ + +import { TRANSFORM_TEMPLATES } from '../properties/properties'; +import type { KeyframeGroup } from './keyframes'; + +/** A CSS property whose value is composed from motion vars. */ +export type TransformTarget = keyof typeof TRANSFORM_TEMPLATES; + +export const TRANSFORM_TARGETS = Object.keys(TRANSFORM_TEMPLATES) as TransformTarget[]; + +/** A template is a run of literal text and `var()` references. */ +type TemplatePart = string | { name: string; fallback: string }; + +const VAR_RE = /var\(\s*(--[a-z0-9-]+)\s*(?:,\s*([^)]*))?\)/gi; + +const parseTemplate = (template: string): TemplatePart[] => { + const parts: TemplatePart[] = []; + let last = 0; + for (const match of template.matchAll(VAR_RE)) { + const at = match.index; + if (at > last) parts.push(template.slice(last, at)); + parts.push({ name: match[1]!, fallback: (match[2] ?? '').trim() }); + last = at + match[0].length; + } + if (last < template.length) parts.push(template.slice(last)); + return parts; +}; + +const TEMPLATE_PARTS = Object.fromEntries( + TRANSFORM_TARGETS.map((target) => [target, parseTemplate(TRANSFORM_TEMPLATES[target])]) +) as Record; + +/** One transform property that can be driven directly for this `animate()` call. */ +export interface FoldPlan { + target: TransformTarget; + /** Index into the groups array — the single group holding every animated var. */ + groupIndex: number; + /** Variable keyframe keys the folded keyframe replaces. */ + varKeys: string[]; + /** The substituted template, one entry per keyframe stop. */ + stops: string[]; +} + +/** + * Work out which transform properties can be driven directly. + * + * A target is foldable only when we installed its template (a caller's own + * `translate` stays authoritative), at least one of its vars is animated, and + * every animated var it reads sits in one timing group with the same number of + * stops — per-axis springs keep their independent timing on the variable path. + * + * Pure: reads the DOM only through `readVar`, and mutates nothing. + */ +export const planFolds = ( + groups: readonly KeyframeGroup[], + readVar: (name: string) => string, + isOwned: (target: TransformTarget) => boolean +): FoldPlan[] => { + const plans: FoldPlan[] = []; + for (const target of TRANSFORM_TARGETS) { + if (!isOwned(target)) continue; + const parts = TEMPLATE_PARTS[target]!; + const varKeys: string[] = []; + let groupIndex = -1; + let stopCount = 0; + let foldable = true; + for (const part of parts) { + if (typeof part === 'string') continue; + const index = groups.findIndex((group) => part.name in group.keyframes); + if (index === -1) continue; + const stops = groups[index]!.keyframes[part.name]!; + if (groupIndex === -1) { + groupIndex = index; + stopCount = stops.length; + } else if (index !== groupIndex || stops.length !== stopCount) { + // Channels of one target on different timings cannot collapse into + // a single keyframe list. + foldable = false; + break; + } + varKeys.push(part.name); + } + if (!foldable || groupIndex === -1) continue; + + const { keyframes } = groups[groupIndex]!; + const stops: string[] = []; + for (let stop = 0; stop < stopCount; stop++) { + let value = ''; + for (const part of parts) { + if (typeof part === 'string') { + value += part; + continue; + } + const animated = keyframes[part.name]; + value += animated ? animated[stop]! : readVar(part.name) || part.fallback; + } + stops.push(value); + } + plans.push({ target, groupIndex, varKeys, stops }); + } + return plans; +}; + +/** + * Rewrite the groups in place per plan, returning each touched group's original + * keyframes so the fold can be undone later. The variable keys are removed, not + * merely shadowed: leaving a custom property in the effect would pin it to the + * main thread again and defeat the whole exercise. + */ +export const applyFolds = ( + groups: KeyframeGroup[], + plans: readonly FoldPlan[] +): Map> => { + const originals = new Map>(); + for (const plan of plans) { + const group = groups[plan.groupIndex]!; + if (!originals.has(plan.groupIndex)) originals.set(plan.groupIndex, { ...group.keyframes }); + for (const key of plan.varKeys) delete group.keyframes[key]; + group.keyframes[plan.target] = plan.stops; + } + return originals; +}; diff --git a/src/lib/animate/keyframes/keyframes.test.ts b/src/lib/animate/keyframes/keyframes.test.ts index 8bb35bd..c1f1dd9 100644 --- a/src/lib/animate/keyframes/keyframes.test.ts +++ b/src/lib/animate/keyframes/keyframes.test.ts @@ -142,3 +142,43 @@ describe('buildKeyframes() — multi-stop sequences', () => { expect(groups[0]!.offset).toBeUndefined(); }); }); + +describe('buildKeyframes() — compositability grouping', () => { + it('keeps a layout-animating prop out of the compositable group', () => { + const { groups } = buildKeyframes( + el, + { opacity: ['0', '1'], width: ['0px', '100px'] }, + { duration: 300 } + ); + + // One effect per class: a `width` in the same effect would pin the + // opacity to the main thread alongside it. + expect(groups).toHaveLength(2); + expect(groups.find((group) => group.compositable)!.keyframes).toEqual({ + opacity: ['0', '1'] + }); + expect(groups.find((group) => !group.compositable)!.keyframes).toEqual({ + width: ['0px', '100px'] + }); + }); + + it('still merges props that are compositable together', () => { + const { groups } = buildKeyframes( + el, + { opacity: ['0', '1'], x: ['0px', '100px'], filter: ['none', 'blur(2px)'] }, + { duration: 300 } + ); + + expect(groups).toHaveLength(1); + }); + + it('groups several layout props together rather than one effect each', () => { + const { groups } = buildKeyframes( + el, + { width: ['0px', '10px'], height: ['0px', '10px'] }, + { duration: 300 } + ); + + expect(groups).toHaveLength(1); + }); +}); diff --git a/src/lib/animate/keyframes/keyframes.ts b/src/lib/animate/keyframes/keyframes.ts index 11b4ff6..499dccc 100644 --- a/src/lib/animate/keyframes/keyframes.ts +++ b/src/lib/animate/keyframes/keyframes.ts @@ -10,6 +10,7 @@ import { isBrowser } from '../../shared/browser'; import { isAutoKeyword, measureKeywordValue } from './keyword'; import { formatValue, + isCompositable, readCurrentValue, resolveProp, toKeyframeKey @@ -18,6 +19,13 @@ import { export interface KeyframeGroup { timing: { duration: number; easing: string; delay: number }; keyframes: Record; + /** + * Whether every prop in this group can be composited. Props are never merged + * across this boundary, so the compositable half of a mixed `animate()` call + * keeps its own effect instead of being pinned to the main thread by a + * sibling that animates layout. + */ + compositable: boolean; /** * Explicit keyframe offsets (0–1) for a multi-stop sequence. Present only * for groups carrying a single offset-bearing prop, so it never conflicts @@ -84,12 +92,14 @@ const resolveStop = ( * to the single prop that requested it. */ const findGroup = ( groups: KeyframeGroup[], - timing: KeyframeGroup['timing'] + timing: KeyframeGroup['timing'], + compositable: boolean ): KeyframeGroup | undefined => { for (const group of groups) { const t = group.timing; if ( group.offset === undefined && + group.compositable === compositable && t.duration === timing.duration && t.delay === timing.delay && t.easing === timing.easing @@ -146,10 +156,12 @@ export const buildKeyframes = ( } finalStyles.push({ css: def.css, value: values[values.length - 1]! }); - // Offset-bearing props get a private group; everything else merges by timing. - let group = offset ? undefined : findGroup(groups, timing); + // Offset-bearing props get a private group; everything else merges by + // timing, within its compositability class. + const compositable = isCompositable(def); + let group = offset ? undefined : findGroup(groups, timing, compositable); if (!group) { - group = { timing, keyframes: {}, offset }; + group = { timing, keyframes: {}, offset, compositable }; groups.push(group); } // WAAPI keyframes are keyed by camelCased IDL names; hyphenated multi-word diff --git a/src/lib/animate/properties/prop-utils.ts b/src/lib/animate/properties/prop-utils.ts index 798dc76..a8ab118 100644 --- a/src/lib/animate/properties/prop-utils.ts +++ b/src/lib/animate/properties/prop-utils.ts @@ -28,6 +28,22 @@ export const formatValue = (value: AnimatableValue, def: PropDef): string => export const toKeyframeKey = (css: string): string => css.startsWith('--') ? css : css.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase()); +/** + * Properties Chromium can run on the compositor. Transform components qualify + * through `def.transform`, so only the non-transform ones are listed here. + */ +const COMPOSITABLE_CSS = new Set(['opacity', 'filter', 'backdrop-filter']); + +/** + * Whether an animation of this property can run off the main thread. + * + * Compositing is decided per effect, not per property: one non-compositable + * property pins everything animating alongside it, so a `width` would drag a + * sibling `opacity` down with it. Grouping keeps the two apart. + */ +export const isCompositable = (def: PropDef): boolean => + def.transform === true || COMPOSITABLE_CSS.has(def.css); + /** Cache passthrough PropDefs for unknown keys to avoid repeat regex/string work. */ const UNKNOWN_PROP_CACHE = new Map(); diff --git a/src/lib/animate/properties/properties.ts b/src/lib/animate/properties/properties.ts index 739f866..45053f4 100644 --- a/src/lib/animate/properties/properties.ts +++ b/src/lib/animate/properties/properties.ts @@ -208,5 +208,10 @@ export { VAR_BIT, registerTransformAnimation, deregisterTransformAnimation, - measureWithoutAncestorTransforms + registerFoldedTransforms, + hintTransformLayer, + demoteFoldedTransforms, + hasActiveTransforms, + measureWithoutAncestorTransforms, + type FoldedTransform } from './transform-tracker'; diff --git a/src/lib/animate/properties/transform-setup.ts b/src/lib/animate/properties/transform-setup.ts index e752285..fd3b24d 100644 --- a/src/lib/animate/properties/transform-setup.ts +++ b/src/lib/animate/properties/transform-setup.ts @@ -37,7 +37,13 @@ export const ensurePropertiesRegistered = (): void => { } }; -const ELEMENTS_WITH_TRANSFORM = new WeakSet(); +/** + * Which transform properties we installed a template into, per element. A + * caller's own `translate` is left alone at wiring time and must also never be + * folded into (see `keyframes/fold`) — we would be animating a value we do not + * own. + */ +const ELEMENTS_WITH_TRANSFORM = new WeakMap>(); /** * Make sure the element's `translate` / `scale` / `rotate` styles are wired @@ -45,13 +51,22 @@ const ELEMENTS_WITH_TRANSFORM = new WeakSet(); */ export const ensureTransformWired = (element: MotionElement): void => { if (ELEMENTS_WITH_TRANSFORM.has(element)) return; - ELEMENTS_WITH_TRANSFORM.add(element); const { style } = element; - style.translate ||= TRANSFORM_TEMPLATES.translate; - style.scale ||= TRANSFORM_TEMPLATES.scale; - style.rotate ||= TRANSFORM_TEMPLATES.rotate; + const owned: Record = { + translate: !style.translate, + scale: !style.scale, + rotate: !style.rotate + }; + ELEMENTS_WITH_TRANSFORM.set(element, owned); + if (owned.translate) style.translate = TRANSFORM_TEMPLATES.translate; + if (owned.scale) style.scale = TRANSFORM_TEMPLATES.scale; + if (owned.rotate) style.rotate = TRANSFORM_TEMPLATES.rotate; }; +/** True when the template on this transform property is ours to animate. */ +export const isTransformOwned = (element: Element, target: string): boolean => + ELEMENTS_WITH_TRANSFORM.get(element)?.[target] === true; + /** * Both halves of the setup an element needs before anything writes `--motion-*` * to it: global property registration plus this element's transform chain. diff --git a/src/lib/animate/properties/transform-tracker.svelte.test.ts b/src/lib/animate/properties/transform-tracker.svelte.test.ts index d4dec42..130798d 100644 --- a/src/lib/animate/properties/transform-tracker.svelte.test.ts +++ b/src/lib/animate/properties/transform-tracker.svelte.test.ts @@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { deregisterTransformAnimation, measureWithoutAncestorTransforms, + registerFoldedTransforms, registerTransformAnimation, VAR_BIT } from './transform-tracker'; @@ -44,3 +45,76 @@ describe('measureWithoutAncestorTransforms()', () => { expect(observed[1]).toEqual(['20px', '']); }); }); + +describe('will-change hint', () => { + it('is set for the first channel, held across overlap, and restored after the last', () => { + const element = document.createElement('div'); + document.body.appendChild(element); + mounted.push(element); + element.style.setProperty('will-change', 'opacity'); + const x = VAR_BIT['--motion-x']!; + const y = VAR_BIT['--motion-y']!; + + registerTransformAnimation(element, x); + expect(element.style.getPropertyValue('will-change')).toBe('translate, scale, rotate'); + + registerTransformAnimation(element, y); + deregisterTransformAnimation(element, x); + expect(element.style.getPropertyValue('will-change')).toBe('translate, scale, rotate'); + + deregisterTransformAnimation(element, y); + expect(element.style.getPropertyValue('will-change')).toBe('opacity'); + }); +}); + +describe('folded transforms', () => { + const fold = (element: HTMLElement) => { + const animation = { effect: { setKeyframes: vi.fn() } } as unknown as Animation; + registerFoldedTransforms(element, [ + { + animation, + varFrames: { '--flip-x': ['10px', '0px'] }, + targets: [['translate', 'calc(var(--flip-x, 0px)) 0px 0px']] + } + ]); + return animation; + }; + + it('reinstates the variable keyframes when another transform animation starts', () => { + const element = document.createElement('div'); + document.body.appendChild(element); + mounted.push(element); + registerTransformAnimation(element, VAR_BIT['--flip-x']!); + const animation = fold(element); + + registerTransformAnimation(element, VAR_BIT['--motion-x']!); + + expect((animation.effect as KeyframeEffect).setKeyframes).toHaveBeenCalledWith({ + '--flip-x': ['10px', '0px'] + }); + }); + + it('measures at rest by reinstating the template the fold overrides', () => { + const parent = document.createElement('div'); + const child = document.createElement('div'); + parent.appendChild(child); + document.body.appendChild(parent); + mounted.push(parent); + parent.style.setProperty('--flip-x', '10px'); + registerTransformAnimation(parent, VAR_BIT['--flip-x']!); + fold(parent); + let observed: [string, string] | undefined; + vi.spyOn(child, 'getBoundingClientRect').mockImplementation(() => { + observed = [ + parent.style.getPropertyValue('translate'), + parent.style.getPropertyPriority('translate') + ]; + return new DOMRect(); + }); + + measureWithoutAncestorTransforms(child); + + expect(observed).toEqual(['calc(var(--flip-x, 0px)) 0px 0px', 'important']); + expect(parent.style.getPropertyValue('translate')).toBe(''); + }); +}); diff --git a/src/lib/animate/properties/transform-tracker.ts b/src/lib/animate/properties/transform-tracker.ts index 0cb3074..1dcd865 100644 --- a/src/lib/animate/properties/transform-tracker.ts +++ b/src/lib/animate/properties/transform-tracker.ts @@ -6,6 +6,13 @@ * has a running `animate()` transform animation, so `getBoundingClientRect()` * returns the element's "at rest" layout position even mid-animation. * + * The tracker also owns the two things that keep a transform animation off the + * main thread: the `will-change` hint that gets the element its own layer, and + * the registry of *folded* animations — ones that drive `translate` / `scale` / + * `rotate` directly instead of the motion vars (see `keyframes/fold`). A fold + * is only valid while nothing else touches the element's transform, so this + * module demotes it back to the variable path the moment that stops holding. + * * The tracker is intentionally separate from the property registry so the * static registry data and the dynamic runtime state live in different modules. */ @@ -58,12 +65,90 @@ const forEachBit = (bits: number, fn: (i: number) => void): void => { } }; +/** + * Can we write inline styles to this node? Duck-typed rather than an + * `instanceof HTMLElement` check: the ref-counting entry points also run under + * SSR, where those globals do not exist, and the capability is what we actually + * need — it also holds for elements from another realm, such as an iframe. + */ +const isMutableElement = (n: Element): n is MotionElement => + typeof (n as Partial).style?.setProperty === 'function'; + +/** True while the element has at least one `animate()` transform channel running. */ +export const hasActiveTransforms = (element: Element): boolean => + (activeTransformBits.get(element) ?? 0) !== 0; + +const WILL_CHANGE = 'will-change'; +/** + * Hint the three properties the motion templates drive. Chromium promotes an + * element for an active *transform* animation, but a custom-property animation + * earns no layer, so without this the element repaints every frame — shadows, + * borders and all. The element already has non-`none` `translate`/`scale` once + * wired, so it is a stacking context either way and the hint changes nothing + * observable. + */ +const WILL_CHANGE_VALUE = 'translate, scale, rotate'; +const savedWillChange = new WeakMap(); +/** + * Ref-count for the hint itself, separate from the per-channel transform + * counts: a `animate()` call and a drag can both want the layer at once, and + * whichever finishes first must not take it away from the other. + */ +const layerHintCounts = new WeakMap(); + +const retainLayerHint = (element: Element): void => { + const count = layerHintCounts.get(element) ?? 0; + layerHintCounts.set(element, count + 1); + if (count > 0 || !isMutableElement(element)) return; + savedWillChange.set(element, saveStyleProp(element.style, WILL_CHANGE)); + element.style.setProperty(WILL_CHANGE, WILL_CHANGE_VALUE); +}; + +const releaseLayerHint = (element: Element): void => { + const count = layerHintCounts.get(element) ?? 0; + if (count === 0) return; + if (count > 1) { + layerHintCounts.set(element, count - 1); + return; + } + layerHintCounts.delete(element); + const saved = savedWillChange.get(element); + if (!saved || !isMutableElement(element)) return; + savedWillChange.delete(element); + restoreStyleProp(element.style, WILL_CHANGE, saved); +}; + +/** + * Hold a compositor layer for an element whose `--motion-*` values are being + * written directly, which is what every gesture does: they drive the vars by + * hand rather than through `animate()`, so nothing else would hint them and a + * dragged card with a shadow repaints on every pointer event. + * + * Scope the call to the active gesture, not to attachment setup — a permanent + * hint is a permanent layer, and a long list would hold one per row. The + * returned release is idempotent, so a gesture that ends twice is safe. + */ +export const hintTransformLayer = (element: Element): (() => void) => { + retainLayerHint(element); + let released = false; + return () => { + if (released) return; + released = true; + releaseLayerHint(element); + }; +}; + /** Mark that an element has started a WAAPI transform animation. */ export const registerTransformAnimation = (element: Element, bits: number): void => { + // A fold owns `translate`/`scale`/`rotate` outright, which would mask the + // variable channel the incoming animation is about to drive. Hand the + // element back to the vars before it starts. + demoteFoldedTransforms(element); let counts = activeTransformCounts.get(element); if (!counts) { counts = new Uint8Array(N_TRANSFORM_VARS); activeTransformCounts.set(element, counts); + retainLayerHint(element); } let activeBits = activeTransformBits.get(element) ?? 0; forEachBit(bits, (i) => { @@ -83,11 +168,94 @@ export const deregisterTransformAnimation = (element: Element, bits: number): vo if (activeBits === 0) { activeTransformCounts.delete(element); activeTransformBits.delete(element); + releaseLayerHint(element); } else { activeTransformBits.set(element, activeBits); } }; +/** + * One WAAPI animation that has been folded onto a real transform property. + */ +export interface FoldedTransform { + animation: Animation; + /** The pre-fold, variable-driven keyframes, reinstated on demotion. */ + varFrames: PropertyIndexedKeyframes; + /** + * Transform properties this animation drives directly, each paired with the + * inline template it temporarily overrides. Measurement reinstates the + * template to read the element's resting box. + */ + targets: ReadonlyArray; +} + +const foldedTransforms = new WeakMap(); +const foldObservers = new WeakMap(); +const foldVarSnapshots = new WeakMap(); + +/** Inline (not computed) motion-var values — no style recalc, safe to poll. */ +const readInlineVars = (element: MotionElement): string[] => + MOTION_TRANSFORM_IDENTITIES.map(([name]) => element.style.getPropertyValue(name)); + +/** + * Take ownership of an element's folded animations. + * + * A fold bakes the element's *other* motion vars into its keyframes, so any + * inline write to one of them leaves the animation showing a stale value. A + * style-attribute observer watches for exactly that and demotes, which is why + * gestures, presets and consumer code can keep writing `--motion-x` without + * knowing folds exist. Other style mutations — `pointer-events`, `opacity`, a + * measurement's save/restore round-trip — leave the baked values correct and + * are ignored. + */ +export const registerFoldedTransforms = ( + element: MotionElement, + folds: readonly FoldedTransform[] +): void => { + if (folds.length === 0) return; + foldedTransforms.set(element, [...folds]); + if (typeof MutationObserver === 'undefined') return; + const before = readInlineVars(element); + foldVarSnapshots.set(element, before); + const observer = new MutationObserver(() => { + const snapshot = foldVarSnapshots.get(element); + if (!snapshot) return; + const now = readInlineVars(element); + for (let i = 0; i < now.length; i++) { + if (now[i] !== snapshot[i]) { + demoteFoldedTransforms(element); + return; + } + } + }); + observer.observe(element, { attributes: true, attributeFilter: ['style'] }); + foldObservers.set(element, observer); +}; + +/** + * Put folded animations back on the variable path, in place. + * + * `setKeyframes` swaps an effect's values without touching its timing, so + * `currentTime`, playback state and the `finished` promise all survive and the + * animation carries on from the same visual position. Safe to call on an + * element that has no folds, and safe to call twice. + * + * ponytail: demotion is per element, not per transform property — a write to + * `--motion-x` also gives back a folded `rotate` that nothing touched. Split it + * per target if a trace ever shows that costing a frame. + */ +export const demoteFoldedTransforms = (element: Element): void => { + foldObservers.get(element)?.disconnect(); + foldObservers.delete(element); + foldVarSnapshots.delete(element); + const folds = foldedTransforms.get(element); + if (!folds) return; + foldedTransforms.delete(element); + for (const { animation, varFrames } of folds) { + (animation.effect as KeyframeEffect | null)?.setKeyframes(varFrames); + } +}; + /** * Like `element.getBoundingClientRect()` but walks up the ancestor chain * and temporarily suppresses the motion CSS vars on every ancestor that @@ -107,9 +275,6 @@ export const deregisterTransformAnimation = (element: Element, bits: number): vo type SavedProp = { name: string; saved: SavedStyleProp }; type Suppressed = { node: MotionElement; props: SavedProp[] }; -const isMutableElement = (n: Element): n is MotionElement => - n instanceof HTMLElement || n instanceof SVGElement; - export const measureWithoutAncestorTransforms = ( el: Element, { suppressSelf = true }: { suppressSelf?: boolean } = {} @@ -125,6 +290,17 @@ export const measureWithoutAncestorTransforms = ( props.push({ name, saved: saveStyleProp(target.style, name) }); target.style.setProperty(name, identity, 'important'); }); + // A folded animation drives `translate`/`scale`/`rotate` itself, so + // forcing the vars to identity no longer reaches it. Reinstating the + // template with `!important` does: the template reads the vars we just + // neutralised, so the element measures at rest exactly as on the + // variable path. + for (const { targets } of foldedTransforms.get(target) ?? []) { + for (const [name, template] of targets) { + props.push({ name, saved: saveStyleProp(target.style, name) }); + target.style.setProperty(name, template, 'important'); + } + } if (props.length > 0) suppressed.push({ node: target, props }); }; diff --git a/src/lib/gestures/draggable.svelte.test.ts b/src/lib/gestures/draggable.svelte.test.ts index eb715a4..b6fd008 100644 --- a/src/lib/gestures/draggable.svelte.test.ts +++ b/src/lib/gestures/draggable.svelte.test.ts @@ -69,4 +69,23 @@ describe('draggable()', () => { cleanup?.(); expect(el.style.touchAction).toBe('auto'); }); + + it('holds a compositor layer for the drag and releases it once settled', async () => { + const el = mount(); + el.style.setProperty('will-change', 'opacity'); + const cleanup = draggable({ snapToOrigin: true })(el); + + el.dispatchEvent(pointer('pointerdown', 0, 0)); + el.dispatchEvent(pointer('pointermove', 50, 30)); + expect(el.style.getPropertyValue('will-change')).toBe('translate, scale, rotate'); + + el.dispatchEvent(pointer('pointerup', 50, 30)); + // The flick keeps writing after the pointer is gone, so the layer outlives + // pointerup and is handed back only when the spring stops. + expect(el.style.getPropertyValue('will-change')).toBe('translate, scale, rotate'); + await expect + .poll(() => el.style.getPropertyValue('will-change'), { timeout: 3000 }) + .toBe('opacity'); + cleanup?.(); + }); }); diff --git a/src/lib/gestures/draggable.ts b/src/lib/gestures/draggable.ts index a6d1155..c8b54b5 100644 --- a/src/lib/gestures/draggable.ts +++ b/src/lib/gestures/draggable.ts @@ -19,6 +19,7 @@ import type { SpringOptions } from '../shared/types'; import { createSpringValue } from '../animate/spring-value'; import { capture, lockTouchAction, release } from './pointer-capture'; import { wireTransform } from '../animate/properties/transform-setup'; +import { hintTransformLayer } from '../animate/properties/properties'; import { applyConstraint, xBounds, yBounds, type DragConstraints } from './constraints'; export type DragAxis = 'x' | 'y' | 'both'; @@ -85,6 +86,14 @@ export const draggable = (options: DraggableOptions = {}): Attachment element.style.setProperty('--motion-y', `${v}px`)); let dragging = false; + // Held from pointerdown until the spring stops writing, which is after the + // release flick has settled — not at pointerup. + let dropLayer: (() => void) | null = null; + const releaseLayer = (): void => { + const drop = dropLayer; + dropLayer = null; + drop?.(); + }; let pointerId = -1; let startX = 0; let startY = 0; @@ -103,6 +112,7 @@ export const draggable = (options: DraggableOptions = {}): Attachment { if (dragging || event.button !== 0) return; dragging = true; + dropLayer ??= hintTransformLayer(element); pointerId = event.pointerId; capture(element, pointerId); // Resume from wherever the spring currently sits (interrupt-friendly). @@ -146,6 +156,10 @@ export const draggable = (options: DraggableOptions = {}): Attachment { + if (!dragging) releaseLayer(); + }); }; const unlisten = listen(element, { @@ -161,6 +175,7 @@ export const draggable = (options: DraggableOptions = {}): Attachment { expect(onMove).not.toHaveBeenCalled(); cleanup?.(); }); + + it('holds a compositor layer from pointerenter through the spring back', async () => { + const el = mount(); + const cleanup = moveable({ applyTransform: true })(el); + + el.dispatchEvent(pointer('pointerenter', 150, 100)); + expect(el.style.getPropertyValue('will-change')).toBe('translate, scale, rotate'); + + el.dispatchEvent(pointer('pointerleave', 150, 100)); + await expect.poll(() => el.style.getPropertyValue('will-change'), { timeout: 3000 }).toBe(''); + cleanup?.(); + }); }); diff --git a/src/lib/gestures/move.ts b/src/lib/gestures/move.ts index 9aee447..0de14e8 100644 --- a/src/lib/gestures/move.ts +++ b/src/lib/gestures/move.ts @@ -24,6 +24,7 @@ import type { MotionElement } from '../animate'; import type { SpringOptions } from '../shared/types'; import { createSpringValue } from '../animate/spring-value'; import { wireTransform } from '../animate/properties/transform-setup'; +import { hintTransformLayer } from '../animate/properties/properties'; export interface MoveInfo { /** Pointer position in client coordinates. */ @@ -81,10 +82,21 @@ export const moveable = (options: MoveableOptions = {}): Attachment element.style.setProperty('--motion-y', `${v}px`)); } + // Held while the pointer is over the element and through the spring-back + // after it leaves, which is still writing `--motion-x`. + let dropLayer: (() => void) | null = null; + const releaseLayer = (): void => { + const drop = dropLayer; + dropLayer = null; + drop?.(); + }; + let hovering = false; + const ignore = (event: PointerEvent): boolean => !includeTouch && event.pointerType === 'touch'; - const measure = (event: PointerEvent): MoveInfo => { - const rect = element.getBoundingClientRect(); + // The rect is passed in, not read here: `pull` needs the same one, and a + // spring write between two reads would force a second layout. + const measure = (event: PointerEvent, rect: DOMRect): MoveInfo => { const localX = event.clientX - rect.left; const localY = event.clientY - rect.top; return { @@ -97,28 +109,36 @@ export const moveable = (options: MoveableOptions = {}): Attachment { + const pull = (info: MoveInfo, rect: DOMRect): void => { // Magnetic offset is the pointer's distance from centre, scaled. Written // live with jump() (synchronous) — the pointer path is already smooth. - springX!.jump((info.nx * element.getBoundingClientRect().width * strength) / 2); - springY!.jump((info.ny * element.getBoundingClientRect().height * strength) / 2); + springX!.jump((info.nx * rect.width * strength) / 2); + springY!.jump((info.ny * rect.height * strength) / 2); }; const onEnter = (event: PointerEvent): void => { if (ignore(event)) return; - onMoveStart?.(measure(event), element); + hovering = true; + if (applyTransform) dropLayer ??= hintTransformLayer(element); + onMoveStart?.(measure(event, element.getBoundingClientRect()), element); }; const onPointerMove = (event: PointerEvent): void => { if (ignore(event)) return; - const info = measure(event); - if (applyTransform) pull(info); + const rect = element.getBoundingClientRect(); + const info = measure(event, rect); + if (applyTransform) pull(info, rect); onMove?.(info, element); }; const onLeave = (event: PointerEvent): void => { if (ignore(event)) return; + hovering = false; // Spring back to rest, carrying any momentum. springX?.set(0); springY?.set(0); + // Re-entering before the spring-back finishes keeps the layer. + void Promise.all([springX?.finished, springY?.finished]).then(() => { + if (!hovering) releaseLayer(); + }); onMoveEnd?.(element); }; @@ -130,6 +150,7 @@ export const moveable = (options: MoveableOptions = {}): Attachment { unlisten(); + releaseLayer(); unsubX(); unsubY(); springX?.stop(); diff --git a/src/lib/gestures/pinch.svelte.test.ts b/src/lib/gestures/pinch.svelte.test.ts index 6978f96..66affd4 100644 --- a/src/lib/gestures/pinch.svelte.test.ts +++ b/src/lib/gestures/pinch.svelte.test.ts @@ -105,4 +105,20 @@ describe('pinchable()', () => { expect(onMove).not.toHaveBeenCalled(); cleanup?.(); }); + + it('holds a compositor layer while both pointers are down', () => { + const el = mount(); + const cleanup = pinchable()(el); + + el.dispatchEvent(pointer('pointerdown', 1, 0, 0)); + expect(el.style.getPropertyValue('will-change')).toBe(''); + + // The gesture, and the layer, begin on the second pointer. + el.dispatchEvent(pointer('pointerdown', 2, 100, 0)); + expect(el.style.getPropertyValue('will-change')).toBe('translate, scale, rotate'); + + el.dispatchEvent(pointer('pointerup', 2, 100, 0)); + expect(el.style.getPropertyValue('will-change')).toBe(''); + cleanup?.(); + }); }); diff --git a/src/lib/gestures/pinch.ts b/src/lib/gestures/pinch.ts index 6a2ceb5..0d6dfcb 100644 --- a/src/lib/gestures/pinch.ts +++ b/src/lib/gestures/pinch.ts @@ -17,6 +17,7 @@ import { applyConstraint, type AxisBounds } from './constraints'; import type { MotionElement } from '../animate'; import { capture, lockTouchAction, release } from './pointer-capture'; import { wireTransform } from '../animate/properties/transform-setup'; +import { hintTransformLayer } from '../animate/properties/properties'; export interface PinchInfo { /** Distance ratio of the two pointers relative to gesture start. */ @@ -62,6 +63,14 @@ export const pinchable = (options: PinchableOptions = {}): Attachment(); let pinching = false; + // The scale/rotate writes are synchronous with the pointers, so the layer + // is wanted for exactly as long as two fingers are down. + let dropLayer: (() => void) | null = null; + const releaseLayer = (): void => { + const drop = dropLayer; + dropLayer = null; + drop?.(); + }; let startDistance = 0; let startAngle = 0; @@ -91,6 +100,7 @@ export const pinchable = (options: PinchableOptions = {}): Attachment { unlisten(); points.clear(); + releaseLayer(); unlockTouchAction(); }; }; diff --git a/src/lib/gestures/reorder.svelte.test.ts b/src/lib/gestures/reorder.svelte.test.ts index 521ab9e..a259ba1 100644 --- a/src/lib/gestures/reorder.svelte.test.ts +++ b/src/lib/gestures/reorder.svelte.test.ts @@ -193,4 +193,22 @@ describe('reorder()', () => { els[0]!.dispatchEvent(pointer('pointerup', 120)); expect(order).toEqual(['a', 'b', 'c']); }); + + it('holds a compositor layer on every row for the duration of a drag', () => { + const { values, els } = setup(); + let order = [...values]; + const r = reorder({ items: () => order, onReorder: (next) => (order = next) }); + els.forEach((el, i) => r.item(values[i]!)(el)); + + els[0]!.dispatchEvent(pointer('pointerdown', 20)); + // Siblings slide into the vacated slot, so they are hinted too. + expect(els.map((el) => el.style.getPropertyValue('will-change'))).toEqual([ + 'translate, scale, rotate', + 'translate, scale, rotate', + 'translate, scale, rotate' + ]); + + els[0]!.dispatchEvent(pointer('pointerup', 20)); + expect(els.map((el) => el.style.getPropertyValue('will-change'))).toEqual(['', '', '']); + }); }); diff --git a/src/lib/gestures/reorder.ts b/src/lib/gestures/reorder.ts index 3db5b66..e8f8b13 100644 --- a/src/lib/gestures/reorder.ts +++ b/src/lib/gestures/reorder.ts @@ -27,6 +27,7 @@ import { listen } from '../shared/listen'; import { snapshotRect, flipFrom } from '../flip'; import type { EasingFn } from '../shared/types'; import { wireTransform } from '../animate/properties/transform-setup'; +import { hintTransformLayer } from '../animate/properties/properties'; import { restoreStyleProp, saveStyleProp, type SavedStyleProp } from '../shared/inline-style'; import { capture, lockTouchAction, release } from './pointer-capture'; @@ -103,6 +104,8 @@ interface DragState { styles: Map; /** Detaches the move/up/cancel listeners this drag installed. */ unlisten: () => void; + /** Releases the compositor-layer hint held on every row for this drag. */ + dropLayers: Array<() => void>; } /** Create a reorder controller for one list. */ @@ -130,6 +133,9 @@ export const reorder = (options: ReorderOptions): ReorderHandle => { const restoreDrag = (state: DragState): void => { for (const node of state.els) restoreDragStyles(node, state.styles.get(node)!); + // The settle FLIP that follows goes through `animate()`, which takes its + // own hint, so there is no gap between the two. + for (const drop of state.dropLayers) drop(); }; /** Slide siblings into the dragged row's actual slot, including variable item sizes and gaps. */ @@ -224,7 +230,10 @@ export const reorder = (options: ReorderOptions): ReorderHandle => { pointermove: onMove, pointerup: endDrag, pointercancel: endDrag - }) + }), + // Every row moves during a drag: the dragged one follows the pointer, + // the rest slide into its vacated slot. + dropLayers: rows.map((node) => hintTransformLayer(node)) }; // Reorder owns only its dedicated motion channel and temporary drag diff --git a/src/lib/gestures/wheel.svelte.test.ts b/src/lib/gestures/wheel.svelte.test.ts index fbdfb2f..aa56bd5 100644 --- a/src/lib/gestures/wheel.svelte.test.ts +++ b/src/lib/gestures/wheel.svelte.test.ts @@ -91,4 +91,16 @@ describe('wheelable()', () => { expect(onMove).toHaveBeenCalledOnce(); cleanup?.(); }); + + it('holds a compositor layer until the wheel goes quiet', async () => { + const el = mount(); + const cleanup = wheelable({ speed: 0.01, endDelay: 20 })(el); + + el.dispatchEvent(wheel(-100)); + expect(el.style.getPropertyValue('will-change')).toBe('translate, scale, rotate'); + + // Released only after the idle timer fires and the smoothing spring rests. + await expect.poll(() => el.style.getPropertyValue('will-change'), { timeout: 3000 }).toBe(''); + cleanup?.(); + }); }); diff --git a/src/lib/gestures/wheel.ts b/src/lib/gestures/wheel.ts index 0a55df9..282f22f 100644 --- a/src/lib/gestures/wheel.ts +++ b/src/lib/gestures/wheel.ts @@ -25,6 +25,7 @@ import type { MotionElement } from '../animate'; import { createSpringValue } from '../animate/spring-value'; import type { SpringOptions } from '../shared/types'; import { wireTransform } from '../animate/properties/transform-setup'; +import { hintTransformLayer } from '../animate/properties/properties'; export interface WheelInfo { /** Accumulated scale since the element mounted (baseline 1). */ @@ -103,6 +104,15 @@ export const wheelable = (options: WheelableOptions = {}): Attachment void) | null = null; + const releaseLayer = (): void => { + const drop = dropLayer; + dropLayer = null; + drop?.(); + }; + let detachSpring: (() => void) | null = null; if (applyTransform) { wireTransform(element); @@ -120,6 +130,10 @@ export const wheelable = (options: WheelableOptions = {}): Attachment { endTimer = null; active = false; + // A notch arriving during the settle restarts the gesture and keeps it. + void Promise.resolve(springScale?.finished).then(() => { + if (!active) releaseLayer(); + }); onEnd?.(info(0, 0), element); }; @@ -133,6 +147,7 @@ export const wheelable = (options: WheelableOptions = {}): Attachment