diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 4fd14ce..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(gh.exe pr:*)", - "Bash(where gh:*)", - "WebFetch(domain:api.github.com)", - "WebFetch(domain:raw.githubusercontent.com)", - "Bash(pnpm install:*)", - "WebSearch", - "WebFetch(domain:github.com)" - ] - } -} diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index eda2dff..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:react-hooks/recommended"], - "plugins": ["react", "react-hooks"], - "parserOptions": { - "ecmaVersion": 2020, - "sourceType": "module", - "ecmaFeatures": { - "jsx": true - } - }, - "env": { - "browser": true, - "es2015": true - }, - "rules": { - "react/react-in-jsx-scope": "off" - } -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d562d71 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install --frozen-lockfile + - run: bun run typecheck + - run: bun run lint + - run: bun run build + + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + react: [18, 19] + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install --frozen-lockfile + - name: Pin React ${{ matrix.react }} + if: matrix.react == 18 + run: bun add --cwd packages/hamo -d react@18 react-dom@18 @types/react@18 @types/react-dom@18 + - run: bun run test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6ac3f33..1fdd7cb 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,29 +1,34 @@ -name: Publish to NPM +name: Publish to npm + on: release: types: [created] + +permissions: + contents: read + id-token: write # required for npm provenance + jobs: - build: + publish: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v3 + - uses: oven-sh/setup-bun@v2 with: - node-version: 16 - registry-url: 'https://registry.npmjs.org' + bun-version: latest - - name: Install pnpm - uses: pnpm/action-setup@v2 + - uses: actions/setup-node@v4 with: - version: 7 - run_install: false + node-version: 20 + registry-url: 'https://registry.npmjs.org' + + - run: bun install --frozen-lockfile + - run: bun run typecheck + - run: bun run lint + - run: bun run test + - run: bun run build - - name: Install dependencies and build - run: pnpm i --frozen-lockfile - - name: Publish package on NPM - run: npm publish + - run: cd packages/hamo && npm publish --provenance --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index c53b980..c59a838 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules .DS_Store dist bundled -bun.lock \ No newline at end of file +plans +.claude/settings.local.json \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 8e43305..0000000 --- a/README.md +++ /dev/null @@ -1,44 +0,0 @@ -[![HAMO](https://assets.darkroom.engineering/hamo/banner.gif)](https://github.com/darkroomengineering/hamo) - -## Introduction - -`hāmō` means hook in Latin, this package is a collection of custom performance-oriented React hooks. - -## Features - -- **Render-safe hooks** — a small, tree-shakeable set of performance-oriented React hooks -- **Measurement hooks** — useRect, useWindowSize, and useResizeObserver track element and viewport size without thrash -- **useIntersectionObserver** — fire when elements enter or leave the viewport -- **useLazyState** — update a value through a callback without triggering a re-render -- **Debounce hooks** — useDebouncedCallback, Effect, and State debounce from one primitive -- **useObjectFit** — compute object-fit scale from a parent's dimensions - -## Installation - -```bash -$ npm i hamo -``` - -## Hooks - -[`useRect`](./packages/react/src/use-rect/README.md) – tracks element position within the page - -[`useWindowSize`](./packages/react/src/use-window-size/README.md) – tracks window dimensions - -[`useResizeObserver`](./packages/react/src/use-resize-observer/README.md) – observes element dimensions using ResizeObserver - -[`useLazyState`](./packages/react/src/use-lazy-state/README.md) – runs a callback when the state changes without re-rendering the component - -[`useDebouncedCallback/useDebouncedEffect/useDebouncedState`](./packages/react/src/use-debounce/README.md) – debounces a callback, effect, or state - -[`useObjectFit`](./packages/react/src/use-object-fit/README.md) – calculates the x and y scale of an object based on its parent width and height - -[`useIntersectionObserver`](./packages/react/src/use-intersection-observer/README.md) – observes element intersection with the viewport - -## License - -MIT © [darkroom.engineering](https://github.com/darkroomengineering) - -## Shoutout - -Thank you to [Luca Gesmundo](https://github.com/lucagez) for having transfered us the npm package name 🙏. \ No newline at end of file diff --git a/biome.json b/biome.json deleted file mode 100644 index 2b4840e..0000000 --- a/biome.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "$schema": "node_modules/@biomejs/biome/configuration_schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false - }, - "files": { - "ignoreUnknown": false, - "ignore": [ - "node_modules", - "**/.next/**", - "**/dist/**", - "**/public/**", - ".github/**", - ".vercel/**", - ".husky/**", - "pnpm-lock.yaml", - "bun.lockb", - "**/*.md", - "**/*.mdx", - "**/public/*.js", - "**/.storybook/**" - ] - }, - "formatter": { - "enabled": true, - "indentStyle": "space", - "lineEnding": "lf", - "indentWidth": 2, - "lineWidth": 80 - }, - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "suspicious": { - "noExplicitAny": "warn" - }, - "correctness": { - "useExhaustiveDependencies": "warn" - }, - "a11y": { - "useKeyWithClickEvents": "warn", - "useValidAnchor": "warn" - }, - "style": { - "noNonNullAssertion": "off" - } - } - }, - "css": { - "linter": { - "enabled": true - }, - "formatter": { - "enabled": true, - "indentStyle": "space", - "indentWidth": 2 - }, - "parser": { - "cssModules": true - } - }, - "javascript": { - "formatter": { - "quoteStyle": "single", - "semicolons": "asNeeded", - "trailingCommas": "es5" - } - }, - "json": { - "parser": { - "allowComments": true - } - }, - "overrides": [ - { - "include": ["**/*.css"], - "linter": { - "rules": { - "correctness": { - "noUnknownFunction": "off" - } - } - } - } - ] -} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..1f9d19d --- /dev/null +++ b/bun.lock @@ -0,0 +1,1182 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "hamo", + }, + "packages/hamo": { + "name": "hamo", + "version": "1.0.0", + "devDependencies": { + "@biomejs/biome": "^2.2.0", + "@happy-dom/global-registrator": "^20.0.0", + "@testing-library/react": "^16.1.0", + "@types/react": "^19.0.0", + "happy-dom": "^20.0.0", + "lenis": "^1.3.19", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tsdown": "^0.21.4", + "typescript": "^5.4.5", + }, + "peerDependencies": { + "lenis": ">=1.3.0", + "react": ">=18.0.0", + }, + "optionalPeers": [ + "lenis", + ], + }, + "playground": { + "name": "playground", + "version": "0.0.1", + "dependencies": { + "@astrojs/check": "^0.9.9", + "@astrojs/react": "^5.0.7", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "astro": "^6.4.4", + "hamo": "workspace:*", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "typescript": "^6.0.3", + }, + }, + }, + "packages": { + "@astrojs/check": ["@astrojs/check@0.9.9", "", { "dependencies": { "@astrojs/language-server": "^2.16.7", "chokidar": "^4.0.3", "kleur": "^4.1.5", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": "^5.0.0 || ^6.0.0" }, "bin": { "astro-check": "bin/astro-check.js" } }, "sha512-A5UW8uIuErLWEoRQvzgXpO1gTjUFtK8r7nU2Z7GewAMxUb7bPvpk11qaKKgxqXlHJWlAvaaxy+Xg28A6bmQ1Tg=="], + + "@astrojs/compiler": ["@astrojs/compiler@4.0.0", "", {}, "sha512-eouss7G8ygdZqHuke033VMcVw5HTZUu+PXd/h06DGDUg/jt5btPYPqh66ENWw/mU78rBrf/oeC4oqoBwMtDMNA=="], + + "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.10.0", "", { "dependencies": { "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", "js-yaml": "^4.1.1", "picomatch": "^4.0.4", "retext-smartypants": "^6.2.0", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "unified": "^11.0.5" } }, "sha512-Ry2R3VPeIN4uPCSA4xQc+e+vsJXkalKpEbDc07hV+a/o5Bs2N/s/uDcPJH/05L19DKh9tAy7e6JM3YZ6Cxfezw=="], + + "@astrojs/language-server": ["@astrojs/language-server@2.16.10", "", { "dependencies": { "@astrojs/compiler": "^2.13.1", "@astrojs/yaml2ts": "^0.2.4", "@jridgewell/sourcemap-codec": "^1.5.5", "@volar/kit": "~2.4.28", "@volar/language-core": "~2.4.28", "@volar/language-server": "~2.4.28", "@volar/language-service": "~2.4.28", "muggle-string": "^0.4.1", "tinyglobby": "^0.2.16", "volar-service-css": "0.0.70", "volar-service-emmet": "0.0.70", "volar-service-html": "0.0.70", "volar-service-prettier": "0.0.70", "volar-service-typescript": "0.0.70", "volar-service-typescript-twoslash-queries": "0.0.70", "volar-service-yaml": "0.0.70", "vscode-html-languageservice": "^5.6.2", "vscode-uri": "^3.1.0" }, "peerDependencies": { "prettier": "^3.0.0", "prettier-plugin-astro": ">=0.11.0" }, "optionalPeers": ["prettier", "prettier-plugin-astro"], "bin": { "astro-ls": "./bin/nodeServer.js" } }, "sha512-87VQ/5GSdHlRnUA+hGuerYyIGAj+9RbZmATyuKLEUePinUXhQ5YkRnRrHhOD9sSi5JOErLjrLkHnfZFEvGrV8w=="], + + "@astrojs/markdown-remark": ["@astrojs/markdown-remark@7.2.0", "", { "dependencies": { "@astrojs/internal-helpers": "0.10.0", "@astrojs/prism": "4.0.2", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-+YxmVQu1Bd+MFfSzjq1rOJvD9+nIOJzz5YIIhdIH01RrxRkKbyKoEgyIqP3yv51MhzMDgd79QaPv+kCVPT8vHw=="], + + "@astrojs/prism": ["@astrojs/prism@4.0.2", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA=="], + + "@astrojs/react": ["@astrojs/react@5.0.7", "", { "dependencies": { "@astrojs/internal-helpers": "0.10.0", "@vitejs/plugin-react": "^5.2.0", "devalue": "^5.8.1", "ultrahtml": "^1.6.0", "vite": "^7.3.2" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, "sha512-N9cCoxvnLWaP+AK1Fv4e5Mc7ktnVTpSo2nWLwvD9Ohr1dJKygwrTSm9yatqoahgb1A5Kwjg/rT2shRiIVdn3aw=="], + + "@astrojs/telemetry": ["@astrojs/telemetry@3.3.2", "", { "dependencies": { "ci-info": "^4.4.0", "dset": "^3.1.4", "is-docker": "^4.0.0", "is-wsl": "^3.1.1", "which-pm-runs": "^1.1.0" } }, "sha512-j8DNruA8ors99Al39RYZPJK4DC1bKkoNm93mAMuBhY9TCNC4R8n1q7ovFnJ5qhGh5Lsh7pa1gpQVpYpsJPeTHQ=="], + + "@astrojs/yaml2ts": ["@astrojs/yaml2ts@0.2.4", "", { "dependencies": { "yaml": "^2.8.3" } }, "sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="], + + "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="], + + "@babel/generator": ["@babel/generator@8.0.0-rc.3", "", { "dependencies": { "@babel/parser": "^8.0.0-rc.3", "@babel/types": "^8.0.0-rc.3", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-em37/13/nR320G4jab/nIIHZgc2Wz2y/D39lxnTyxB4/D/omPQncl/lSdlnJY1OhQcRGugTSIF2l/69o31C9dA=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.6", "", {}, "sha512-BCkFy+zN6kXQed3YOT7aJl93NfDSzQc3pBfsvTVPs9gU9X3V0aefEF5kwBT0E+mDWH9QgKaZstYUQN9VdQZT4g=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0-rc.3", "", {}, "sha512-8AWCJ2VJJyDFlGBep5GpaaQ9AAaE/FjAcrqI7jyssYhtL7WGV0DOKpJsQqM037xDbpRLHXsY8TwU7zDma7coOw=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="], + + "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], + + "@babel/parser": ["@babel/parser@8.0.0-rc.3", "", { "dependencies": { "@babel/types": "^8.0.0-rc.3" }, "bin": "./bin/babel-parser.js" }, "sha512-B20dvP3MfNc/XS5KKCHy/oyWl5IA6Cn9YjXRdDlCjNmUFrjvLXMNUfQq/QUy9fnG2gYkKKcrto2YaF9B32ToOQ=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q=="], + + "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], + + "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], + + "@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="], + + "@babel/types": ["@babel/types@8.0.0-rc.3", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.3", "@babel/helper-validator-identifier": "^8.0.0-rc.3" } }, "sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q=="], + + "@biomejs/biome": ["@biomejs/biome@2.4.16", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.16", "@biomejs/cli-darwin-x64": "2.4.16", "@biomejs/cli-linux-arm64": "2.4.16", "@biomejs/cli-linux-arm64-musl": "2.4.16", "@biomejs/cli-linux-x64": "2.4.16", "@biomejs/cli-linux-x64-musl": "2.4.16", "@biomejs/cli-win32-arm64": "2.4.16", "@biomejs/cli-win32-x64": "2.4.16" }, "bin": { "biome": "bin/biome" } }, "sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.16", "", { "os": "linux", "cpu": "x64" }, "sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.16", "", { "os": "linux", "cpu": "x64" }, "sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.16", "", { "os": "win32", "cpu": "x64" }, "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw=="], + + "@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="], + + "@clack/core": ["@clack/core@1.4.1", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-FILJa1gGKEFTGZAJE9RpVhrjKz3c3h4ar60dSv6cGuDqufQ84YEIS3GAGvZiN+H6yaLbbvTFNejjCC4tXpZEuw=="], + + "@clack/prompts": ["@clack/prompts@1.5.1", "", { "dependencies": { "@clack/core": "1.4.1", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw=="], + + "@emmetio/abbreviation": ["@emmetio/abbreviation@2.3.3", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA=="], + + "@emmetio/css-abbreviation": ["@emmetio/css-abbreviation@2.1.8", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw=="], + + "@emmetio/css-parser": ["@emmetio/css-parser@0.4.1", "", { "dependencies": { "@emmetio/stream-reader": "^2.2.0", "@emmetio/stream-reader-utils": "^0.1.0" } }, "sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ=="], + + "@emmetio/html-matcher": ["@emmetio/html-matcher@1.3.0", "", { "dependencies": { "@emmetio/scanner": "^1.0.0" } }, "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ=="], + + "@emmetio/scanner": ["@emmetio/scanner@1.0.4", "", {}, "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA=="], + + "@emmetio/stream-reader": ["@emmetio/stream-reader@2.2.0", "", {}, "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw=="], + + "@emmetio/stream-reader-utils": ["@emmetio/stream-reader-utils@0.1.0", "", {}, "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A=="], + + "@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/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + + "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.10.2", "", { "dependencies": { "@types/node": ">=20.0.0", "happy-dom": "^20.10.2" } }, "sha512-/DC0hluanNJDVPUu69cidD46sGwzt8MJATiGx7WgCScn+ZH48fJQ0fvTfMPXY82/ASXWxnNo8P4BdHyU/dI/EA=="], + + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], + + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], + + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@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=="], + + "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="], + + "@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="], + + "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.17", "", { "os": "android", "cpu": "arm64" }, "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm" }, "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "s390x" }, "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.17", "", { "os": "none", "cpu": "arm64" }, "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.17", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "x64" }, "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.17", "", {}, "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg=="], + + "@rollup/pluginutils": ["@rollup/pluginutils@5.4.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.61.1", "", { "os": "android", "cpu": "arm" }, "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.61.1", "", { "os": "android", "cpu": "arm64" }, "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.61.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.61.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.61.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.61.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.61.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.61.1", "", { "os": "linux", "cpu": "arm" }, "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.61.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.61.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.61.1", "", { "os": "linux", "cpu": "none" }, "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.61.1", "", { "os": "linux", "cpu": "none" }, "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.61.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.61.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.61.1", "", { "os": "linux", "cpu": "none" }, "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.61.1", "", { "os": "linux", "cpu": "none" }, "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.61.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.61.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.61.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.61.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.61.1", "", { "os": "none", "cpu": "arm64" }, "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.61.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.61.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.61.1", "", { "os": "win32", "cpu": "x64" }, "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.61.1", "", { "os": "win32", "cpu": "x64" }, "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw=="], + + "@shikijs/core": ["@shikijs/core@4.2.0", "", { "dependencies": { "@shikijs/primitive": "4.2.0", "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g=="], + + "@shikijs/langs": ["@shikijs/langs@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0" } }, "sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ=="], + + "@shikijs/primitive": ["@shikijs/primitive@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA=="], + + "@shikijs/themes": ["@shikijs/themes@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0" } }, "sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w=="], + + "@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + + "@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/react": ["@testing-library/react@16.3.2", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g=="], + + "@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/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + + "@types/jsesc": ["@types/jsesc@2.5.1", "", {}, "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="], + + "@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], + + "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.1", "", {}, "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="], + + "@volar/kit": ["@volar/kit@2.4.28", "", { "dependencies": { "@volar/language-service": "2.4.28", "@volar/typescript": "2.4.28", "typesafe-path": "^0.2.2", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "typescript": "*" } }, "sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg=="], + + "@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="], + + "@volar/language-server": ["@volar/language-server@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "@volar/language-service": "2.4.28", "@volar/typescript": "2.4.28", "path-browserify": "^1.0.1", "request-light": "^0.7.0", "vscode-languageserver": "^9.0.1", "vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" } }, "sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw=="], + + "@volar/language-service": ["@volar/language-service@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" } }, "sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw=="], + + "@volar/source-map": ["@volar/source-map@2.4.28", "", {}, "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ=="], + + "@volar/typescript": ["@volar/typescript@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="], + + "@vscode/emmet-helper": ["@vscode/emmet-helper@2.11.0", "", { "dependencies": { "emmet": "^2.4.3", "jsonc-parser": "^2.3.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.15.1", "vscode-uri": "^3.0.8" } }, "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw=="], + + "@vscode/l10n": ["@vscode/l10n@0.0.18", "", {}, "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ=="], + + "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "ansis": ["ansis@4.3.1", "", {}, "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA=="], + + "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "array-iterate": ["array-iterate@2.0.1", "", {}, "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg=="], + + "ast-kit": ["ast-kit@3.0.0-beta.1", "", { "dependencies": { "@babel/parser": "^8.0.0-beta.4", "estree-walker": "^3.0.3", "pathe": "^2.0.3" } }, "sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw=="], + + "astro": ["astro@6.4.4", "", { "dependencies": { "@astrojs/compiler": "^4.0.0", "@astrojs/internal-helpers": "0.10.0", "@astrojs/markdown-remark": "7.2.0", "@astrojs/telemetry": "3.3.2", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.8.1", "diff": "^8.0.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", "flattie": "^1.1.1", "fontace": "~0.4.1", "get-tsconfig": "5.0.0-beta.4", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "jsonc-parser": "^3.3.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.4", "rehype": "^13.0.2", "semver": "^7.7.4", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "svgo": "^4.0.1", "tinyclip": "^0.1.12", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unist-util-visit": "^5.1.0", "unstorage": "^1.17.5", "vfile": "^6.0.3", "vite": "^7.3.2", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "./bin/astro.mjs" } }, "sha512-hVe8tq3lqt/Dr0UyB//yUmQSlHMTU8scTiF/vQddQVahLE4TTaSdH5H0nb7OvRcwo0UmlAO8DWYar4jNaS7H+A=="], + + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], + + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.34", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw=="], + + "birpc": ["birpc@4.0.0", "", {}, "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw=="], + + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + + "buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="], + + "cac": ["cac@7.0.0", "", {}, "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001793", "", {}, "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], + + "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + + "cookie-es": ["cookie-es@1.2.3", "", {}, "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw=="], + + "crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="], + + "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], + + "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], + + "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + + "csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], + + "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "devalue": ["devalue@5.8.1", "", {}, "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + + "dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + + "dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="], + + "dts-resolver": ["dts-resolver@2.1.3", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.368", "", {}, "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw=="], + + "emmet": ["emmet@2.4.11", "", { "dependencies": { "@emmetio/abbreviation": "^2.3.3", "@emmetio/css-abbreviation": "^2.1.8" } }, "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "empathic": ["empathic@2.0.1", "", {}, "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q=="], + + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + + "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], + + "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=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="], + + "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="], + + "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], + + "fast-wrap-ansi": ["fast-wrap-ansi@0.2.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="], + + "fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="], + + "fontkitten": ["fontkitten@1.0.3", "", { "dependencies": { "tiny-inflate": "^1.0.3" } }, "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-tsconfig": ["get-tsconfig@5.0.0-beta.4", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ=="], + + "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], + + "h3": ["h3@1.15.11", "", { "dependencies": { "cookie-es": "^1.2.3", "crossws": "^0.3.5", "defu": "^6.1.6", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="], + + "hamo": ["hamo@workspace:packages/hamo"], + + "happy-dom": ["happy-dom@20.10.2", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-5p9Sxis3eowDJKqx90QCsgbNA02XXqJ59NOHvD4V6cxp+rP4d/xOyVx7uY3hS8hiUbY1VeiFH8lbJ81AyuDVLQ=="], + + "hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="], + + "hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="], + + "hast-util-is-element": ["hast-util-is-element@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g=="], + + "hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], + + "hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + + "hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="], + + "hast-util-to-text": ["hast-util-to-text@4.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "hast-util-is-element": "^3.0.0", "unist-util-find-after": "^5.0.0" } }, "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], + + "hookable": ["hookable@6.1.1", "", {}, "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ=="], + + "html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + + "import-without-cache": ["import-without-cache@0.3.3", "", {}, "sha512-bDxwDdF04gm550DfZHgffvlX+9kUlcz32UD0AeBTmVPFiWkrexF2XVmiuFFbDhiFuP8fQkrkvI2KdSNPYWAXkQ=="], + + "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], + + "is-docker": ["is-docker@4.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "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-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + + "lenis": ["lenis@1.3.23", "", { "peerDependencies": { "@nuxt/kit": ">=3.0.0", "react": ">=17.0.0", "vue": ">=3.0.0" }, "optionalPeers": ["@nuxt/kit", "react", "vue"] }, "sha512-YxYq3TJqj9sJNv0V9SkyQHejt14xwyIwgDaaMK89Uf9SxQfIszu+gTQSSphh6BWlLTNVKvvXAGkg+Zf+oFIevg=="], + + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + + "lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="], + + "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.3", "", { "dependencies": { "@babel/parser": "^7.29.3", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw=="], + + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + + "mdast-util-definitions": ["mdast-util-definitions@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="], + + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], + + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], + + "nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], + + "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="], + + "nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="], + + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], + + "node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="], + + "node-releases": ["node-releases@2.0.47", "", {}, "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + + "obug": ["obug@2.1.2", "", {}, "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg=="], + + "ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], + + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + + "oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="], + + "oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="], + + "p-limit": ["p-limit@7.3.0", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw=="], + + "p-queue": ["p-queue@9.3.0", "", { "dependencies": { "eventemitter3": "^5.0.4", "p-timeout": "^7.0.0" } }, "sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang=="], + + "p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="], + + "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], + + "parse-latin": ["parse-latin@7.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="], + + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "playground": ["playground@workspace:playground"], + + "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], + + "prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="], + + "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=="], + + "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + + "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], + + "quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], + + "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="], + + "react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="], + + "react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="], + + "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + + "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], + + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + + "rehype": ["rehype@13.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "rehype-parse": "^9.0.0", "rehype-stringify": "^10.0.0", "unified": "^11.0.0" } }, "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A=="], + + "rehype-parse": ["rehype-parse@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" } }, "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="], + + "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="], + + "rehype-stringify": ["rehype-stringify@10.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" } }, "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="], + + "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], + + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + + "remark-smartypants": ["remark-smartypants@3.0.2", "", { "dependencies": { "retext": "^9.0.0", "retext-smartypants": "^6.0.0", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" } }, "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA=="], + + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + + "request-light": ["request-light@0.7.0", "", {}, "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "retext": ["retext@9.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "retext-latin": "^4.0.0", "retext-stringify": "^4.0.0", "unified": "^11.0.0" } }, "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA=="], + + "retext-latin": ["retext-latin@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "parse-latin": "^7.0.0", "unified": "^11.0.0" } }, "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA=="], + + "retext-smartypants": ["retext-smartypants@6.2.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ=="], + + "retext-stringify": ["retext-stringify@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unified": "^11.0.0" } }, "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA=="], + + "rolldown": ["rolldown@1.0.0-rc.17", "", { "dependencies": { "@oxc-project/types": "=0.127.0", "@rolldown/pluginutils": "1.0.0-rc.17" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-x64": "1.0.0-rc.17", "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA=="], + + "rolldown-plugin-dts": ["rolldown-plugin-dts@0.23.2", "", { "dependencies": { "@babel/generator": "8.0.0-rc.3", "@babel/helper-validator-identifier": "8.0.0-rc.3", "@babel/parser": "8.0.0-rc.3", "@babel/types": "8.0.0-rc.3", "ast-kit": "^3.0.0-beta.1", "birpc": "^4.0.0", "dts-resolver": "^2.1.3", "get-tsconfig": "^4.13.7", "obug": "^2.1.1", "picomatch": "^4.0.4" }, "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20260325.1", "rolldown": "^1.0.0-rc.12", "typescript": "^5.0.0 || ^6.0.0", "vue-tsc": "~3.2.0" }, "optionalPeers": ["@ts-macro/tsc", "@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-PbSqLawLgZBGcOGT3yqWBGn4cX+wh2nt5FuBGdcMHyOhoukmjbhYAl8NT9sE4U38Cm9tqLOIQeOrvzeayM0DLQ=="], + + "rollup": ["rollup@4.61.1", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.61.1", "@rollup/rollup-android-arm64": "4.61.1", "@rollup/rollup-darwin-arm64": "4.61.1", "@rollup/rollup-darwin-x64": "4.61.1", "@rollup/rollup-freebsd-arm64": "4.61.1", "@rollup/rollup-freebsd-x64": "4.61.1", "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", "@rollup/rollup-linux-arm-musleabihf": "4.61.1", "@rollup/rollup-linux-arm64-gnu": "4.61.1", "@rollup/rollup-linux-arm64-musl": "4.61.1", "@rollup/rollup-linux-loong64-gnu": "4.61.1", "@rollup/rollup-linux-loong64-musl": "4.61.1", "@rollup/rollup-linux-ppc64-gnu": "4.61.1", "@rollup/rollup-linux-ppc64-musl": "4.61.1", "@rollup/rollup-linux-riscv64-gnu": "4.61.1", "@rollup/rollup-linux-riscv64-musl": "4.61.1", "@rollup/rollup-linux-s390x-gnu": "4.61.1", "@rollup/rollup-linux-x64-gnu": "4.61.1", "@rollup/rollup-linux-x64-musl": "4.61.1", "@rollup/rollup-openbsd-x64": "4.61.1", "@rollup/rollup-openharmony-arm64": "4.61.1", "@rollup/rollup-win32-arm64-msvc": "4.61.1", "@rollup/rollup-win32-ia32-msvc": "4.61.1", "@rollup/rollup-win32-x64-gnu": "4.61.1", "@rollup/rollup-win32-x64-msvc": "4.61.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA=="], + + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="], + + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + + "shiki": ["shiki@4.2.0", "", { "dependencies": { "@shikijs/core": "4.2.0", "@shikijs/engine-javascript": "4.2.0", "@shikijs/engine-oniguruma": "4.2.0", "@shikijs/langs": "4.2.0", "@shikijs/themes": "4.2.0", "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ=="], + + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "smol-toml": ["smol-toml@1.6.1", "", {}, "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="], + + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], + + "tinyclip": ["tinyclip@0.1.14", "", {}, "sha512-F1oWdz8tjT17qe1d5JgDK6z03WGOhYYAN0lK3/D/fzNiy93xswLLEw7pk+3g05onhAy6Bsc6PLNUGhdgVjemMQ=="], + + "tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + + "tsdown": ["tsdown@0.21.10", "", { "dependencies": { "ansis": "^4.2.0", "cac": "^7.0.0", "defu": "^6.1.7", "empathic": "^2.0.0", "hookable": "^6.1.1", "import-without-cache": "^0.3.3", "obug": "^2.1.1", "picomatch": "^4.0.4", "rolldown": "1.0.0-rc.17", "rolldown-plugin-dts": "^0.23.2", "semver": "^7.7.4", "tinyexec": "^1.1.1", "tinyglobby": "^0.2.16", "tree-kill": "^1.2.2", "unconfig-core": "^7.5.0", "unrun": "^0.2.37" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@tsdown/css": "0.21.10", "@tsdown/exe": "0.21.10", "@vitejs/devtools": "*", "publint": "^0.3.0", "typescript": "^5.0.0 || ^6.0.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "@tsdown/css", "@tsdown/exe", "@vitejs/devtools", "publint", "typescript", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-3wk73yBhZe/wX7REqSdivNQ84TDs1mJ+IlnzrrEREP70xlJ/AEIzqaI04l/TzMKVIdkTdC3CPaADn2Lk/0SkdA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typesafe-path": ["typesafe-path@0.2.2", "", {}, "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "typescript-auto-import-cache": ["typescript-auto-import-cache@0.3.6", "", { "dependencies": { "semver": "^7.3.8" } }, "sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ=="], + + "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + + "ultrahtml": ["ultrahtml@1.6.0", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="], + + "unconfig-core": ["unconfig-core@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w=="], + + "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], + + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + + "unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="], + + "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-modify-children": ["unist-util-modify-children@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "array-iterate": "^2.0.0" } }, "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], + + "unist-util-visit-children": ["unist-util-visit-children@3.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "unrun": ["unrun@0.2.39", "", { "dependencies": { "rolldown": "1.0.0-rc.17" }, "peerDependencies": { "synckit": "^0.11.11" }, "optionalPeers": ["synckit"], "bin": { "unrun": "dist/cli.mjs" } }, "sha512-h9FxYVpztY/wwq+bauLOh6Y3CWu2IVeRLq5lxzneBiIU9Tn86OGp9xiQrGhnYspAmg5dzdY0Cc8+Y70kuTARCg=="], + + "unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "vite": ["vite@7.3.5", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww=="], + + "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="], + + "volar-service-css": ["volar-service-css@0.0.70", "", { "dependencies": { "vscode-css-languageservice": "^6.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-K1qyOvBpE3rzdAv3e4/6Rv5yizrYPy5R/ne3IWCAzLBuMO4qBMV3kSqWzj6KUVe6S0AnN6wxF7cRkiaKfYMYJw=="], + + "volar-service-emmet": ["volar-service-emmet@0.0.70", "", { "dependencies": { "@emmetio/css-parser": "^0.4.1", "@emmetio/html-matcher": "^1.3.0", "@vscode/emmet-helper": "^2.9.3", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-xi5bC4m/VyE3zy/n2CXspKeDZs3qA41tHLTw275/7dNWM/RqE2z3BnDICQybHIVp/6G1iOQj5c1qXMgQC08TNg=="], + + "volar-service-html": ["volar-service-html@0.0.70", "", { "dependencies": { "vscode-html-languageservice": "^5.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-eR6vCgMdmYAo4n+gcT7DSyBQbwB8S3HZZvSagTf0sxNaD4WppMCFfpqWnkrlGStPKMZvMiejRRVmqsX9dYcTvQ=="], + + "volar-service-prettier": ["volar-service-prettier@0.0.70", "", { "dependencies": { "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0", "prettier": "^2.2 || ^3.0" }, "optionalPeers": ["@volar/language-service", "prettier"] }, "sha512-Z6BCFSpGVCd8BPAsZ785Kce1BGlWd5ODqmqZGVuB14MJvrR4+CYz6cDy4F+igmE1gMifqfvMhdgT8Aud4M5ngg=="], + + "volar-service-typescript": ["volar-service-typescript@0.0.70", "", { "dependencies": { "path-browserify": "^1.0.1", "semver": "^7.6.2", "typescript-auto-import-cache": "^0.3.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-nls": "^5.2.0", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-l46Bx4cokkUedTd74ojO5H/zqHZJ8SUuyZ0IB8JN4jfRqUM3bQFBHoOwlZCyZmOeO0A3RQNkMnFclxO4c++gsg=="], + + "volar-service-typescript-twoslash-queries": ["volar-service-typescript-twoslash-queries@0.0.70", "", { "dependencies": { "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-IdD13Z9N2Bu8EM6CM0fDV1E69olEYGHDU25X51YXmq8Y0CmJ2LNj6gOiBJgpS5JGUqFzECVhMNBW7R0sPdRTMQ=="], + + "volar-service-yaml": ["volar-service-yaml@0.0.70", "", { "dependencies": { "vscode-uri": "^3.0.8", "yaml-language-server": "~1.20.0" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-0c8bXDBeoATF9F6iPIlOuYTuZAC4c+yi0siQo920u7eiBJk8oQmUmg9cDUbR4+Gl++bvGP4plj3fErbJuPqdcQ=="], + + "vscode-css-languageservice": ["vscode-css-languageservice@6.3.10", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "3.17.5", "vscode-uri": "^3.1.0" } }, "sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA=="], + + "vscode-html-languageservice": ["vscode-html-languageservice@5.6.2", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "^3.17.5", "vscode-uri": "^3.1.0" } }, "sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg=="], + + "vscode-json-languageservice": ["vscode-json-languageservice@4.1.8", "", { "dependencies": { "jsonc-parser": "^3.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.2" } }, "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg=="], + + "vscode-jsonrpc": ["vscode-jsonrpc@9.0.0", "", {}, "sha512-+VvMmQPJhtvJ+8O+zu2JKIRiLxXF8NW7krWgyMGeOHrp4Cn23T5hc0v2LknNeopDOB70wghHAds7mKtcZ0I4Sg=="], + + "vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="], + + "vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.18.0", "", { "dependencies": { "vscode-jsonrpc": "9.0.0", "vscode-languageserver-types": "3.18.0" } }, "sha512-Zdz+kJ12Iz6tc11xfZyEo501bBATHXrCjmMfnaR3pMnf1CoqZBKIynba3P+/bi9VEdrMbNtAVKYpKhbODvqy+Q=="], + + "vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="], + + "vscode-languageserver-types": ["vscode-languageserver-types@3.18.0", "", {}, "sha512-8TsGPNMIMiiBdkORgRSvLjuiEIiAFtO+KssmYWxQ+uSVvlf7RjK8YKCOjPzZ+YA04jXEV7+7LvkSmHkhpNS99g=="], + + "vscode-nls": ["vscode-nls@5.2.0", "", {}, "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng=="], + + "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], + + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], + + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], + + "which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="], + + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "yaml-language-server": ["yaml-language-server@1.20.0", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "prettier": "^3.5.0", "request-light": "^0.5.7", "vscode-json-languageservice": "4.1.8", "vscode-languageserver": "^9.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", "vscode-uri": "^3.0.2", "yaml": "2.7.1" }, "bin": { "yaml-language-server": "bin/yaml-language-server" } }, "sha512-qhjK/bzSRZ6HtTvgeFvjNPJGWdZ0+x5NREV/9XZWFjIGezew2b4r5JPy66IfOhd5OA7KeFwk1JfmEbnTvev0cA=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], + + "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], + + "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@astrojs/language-server/@astrojs/compiler": ["@astrojs/compiler@2.13.1", "", {}, "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg=="], + + "@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/core/@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="], + + "@babel/core/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + + "@babel/core/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-module-imports/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + + "@babel/helper-module-transforms/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/helpers/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + + "@babel/template/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + + "@babel/template/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + + "@babel/traverse/@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="], + + "@babel/traverse/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + + "@babel/traverse/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + + "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], + + "@types/babel__core/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + + "@types/babel__core/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + + "@types/babel__generator/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + + "@types/babel__template/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + + "@types/babel__template/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + + "@types/babel__traverse/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + + "@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], + + "@vscode/emmet-helper/jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="], + + "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "ast-kit/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], + + "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "is-inside-container/is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "magicast/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + + "magicast/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "playground/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + + "rolldown-plugin-dts/get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], + + "unstorage/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + + "vscode-css-languageservice/vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], + + "vscode-languageserver/vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.5", "", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="], + + "yaml-language-server/yaml": ["yaml@2.7.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="], + + "yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "@babel/core/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/core/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/helper-module-imports/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-module-imports/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/helpers/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helpers/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/template/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/template/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@types/babel__core/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@types/babel__core/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@types/babel__generator/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@types/babel__generator/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@types/babel__template/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@types/babel__template/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@types/babel__traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@types/babel__traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], + + "magicast/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "magicast/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "unstorage/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + + "vscode-languageserver/vscode-languageserver-protocol/vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="], + + "vscode-languageserver/vscode-languageserver-protocol/vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], + } +} diff --git a/docs/App.jsx b/docs/App.jsx deleted file mode 100644 index 526d5a8..0000000 --- a/docs/App.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react' -import { useIntersectionObserver } from '../src/hooks/use-intersection-observer' -import { useResizeObserver } from '../src/hooks/use-resize-observer' -import { - useDebug, - useDocumentReadyState, - useFrame, - useIsClient, - useIsTouchDevice, - useRect, - useMediaQuery, - useWindowSize, - useLazyState, - useFramerate, -} from '../src/index' - -if (typeof window !== 'undefined') { - window.useRect = useRect -} - -function App() { - const [counter, setCounter] = useState(0) - - useEffect(() => { - const interval = setInterval(() => { - // setCounter((prev) => prev + 1) - }, 1000) - - return () => clearInterval(interval) - }, []) - - const [get, set] = useLazyState( - 0, - (value, prevValue) => { - console.log('set', value, prevValue) - }, - [counter], - ) - - useFramerate(1, (time, deltaTime) => { - console.log('time', time, 'deltaTime', deltaTime) - }) - - const [setRectRef, rect, setRectWrapperRef] = useRect({}) - const [setResizeObserverRef, entry] = useResizeObserver({}) - - useEffect(() => { - console.log(entry?.borderBoxSize?.[0], entry?.contentRect, entry?.contentBoxSize?.[0]) - }, [entry]) - - const isTouch = useIsTouchDevice() - const debug = useDebug() - const isClient = useIsClient() - const readyState = useDocumentReadyState() - const [setIntersectionRef, intersection] = useIntersectionObserver({ lazy: false }) - const isMobile = useMediaQuery('(max-width: 800px)') - const { width: windowWidth, height: windowHeight } = useWindowSize() - - const frameRef = useRef() - - useFrame((time, deltaTime) => { - // console.log({ time, deltaTime }) - frameRef.current.textContent = `time: ${time} / deltaTime: ${deltaTime}` - }) - - const contentRef = useRef() - - // useEffect(() => { - // useRect.observe(contentRef.current) - - // return () => { - // useRect.unobserve(contentRef.current) - // } - // }, []) - - useEffect(() => { - // setRectWrapperRef(document.querySelector('#root')) - }, [setRectWrapperRef]) - - return ( -
{ - setIntersectionRef(node) - contentRef.current = node - }} - > -

-

- window: {windowWidth} / {windowHeight} -

-

is touch? {isTouch ? 'yes' : 'no'}

-

is debug? {debug ? 'yes' : 'no'}

-

document readyState? {readyState}

-

is in viewport? {intersection.isIntersecting ? 'yes' : 'no'}

-

is client? {isClient ? 'yes' : 'no'}

-

is mobile? {isMobile ? 'yes' : 'no'}

-
- {entry?.borderBoxSize?.[0].inlineSize} x {entry?.borderBoxSize?.[0].blockSize} -
-
-
- top: {rect?.top} -
- height: {rect?.height} -
- left: {rect?.left} -
- width: {rect?.width} -
- bottom: {rect?.bottom} -
- right: {rect?.right} -
-
-
- ) -} - -export default App diff --git a/docs/index.css b/docs/index.css deleted file mode 100644 index f2d325c..0000000 --- a/docs/index.css +++ /dev/null @@ -1,42 +0,0 @@ -/* html, -body { - overflow-y: hidden; -} */ - -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', - 'Droid Sans', 'Helvetica Neue', sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - font-size: 14px; - line-height: 100%; -} - -/* #root { - position: fixed; - inset: 0; - height: 100vh; - overflow-y: scroll; -} */ - -.main { - padding: 5vw; - padding-top: 50vh; - padding-bottom: 120vh; -} - -.rect-wrapper { - /* position: fixed; */ - /* top: 50px; - left: 50px; - width: 700px; - height: 700px; */ -} - -.rect { - padding: 10px; - background: red; - border: 2px solid yellow; - /* width: 500px; */ -} diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 96dd883..0000000 --- a/docs/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - darkroom.engineering - Hamo - - -
- - - diff --git a/docs/main.jsx b/docs/main.jsx deleted file mode 100644 index dbb7d8b..0000000 --- a/docs/main.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react' -import { createRoot } from 'react-dom/client' -import App from './App' -import './index.css' - -const container = document.getElementById('root') -const root = createRoot(container) -root.render() diff --git a/package.json b/package.json index 38b666b..126d99b 100644 --- a/package.json +++ b/package.json @@ -1,69 +1,14 @@ -{ - "name": "hamo", - "version": "1.0.0-dev.10", - "description": "hamo means hook, do the math.", - "type": "module", - "workspaces": [ - "packages/*", - "playground", - "playground/*" - ], - "sideEffects": false, - "unpkg": "./dist/hamo.mjs", - "main": "./dist/hamo.mjs", - "module": "./dist/hamo.mjs", - "types": "./dist/hamo.d.ts", - "exports": { - ".": { - "types": "./dist/hamo.d.ts", - "default": "./dist/hamo.mjs" - }, - "./dist/*": "./dist/*" - }, - "files": [ - "dist" - ], - "author": "darkroom.engineering", - "license": "MIT", - "bugs": { - "url": "https://github.com/darkroomengineering/hamo/issues" - }, - "homepage": "https://github.com/darkroomengineering/hamo", - "repository": { - "type": "git", - "url": "https://github.com/darkroomengineering/hamo" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/darkroomengineering" - }, - "scripts": { - "build": "tsdown", - "dev": "bun run --parallel dev:build dev:playground", - "dev:build": "tsdown --watch", - "dev:playground": "bun --filter playground dev", - "version:dev": "npm version prerelease --preid dev --force --no-git-tag-version", - "version:patch": "npm version patch --force --no-git-tag-version", - "version:minor": "npm version minor --force --no-git-tag-version", - "version:major": "npm version major --force --no-git-tag-version", - "postversion": "bun run build", - "publish:main": "npm publish", - "publish:dev": "npm publish --tag dev" - }, - "keywords": [ - "react", - "hooks" - ], - "devDependencies": { - "@biomejs/biome": "1.9.4", - "tsdown": "^0.21.4", - "typescript": "^5.4.5" - }, - "dependencies": { - "nanoevents": "^9.0.0" - }, - "peerDependencies": { - "react": ">=17.0.0", - "react-dom": ">=17.0.0" - } -} +{ + "name": "hamo-monorepo", + "private": true, + "type": "module", + "workspaces": ["packages/*", "playground"], + "scripts": { + "build": "bun --filter hamo build", + "dev": "bun --filter hamo dev", + "typecheck": "bun --filter hamo typecheck", + "lint": "bun --filter hamo lint", + "format": "bun --filter hamo format", + "test": "bun --filter hamo test" + } +} diff --git a/.npmrc b/packages/hamo/.npmrc similarity index 100% rename from .npmrc rename to packages/hamo/.npmrc diff --git a/packages/hamo/CHANGELOG.md b/packages/hamo/CHANGELOG.md new file mode 100644 index 0000000..94c92b5 --- /dev/null +++ b/packages/hamo/CHANGELOG.md @@ -0,0 +1,47 @@ +# Changelog + +## 1.0.0 + +First stable release. + +### Added + +- Dual **ESM + CJS** build with correct `types` resolution for `import` and + `require` (verified with `publint` and `@arethetypeswrong/cli`). +- Test suite (`bun test` + `@testing-library/react` + `happy-dom`) covering + every hook: render smoke tests, SSR (`renderToString`) safety, and a + debounce-cancel-on-unmount leak regression. +- CI: typecheck + lint + build on every PR, and a test matrix across React 18 + and 19. Publishing runs the full gate and publishes with npm provenance. + +### Changed + +- **Zero runtime dependencies** — the internal resize emitter no longer depends + on `nanoevents`. +- Per-file `"use client"` directives (via `tsdown` unbundle) instead of a single + bundle banner, so `useObjectFit` stays usable in Server Components. +- Minification and polyfilling are delegated to the consuming framework; the + package ships modern, unminified, sourcemapped output. +- Toolchain consolidated onto **Biome** (formatter + linter, including + rules-of-hooks). The unused ESLint config was removed. +- Source collapsed from a `packages/react` workspace into a single `src/` tree. + +### Fixed + +- `useRef()` calls now pass an initial value — the library type-checks + cleanly against `@types/react` 19. +- `useIntersectionObserver` now has a correct `lazy` conditional return type + (no more `as IntersectionObserverEntry` cast), a stable callback ref, and + accepts `number | number[]` thresholds. +- `useLazyState` now reports the real `previousValue` (it was always + `undefined`). +- `useDebouncedCallback` and `useResizeObserver` cancel pending timers on + unmount, preventing callbacks from firing after teardown. + +### Removed + +- `react-dom` peer dependency (it was never imported). + +### Peer dependencies + +- `react >= 18`. diff --git a/LICENSE b/packages/hamo/LICENSE similarity index 100% rename from LICENSE rename to packages/hamo/LICENSE diff --git a/packages/hamo/README.md b/packages/hamo/README.md new file mode 100644 index 0000000..802e1fd --- /dev/null +++ b/packages/hamo/README.md @@ -0,0 +1,48 @@ +[![HAMO](https://assets.darkroom.engineering/hamo/banner.gif)](https://github.com/darkroomengineering/hamo) + +## Introduction + +`hāmō` means hook in Latin, this package is a collection of custom performance-oriented React hooks. + +## Features + +- **Render-safe hooks** — a small, tree-shakeable set of performance-oriented React hooks +- **Measurement hooks** — useRect, useWindowSize, and useResizeObserver track element and viewport size without thrash +- **useIntersectionObserver** — fire when elements enter or leave the viewport +- **useLazyState** — update a value through a callback without triggering a re-render +- **Debounce hooks** — useDebouncedCallback, Effect, and State debounce from one primitive +- **useObjectFit** — compute object-fit scale from a parent's dimensions +- **Zero dependencies** — ships dual ESM + CJS with `"use client"` only where needed; works in Next.js, React Router, TanStack Start, Vite, and any bundler +- **SSR & RSC safe** — every browser hook is effect-guarded, so there are no hydration mismatches, and `useObjectFit` runs in Server Components + +## Installation + +```bash +npm i hamo +``` + +Requires **React 18 or 19** (declared as a peer dependency). hamo has **zero runtime dependencies**. + +## Hooks + +[`useRect`](./src/use-rect/README.md) – tracks element position within the page + +[`useWindowSize`](./src/use-window-size/README.md) – tracks window dimensions + +[`useResizeObserver`](./src/use-resize-observer/README.md) – observes element dimensions using ResizeObserver + +[`useLazyState`](./src/use-lazy-state/README.md) – runs a callback when the state changes without re-rendering the component + +[`useDebouncedCallback/useDebouncedEffect/useDebouncedState`](./src/use-debounce/README.md) – debounces a callback, effect, or state + +[`useObjectFit`](./src/use-object-fit/README.md) – calculates the x and y scale of an object based on its parent width and height + +[`useIntersectionObserver`](./src/use-intersection-observer/README.md) – observes element intersection with the viewport + +## License + +MIT © [darkroom.engineering](https://github.com/darkroomengineering) + +## Shoutout + +Thank you to [Luca Gesmundo](https://github.com/lucagez) for having transfered us the npm package name 🙏. \ No newline at end of file diff --git a/packages/hamo/biome.json b/packages/hamo/biome.json new file mode 100644 index 0000000..e1c8dda --- /dev/null +++ b/packages/hamo/biome.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "includes": ["**", "!dist", "!**/node_modules", "!.vscode"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 80, + "lineEnding": "lf" + }, + "assist": { + "actions": { + "source": { + "organizeImports": "on" + } + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "useExhaustiveDependencies": "warn", + "useHookAtTopLevel": "error" + }, + "suspicious": { + "noExplicitAny": "off" + } + } + }, + "overrides": [ + { + "includes": ["**/use-scroll-trigger/debugger.tsx"], + "linter": { + "rules": { + "a11y": { + "noStaticElementInteractions": "off" + } + } + } + } + ], + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "asNeeded", + "trailingCommas": "es5" + } + } +} diff --git a/packages/hamo/bunfig.toml b/packages/hamo/bunfig.toml new file mode 100644 index 0000000..8755352 --- /dev/null +++ b/packages/hamo/bunfig.toml @@ -0,0 +1,2 @@ +[test] +preload = ["./test/setup.ts"] diff --git a/packages/hamo/package.json b/packages/hamo/package.json new file mode 100644 index 0000000..05fd4a5 --- /dev/null +++ b/packages/hamo/package.json @@ -0,0 +1,102 @@ +{ + "name": "hamo", + "version": "1.0.0", + "description": "A collection of performance-oriented React hooks.", + "type": "module", + "sideEffects": false, + "main": "./dist/hamo.cjs", + "module": "./dist/hamo.mjs", + "types": "./dist/hamo.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/hamo.d.ts", + "default": "./dist/hamo.mjs" + }, + "require": { + "types": "./dist/hamo.d.cts", + "default": "./dist/hamo.cjs" + } + }, + "./scroll-trigger/debugger": { + "import": { + "types": "./dist/use-scroll-trigger/debugger.d.ts", + "default": "./dist/use-scroll-trigger/debugger.mjs" + }, + "require": { + "types": "./dist/use-scroll-trigger/debugger.d.cts", + "default": "./dist/use-scroll-trigger/debugger.cjs" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "dist" + ], + "author": "darkroom.engineering", + "license": "MIT", + "bugs": { + "url": "https://github.com/darkroomengineering/hamo/issues" + }, + "homepage": "https://github.com/darkroomengineering/hamo", + "repository": { + "type": "git", + "url": "git+https://github.com/darkroomengineering/hamo.git" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/darkroomengineering" + }, + "scripts": { + "build": "tsdown", + "dev": "bun run --parallel dev:build dev:playground", + "dev:build": "tsdown --watch", + "dev:playground": "bun --filter playground dev", + "typecheck": "tsc --noEmit", + "lint": "biome check", + "format": "biome format --write", + "test": "bun test", + "prepublishOnly": "tsdown", + "version:patch": "npm version patch --force --no-git-tag-version", + "version:minor": "npm version minor --force --no-git-tag-version", + "version:major": "npm version major --force --no-git-tag-version", + "postversion": "bun run build", + "publish:main": "npm publish" + }, + "keywords": [ + "react", + "hooks", + "react-hooks", + "resize-observer", + "intersection-observer", + "media-query", + "ssr" + ], + "devDependencies": { + "@biomejs/biome": "^2.2.0", + "@happy-dom/global-registrator": "^20.0.0", + "@testing-library/react": "^16.1.0", + "@types/react": "^19.0.0", + "happy-dom": "^20.0.0", + "lenis": "^1.3.19", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tsdown": "^0.21.4", + "typescript": "^5.4.5" + }, + "peerDependencies": { + "react": ">=18.0.0", + "lenis": ">=1.3.0" + }, + "peerDependenciesMeta": { + "lenis": { + "optional": true + } + }, + "overrides": { + "yaml": "^2.9.0" + } +} diff --git a/packages/hamo/src/debounce-config.ts b/packages/hamo/src/debounce-config.ts new file mode 100644 index 0000000..7164a95 --- /dev/null +++ b/packages/hamo/src/debounce-config.ts @@ -0,0 +1,23 @@ +// Shared factory for the per-hook global debounce default. Each observer hook +// (useRect, useResizeObserver, useWindowSize) owns its own independent config +// instance, exposing a `setDebounce` to change its default at runtime. This +// keeps the three hooks' semantics identical without copy-pasting the +// module-level `let` + setter into every file. + +export interface DebounceConfig { + /** Read the current default delay. Evaluate per-call so it tracks setDebounce. */ + getDelay: () => number + /** Override the default delay for every future call of the owning hook. */ + setDebounce: (delay: number) => void +} + +export function createDebounceConfig(initialDelay = 500): DebounceConfig { + let delay = initialDelay + + return { + getDelay: () => delay, + setDebounce: (value: number) => { + delay = value + }, + } +} diff --git a/packages/hamo/src/index.ts b/packages/hamo/src/index.ts new file mode 100644 index 0000000..7a68ea4 --- /dev/null +++ b/packages/hamo/src/index.ts @@ -0,0 +1,24 @@ +export type { DebouncedFunction } from './use-debounce' +export { + useDebouncedCallback, + useDebouncedEffect, + useDebouncedState, + useTimeout, +} from './use-debounce' +export { useEffectEvent } from './use-effect-event' +export { useIntersectionObserver } from './use-intersection-observer' +export { useLazyState } from './use-lazy-state' +export { useMediaQuery } from './use-media-query' +export { useObjectFit } from './use-object-fit' +export type { Rect } from './use-rect' +export { useRect } from './use-rect' +export { useResizeObserver } from './use-resize-observer' +export type { UseScrollTriggerOptions } from './use-scroll-trigger' +export { useScrollTrigger } from './use-scroll-trigger' +export type { Transform, TransformRef } from './use-transform' +export { + TransformContext, + TransformProvider, + useTransform, +} from './use-transform' +export { useWindowSize } from './use-window-size' diff --git a/packages/react/src/use-debounce/README.md b/packages/hamo/src/use-debounce/README.md similarity index 100% rename from packages/react/src/use-debounce/README.md rename to packages/hamo/src/use-debounce/README.md diff --git a/packages/hamo/src/use-debounce/index.test.tsx b/packages/hamo/src/use-debounce/index.test.tsx new file mode 100644 index 0000000..2986cf8 --- /dev/null +++ b/packages/hamo/src/use-debounce/index.test.tsx @@ -0,0 +1,159 @@ +import { describe, expect, it, mock } from 'bun:test' +import { act, renderHook } from '@testing-library/react' +import { useDebouncedCallback, useDebouncedState } from './index' + +// --------------------------------------------------------------------------- +// useDebouncedCallback +// --------------------------------------------------------------------------- + +describe('useDebouncedCallback', () => { + it('calls the callback after the delay elapses', async () => { + const fn = mock(() => {}) + const DELAY = 40 + + const { result } = renderHook(() => useDebouncedCallback(fn, DELAY)) + + act(() => { + result.current() + }) + + // Not called yet — delay has not elapsed + expect(fn).toHaveBeenCalledTimes(0) + + await new Promise((r) => setTimeout(r, DELAY + 40)) + + expect(fn).toHaveBeenCalledTimes(1) + }) + + it('debounces: only fires once when called rapidly', async () => { + const fn = mock(() => {}) + const DELAY = 40 + + const { result } = renderHook(() => useDebouncedCallback(fn, DELAY)) + + act(() => { + result.current() + result.current() + result.current() + }) + + await new Promise((r) => setTimeout(r, DELAY + 40)) + + expect(fn).toHaveBeenCalledTimes(1) + }) + + it('leak regression: pending timer is cancelled on unmount', async () => { + // This is the critical correctness contract: if the component unmounts + // before the debounce delay expires, the callback must NOT fire. + // A broken implementation would call the mock after unmount, proving a + // timer leak that could cause state updates on unmounted components. + const fn = mock(() => {}) + const DELAY = 40 + + const { result, unmount } = renderHook(() => + useDebouncedCallback(fn, DELAY) + ) + + act(() => { + result.current() + }) + + // Unmount immediately — before the timer fires + unmount() + + // Wait well past the delay to give the (leaked) timer a chance to fire + await new Promise((r) => setTimeout(r, DELAY + 40)) + + expect(fn).toHaveBeenCalledTimes(0) + }) + + it('does not call previous callback instance after deps change', async () => { + const fn1 = mock(() => {}) + const fn2 = mock(() => {}) + const DELAY = 40 + + const { result, rerender } = renderHook( + ({ cb }) => useDebouncedCallback(cb, DELAY), + { initialProps: { cb: fn1 } } + ) + + act(() => { + result.current() + }) + + // Swap the callback before delay elapses — the debounced fn uses a ref + // so fn2 is what fires, not fn1. Both fire count should still be 1 total. + rerender({ cb: fn2 }) + + await new Promise((r) => setTimeout(r, DELAY + 40)) + + // The ref-based implementation means fn2 (the latest) is invoked + expect(fn1).toHaveBeenCalledTimes(0) + expect(fn2).toHaveBeenCalledTimes(1) + }) +}) + +// --------------------------------------------------------------------------- +// useDebouncedState +// --------------------------------------------------------------------------- + +describe('useDebouncedState', () => { + it('returns the initial value immediately', () => { + const { result } = renderHook(() => useDebouncedState('initial', 40)) + const [state] = result.current + expect(state).toBe('initial') + }) + + it('updates state after the delay', async () => { + const DELAY = 40 + const { result } = renderHook(() => useDebouncedState('a', DELAY)) + + act(() => { + const [, setState] = result.current + setState('b') + }) + + // State should still be the old value immediately after set + expect(result.current[0]).toBe('a') + + await act(async () => { + await new Promise((r) => setTimeout(r, DELAY + 40)) + }) + + expect(result.current[0]).toBe('b') + }) + + it('coalesces rapid updates — only the last value wins', async () => { + const DELAY = 40 + const { result } = renderHook(() => useDebouncedState(0, DELAY)) + + act(() => { + const [, setState] = result.current + setState(1) + setState(2) + setState(3) + }) + + await act(async () => { + await new Promise((r) => setTimeout(r, DELAY + 40)) + }) + + expect(result.current[0]).toBe(3) + }) + + it('functional updater receives the cached (not stale) value', async () => { + const DELAY = 40 + const { result } = renderHook(() => useDebouncedState(10, DELAY)) + + act(() => { + const [, setState] = result.current + setState((prev) => prev + 5) + }) + + await act(async () => { + await new Promise((r) => setTimeout(r, DELAY + 40)) + }) + + expect(result.current[0]).toBe(15) + }) +}) diff --git a/packages/react/src/use-debounce/index.ts b/packages/hamo/src/use-debounce/index.ts similarity index 74% rename from packages/react/src/use-debounce/index.ts rename to packages/hamo/src/use-debounce/index.ts index d60730e..7a63c73 100644 --- a/packages/react/src/use-debounce/index.ts +++ b/packages/hamo/src/use-debounce/index.ts @@ -1,130 +1,125 @@ -import { - type DependencyList, - useCallback, - useEffect, - useRef, - useState, -} from 'react' - -export type DebouncedFunction void> = (( - ...args: Parameters -) => void) & { - cancel: () => void -} - -export function debounce void>( - fn: T, - delay: number -): DebouncedFunction { - let timeoutId: ReturnType | undefined - - const debounced = (...args: Parameters) => { - if (timeoutId !== undefined) clearTimeout(timeoutId) - timeoutId = setTimeout(() => { - timeoutId = undefined - fn(...args) - }, delay) - } - - debounced.cancel = () => { - if (timeoutId !== undefined) { - clearTimeout(timeoutId) - timeoutId = undefined - } - } - - return debounced as DebouncedFunction -} - -function timeout(callback: (...args: any[]) => void, delay: number) { - const timeout = setTimeout(callback, delay) - - return () => clearTimeout(timeout) -} - -function useEffectEvent any>(callback: T): T { - const callbackRef = useRef(callback) - callbackRef.current = callback - - const [memoizedCallback] = useState( - () => - (...args: Parameters) => - callbackRef.current(...args) - ) - - return memoizedCallback as T -} - -export function useDebouncedEffect( - _callback: () => void, - delay: number, - deps: DependencyList = [] -) { - const callback = useEffectEvent(_callback) - - useEffect(() => { - return timeout(() => callback(), delay) - }, [delay, callback, ...deps]) -} - -export function useDebouncedCallback( - _callback: (...args: T[]) => void, - delay: number, - deps: DependencyList = [] -) { - const callback = useEffectEvent(_callback) - - const timeoutRef = useRef | null>(null) - - const debouncedCallback = useCallback( - (...args: T[]) => { - timeoutRef.current?.() - - timeoutRef.current = timeout(() => callback(...args), delay) - }, - [delay, callback, ...deps] - ) - - return debouncedCallback -} - -export function useDebouncedState( - initialValue: T, - delay: number -): [T, (value: T | ((prev: T) => T)) => void] { - const [debouncedState, setDebouncedState] = useState(initialValue) - const cachedStateRef = useRef(initialValue) - - const updateDebouncedState = useDebouncedCallback( - () => setDebouncedState(cachedStateRef.current), - delay - ) - - const setState = useCallback( - (value: T | ((prev: T) => T)) => { - if (typeof value === 'function') { - cachedStateRef.current = (value as (prev: T) => T)( - cachedStateRef.current - ) - } else { - cachedStateRef.current = value - } - updateDebouncedState() - }, - [updateDebouncedState] - ) - - return [debouncedState, setState] -} - -// keep the same name for backward compatibility - -/** - * @name useTimeout - * @description - * A hook that allows you to set a timeout. - * @param {function} callback - The callback function to be executed after the delay. - * @param {number} delay - The delay (in milliseconds) before the callback function is executed. - * @param {array} deps - The dependencies array for the hook. - */ -export const useTimeout = useDebouncedEffect +'use client' + +import { + type DependencyList, + useCallback, + useEffect, + useRef, + useState, +} from 'react' +import { useEffectEvent } from '../use-effect-event' + +export type DebouncedFunction void> = (( + ...args: Parameters +) => void) & { + cancel: () => void +} + +export function debounce void>( + fn: T, + delay: number +): DebouncedFunction { + let timeoutId: ReturnType | undefined + + const debounced = (...args: Parameters) => { + if (timeoutId !== undefined) clearTimeout(timeoutId) + timeoutId = setTimeout(() => { + timeoutId = undefined + fn(...args) + }, delay) + } + + debounced.cancel = () => { + if (timeoutId !== undefined) { + clearTimeout(timeoutId) + timeoutId = undefined + } + } + + return debounced as DebouncedFunction +} + +function timeout(callback: () => void, delay: number) { + const timeout = setTimeout(callback, delay) + + return () => clearTimeout(timeout) +} + +export function useDebouncedEffect( + _callback: () => void, + delay: number, + deps: DependencyList = [] +) { + const callback = useEffectEvent(_callback) + + useEffect(() => { + return timeout(() => callback(), delay) + }, [delay, callback, ...deps]) +} + +export function useDebouncedCallback( + _callback: (...args: T) => void, + delay: number, + deps: DependencyList = [] +) { + const callback = useEffectEvent(_callback) + + const timeoutRef = useRef | null>(null) + + const debouncedCallback = useCallback( + (...args: T) => { + timeoutRef.current?.() + + timeoutRef.current = timeout(() => callback(...args), delay) + }, + [delay, callback, ...deps] + ) + + // cancel any pending timer on unmount + useEffect(() => () => timeoutRef.current?.(), []) + + return debouncedCallback +} + +export function useDebouncedState( + initialValue: T, + delay: number +): [T, (value: T | ((prev: T) => T)) => void] { + const [debouncedState, setDebouncedState] = useState(initialValue) + const cachedStateRef = useRef(initialValue) + + const updateDebouncedState = useDebouncedCallback( + () => setDebouncedState(cachedStateRef.current), + delay + ) + + const setState = useCallback( + (value: T | ((prev: T) => T)) => { + if (typeof value === 'function') { + cachedStateRef.current = (value as (prev: T) => T)( + cachedStateRef.current + ) + } else { + cachedStateRef.current = value + } + updateDebouncedState() + }, + [updateDebouncedState] + ) + + return [debouncedState, setState] +} + +// keep the same name for backward compatibility + +/** + * @name useTimeout + * @description + * Alias of useDebouncedEffect. Runs the callback once after `delay`, re-arming the + * timer whenever `delay`, the callback, or any entry in `deps` changes. Kept for + * backward compatibility. + * @param {function} callback - The callback function to be executed after the delay. + * @param {number} delay - The delay (in milliseconds) before the callback function is executed. + * @param {array} deps - The dependency list that re-arms the timer when changed. + */ +export const useTimeout = useDebouncedEffect diff --git a/packages/hamo/src/use-effect-event/README.md b/packages/hamo/src/use-effect-event/README.md new file mode 100644 index 0000000..6d14fac --- /dev/null +++ b/packages/hamo/src/use-effect-event/README.md @@ -0,0 +1,114 @@ +# useEffectEvent + +A polyfill for React's experimental [`useEffectEvent`](https://react.dev/reference/react/useEffectEvent) hook. Returns a stable function reference that always calls the latest version of your callback, without needing to be listed in effect dependency arrays. + +React's `useEffectEvent` is still experimental and not available in any stable release. This implementation provides the same core behavior for React 17+ projects. + +## Usage + +```jsx +import { useEffectEvent } from 'hamo' + +function Chat({ url, onMessage }) { + const handleMessage = useEffectEvent(onMessage) + + useEffect(() => { + const ws = new WebSocket(url) + ws.addEventListener('message', handleMessage) + + return () => ws.close() + }, [url]) // handleMessage doesn't need to be in deps +} +``` + +## Parameters + +- `callback`: The function to wrap. Can accept any arguments and return any value. + +## Return Value + +A stable function with the same signature as your callback. The identity never changes across renders, but calling it always invokes the latest `callback` from the most recent render. + +## How It Works + +The hook stores your callback in a ref (updated every render) and returns a memoized wrapper created once via lazy `useState`. The wrapper delegates to the ref on each call, so: + +- The returned function has a **stable identity** (same reference every render) +- It always reads the **latest props and state** at call time +- It can safely be omitted from effect dependency arrays + +## Differences from React's Experimental Version + +| | React `useEffectEvent` | hamo `useEffectEvent` | +|---|---|---| +| Availability | Experimental, not in stable React | React 17+ | +| Identity | Intentionally unstable (changes every render) | Stable (same reference every render) | +| Callable from | Effects and other Effect Events only | Anywhere (effects, event handlers, callbacks) | +| Lint enforcement | ESLint plugin enforces constraints | No restrictions | + +The stable identity in hamo's version is a practical trade-off — it makes the hook more versatile (usable in event handlers, passed as props) while still solving the core problem of reading latest values without re-triggering effects. + +## Examples + +### Interval with Latest State + +```jsx +import { useEffect, useState } from 'react' +import { useEffectEvent } from 'hamo' + +function Counter() { + const [count, setCount] = useState(0) + const [step, setStep] = useState(1) + + const onTick = useEffectEvent(() => { + setCount((c) => c + step) // always reads latest step + }) + + useEffect(() => { + const id = setInterval(onTick, 1000) + return () => clearInterval(id) + }, []) // no need to restart interval when step changes + + return ( +
+

{count}

+ setStep(Number(e.target.value))} + /> +
+ ) +} +``` + +### Effect Without Unnecessary Re-runs + +```jsx +import { useEffect } from 'react' +import { useEffectEvent } from 'hamo' + +function Logger({ data, onLog }) { + const log = useEffectEvent(onLog) + + useEffect(() => { + log(data) // always calls latest onLog + }, [data]) // effect only re-runs when data changes, not when onLog changes +} +``` + +### Scroll Listener with Latest Callback + +```jsx +import { useEffect } from 'react' +import { useEffectEvent } from 'hamo' + +function useScroll(callback) { + const handler = useEffectEvent(callback) + + useEffect(() => { + window.addEventListener('scroll', handler) + return () => window.removeEventListener('scroll', handler) + }, []) // listener attached once, always calls latest callback +} +``` diff --git a/packages/hamo/src/use-effect-event/index.ts b/packages/hamo/src/use-effect-event/index.ts new file mode 100644 index 0000000..d93da0b --- /dev/null +++ b/packages/hamo/src/use-effect-event/index.ts @@ -0,0 +1,27 @@ +import { useCallback, useInsertionEffect, useRef } from 'react' + +/** + * Ponyfill for React's experimental `useEffectEvent`. + * + * Returns a stable function identity that always calls the latest `callback`, + * so the returned event can be omitted from effect dependency arrays without + * going stale. The ref is refreshed in `useInsertionEffect` (before any layout + * effect reads it), matching React's own internal implementation and avoiding + * render-phase ref mutation. + */ +export function useEffectEvent unknown>( + callback: T +): T { + const callbackRef = useRef(callback) + + useInsertionEffect(() => { + callbackRef.current = callback + }) + + const stableCallback = useCallback( + (...args: Parameters) => callbackRef.current(...args), + [] + ) + + return stableCallback as unknown as T +} diff --git a/packages/react/src/use-intersection-observer/README.md b/packages/hamo/src/use-intersection-observer/README.md similarity index 100% rename from packages/react/src/use-intersection-observer/README.md rename to packages/hamo/src/use-intersection-observer/README.md diff --git a/packages/hamo/src/use-intersection-observer/index.test.tsx b/packages/hamo/src/use-intersection-observer/index.test.tsx new file mode 100644 index 0000000..72a51df --- /dev/null +++ b/packages/hamo/src/use-intersection-observer/index.test.tsx @@ -0,0 +1,52 @@ +import { describe, expect, it } from 'bun:test' +import { act, renderHook } from '@testing-library/react' +import { useIntersectionObserver } from './index' + +describe('useIntersectionObserver (smoke)', () => { + it('mounts without throwing and returns a [setRef, entry] tuple', () => { + const { result, unmount } = renderHook(() => useIntersectionObserver()) + + const [setRef, entry] = result.current + expect(typeof setRef).toBe('function') + // Non-lazy: entry is undefined until an element is observed + expect(entry).toBeUndefined() + + expect(() => unmount()).not.toThrow() + }) + + it('lazy mode: second element of tuple is a getter function', () => { + const { result, unmount } = renderHook(() => + useIntersectionObserver({ lazy: true }) + ) + + const [setRef, getEntry] = result.current + expect(typeof setRef).toBe('function') + expect(typeof getEntry).toBe('function') + expect((getEntry as () => unknown)()).toBeUndefined() + + expect(() => unmount()).not.toThrow() + }) + + it('setRef with null does not throw', () => { + const { result } = renderHook(() => useIntersectionObserver()) + const [setRef] = result.current + + expect(() => { + act(() => { + setRef(null) + }) + }).not.toThrow() + }) + + it('accepts all options without throwing', () => { + const { unmount } = renderHook(() => + useIntersectionObserver({ + rootMargin: '10px', + threshold: 0.5, + once: true, + callback: () => {}, + }) + ) + expect(() => unmount()).not.toThrow() + }) +}) diff --git a/packages/hamo/src/use-intersection-observer/index.ts b/packages/hamo/src/use-intersection-observer/index.ts new file mode 100644 index 0000000..d36d6f0 --- /dev/null +++ b/packages/hamo/src/use-intersection-observer/index.ts @@ -0,0 +1,90 @@ +'use client' + +import { + type DependencyList, + useCallback, + useEffect, + useRef, + useState, +} from 'react' +import { useEffectEvent } from '../use-effect-event' + +/** + * @name useIntersectionObserver + * @description A React hook that observes element visibility using IntersectionObserver. + * @param {Element | Document} root (optional) + * @param {string} rootMargin (optional, default: `0px`) + * @param {number | number[]} threshold (optional, default: `0`) + * @param {boolean} once (optional, default: `false`) + * @param {boolean} lazy (optional, default: `false`) + * @param {function} callback (optional) + * @param {array} deps (optional) + * @returns {array} [setElement, lazy ? getEntry : entry] + */ + +export function useIntersectionObserver( + { + root = null, + rootMargin = '0px', + threshold = 0, + once = false, + lazy = false as L, + callback = () => {}, + }: { + root?: Element | Document | null + rootMargin?: string + threshold?: number | number[] + once?: boolean + lazy?: L + callback?: (entry: IntersectionObserverEntry | undefined) => void + } = {}, + deps: DependencyList = [] +): [ + (element: HTMLElement | null) => void, + L extends true + ? () => IntersectionObserverEntry | undefined + : IntersectionObserverEntry | undefined, +] { + const entryRef = useRef(undefined) + const [entry, setEntry] = useState() + const [element, setElement] = useState(null) + + const onIntersect = useEffectEvent(callback) + + useEffect(() => { + if (!element) return + + const intersection = new IntersectionObserver( + ([entry]) => { + if (lazy) { + entryRef.current = entry + } else { + setEntry(entry) + } + + onIntersect(entry) + + if (once && entry?.isIntersecting) intersection.disconnect() + }, + { + root, + rootMargin, + threshold, + } + ) + intersection.observe(element) + + return () => { + intersection.disconnect() + } + }, [element, root, rootMargin, threshold, lazy, once, onIntersect, ...deps]) + + const getEntry = useCallback(() => entryRef.current, []) + + return [setElement, lazy ? getEntry : entry] as [ + (element: HTMLElement | null) => void, + L extends true + ? () => IntersectionObserverEntry | undefined + : IntersectionObserverEntry | undefined, + ] +} diff --git a/packages/react/src/use-lazy-state/README.md b/packages/hamo/src/use-lazy-state/README.md similarity index 100% rename from packages/react/src/use-lazy-state/README.md rename to packages/hamo/src/use-lazy-state/README.md diff --git a/packages/hamo/src/use-lazy-state/index.test.tsx b/packages/hamo/src/use-lazy-state/index.test.tsx new file mode 100644 index 0000000..1eb85c5 --- /dev/null +++ b/packages/hamo/src/use-lazy-state/index.test.tsx @@ -0,0 +1,140 @@ +import { describe, expect, it, mock } from 'bun:test' +import { act, renderHook } from '@testing-library/react' +import { useLazyState } from './index' + +describe('useLazyState', () => { + it('does NOT cause a re-render when set() is called', () => { + // The hook's contract: set() triggers the callback but skips React state + // updates, so the host component stays at its initial render count. + let renderCount = 0 + const cb = mock(() => {}) + + const { result } = renderHook(() => { + renderCount++ + return useLazyState(0, cb) + }) + + const [set] = result.current + act(() => { + set(1) + set(2) + set(3) + }) + + // Only the initial mount render — no re-renders from set() + expect(renderCount).toBe(1) + }) + + it('get() returns the latest value after set()', () => { + const cb = mock(() => {}) + const { result } = renderHook(() => useLazyState(0, cb)) + + const [set, get] = result.current + act(() => { + set(42) + }) + + expect(get()).toBe(42) + }) + + it('get() returns the initialValue before any set()', () => { + const cb = mock(() => {}) + const { result } = renderHook(() => useLazyState('hello', cb)) + + const [, get] = result.current + expect(get()).toBe('hello') + }) + + it('passes previousValue correctly on successive set() calls', () => { + // This is the bug that was fixed: previousValue on the second call must be + // the value from the first call, not the initial value. + const calls: Array<{ value: number; prev: number | undefined }> = [] + const cb = mock((value: number, previousValue: number | undefined) => { + calls.push({ value, prev: previousValue }) + }) + + const { result } = renderHook(() => useLazyState(0, cb)) + const [set] = result.current + + act(() => { + set(1) + }) + act(() => { + set(2) + }) + + // calls[0] is the mount-effect call (value=0, prev=undefined). + // set(1): value=1, prev=0 (the initial value stored in stateRef) + expect(calls[1]).toEqual({ value: 1, prev: 0 }) + // set(2): prev must be 1 — not 0 (the fixed bug) + expect(calls[2]).toEqual({ value: 2, prev: 1 }) + }) + + it('callback receives undefined as previousValue on the initial mount effect', () => { + // useLazyState fires the callback once on mount (for deps-based effect). + // At that point prevStateRef is still undefined. + const calls: Array<{ value: number; prev: number | undefined }> = [] + const cb = mock((value: number, previousValue: number | undefined) => { + calls.push({ value, prev: previousValue }) + }) + + renderHook(() => useLazyState(99, cb)) + + expect(calls.length).toBeGreaterThanOrEqual(1) + expect(calls[0]?.prev).toBeUndefined() + }) + + it('functional updater: set((prev) => next) passes correct previousValue', () => { + const calls: Array<{ value: number; prev: number | undefined }> = [] + const cb = mock((value: number, previousValue: number | undefined) => { + calls.push({ value, prev: previousValue }) + }) + + const { result } = renderHook(() => useLazyState(10, cb)) + const [set] = result.current + + act(() => { + set((prev) => prev + 5) + }) + + const last = calls[calls.length - 1] + expect(last?.value).toBe(15) + expect(last?.prev).toBe(10) + }) + + it('does not call callback when set() is called with the same value', () => { + // The hook short-circuits when value === stateRef.current + let callCount = 0 + const cb = mock(() => { + callCount++ + }) + + const { result } = renderHook(() => useLazyState(5, cb)) + const [set] = result.current + + // Reset call count after mount effect fires + callCount = 0 + + act(() => { + set(5) // same value → should not invoke cb + }) + + expect(callCount).toBe(0) + }) + + it('useRef is stable — set and get refs survive re-renders', () => { + // If the parent re-renders for an external reason, the same set/get + // functions must still work (they close over the same refs). + const cb = mock(() => {}) + const { result, rerender } = renderHook(() => useLazyState(0, cb)) + + const [set1] = result.current + rerender() + const [, get2] = result.current + + act(() => { + set1(77) + }) + expect(get2()).toBe(77) + }) +}) diff --git a/packages/react/src/use-lazy-state/index.ts b/packages/hamo/src/use-lazy-state/index.ts similarity index 57% rename from packages/react/src/use-lazy-state/index.ts rename to packages/hamo/src/use-lazy-state/index.ts index 6e1943a..eb169d8 100644 --- a/packages/react/src/use-lazy-state/index.ts +++ b/packages/hamo/src/use-lazy-state/index.ts @@ -1,4 +1,7 @@ -import { useCallback, useEffect, useRef } from 'react' +'use client' + +import { type DependencyList, useCallback, useEffect, useRef } from 'react' +import { useEffectEvent } from '../use-effect-event' /** * @name useLazyState @@ -12,29 +15,30 @@ import { useCallback, useEffect, useRef } from 'react' export function useLazyState( initialValue: T, callback: (value: T, previousValue: T | undefined) => void, - deps: any[] = [] + deps: DependencyList = [] ) { - const prevStateRef = useRef() + const prevStateRef = useRef(undefined) const stateRef = useRef(initialValue) - const callbackRef = useRef(callback) - - callbackRef.current = callback + const onChange = useEffectEvent(callback) + // Runs once on mount and again whenever a caller-provided dep changes; reads the + // latest refs rather than re-subscribing to state. useEffect(() => { - callbackRef.current(stateRef.current, prevStateRef.current) - }, [initialValue, ...deps]) + onChange(stateRef.current, prevStateRef.current) + }, [onChange, ...deps]) function set(value: T | ((prev: T) => T)) { if (typeof value === 'function') { - // @ts-ignore - const nextValue = value(stateRef.current) - callbackRef.current(nextValue, stateRef.current) + const nextValue = (value as (prev: T) => T)(stateRef.current) + onChange(nextValue, stateRef.current) + prevStateRef.current = stateRef.current stateRef.current = nextValue return } if (value !== stateRef.current) { - callbackRef.current(value, stateRef.current) + onChange(value, stateRef.current) + prevStateRef.current = stateRef.current stateRef.current = value } } diff --git a/packages/react/src/use-media-query/README.md b/packages/hamo/src/use-media-query/README.md similarity index 100% rename from packages/react/src/use-media-query/README.md rename to packages/hamo/src/use-media-query/README.md diff --git a/packages/hamo/src/use-media-query/index.test.tsx b/packages/hamo/src/use-media-query/index.test.tsx new file mode 100644 index 0000000..f4a335d --- /dev/null +++ b/packages/hamo/src/use-media-query/index.test.tsx @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'bun:test' +import { act, renderHook } from '@testing-library/react' +import { useMediaQuery } from './index' + +describe('useMediaQuery (smoke)', () => { + it('mounts without throwing and returns boolean or undefined', () => { + const { result, unmount } = renderHook(() => + useMediaQuery('(min-width: 768px)') + ) + + const isMatch = result.current + expect(typeof isMatch === 'boolean' || isMatch === undefined).toBe(true) + + expect(() => unmount()).not.toThrow() + }) + + it('updates when the query string changes', () => { + const { result, rerender, unmount } = renderHook( + ({ q }) => useMediaQuery(q), + { initialProps: { q: '(min-width: 768px)' } } + ) + + expect(() => { + act(() => { + rerender({ q: '(prefers-color-scheme: dark)' }) + }) + }).not.toThrow() + + const isMatch = result.current + expect(typeof isMatch === 'boolean' || isMatch === undefined).toBe(true) + + unmount() + }) +}) diff --git a/packages/react/src/use-media-query/index.ts b/packages/hamo/src/use-media-query/index.ts similarity index 96% rename from packages/react/src/use-media-query/index.ts rename to packages/hamo/src/use-media-query/index.ts index f7bfab4..053aee3 100644 --- a/packages/react/src/use-media-query/index.ts +++ b/packages/hamo/src/use-media-query/index.ts @@ -1,27 +1,29 @@ -import { useEffect, useState } from 'react' - -/** - * @name useMediaQuery - * @description A React hook that detects whether a media query is true or false. - * @param {string} query The media query to test against. - * @returns {boolean | undefined} Whether the media query is true or false (or undefined on ssr). - */ - -export function useMediaQuery(query: string) { - const [isMatch, setIsMatch] = useState() - - useEffect(() => { - const mediaQuery = window.matchMedia(query) - - function onChange() { - setIsMatch(mediaQuery.matches) - } - - mediaQuery.addEventListener('change', onChange, false) - onChange() - - return () => mediaQuery.removeEventListener('change', onChange, false) - }, [query]) - - return isMatch -} +'use client' + +import { useEffect, useState } from 'react' + +/** + * @name useMediaQuery + * @description A React hook that detects whether a media query is true or false. + * @param {string} query The media query to test against. + * @returns {boolean | undefined} Whether the media query is true or false (or undefined on ssr). + */ + +export function useMediaQuery(query: string) { + const [isMatch, setIsMatch] = useState() + + useEffect(() => { + const mediaQuery = window.matchMedia(query) + + function onChange() { + setIsMatch(mediaQuery.matches) + } + + mediaQuery.addEventListener('change', onChange, false) + onChange() + + return () => mediaQuery.removeEventListener('change', onChange, false) + }, [query]) + + return isMatch +} diff --git a/packages/react/src/use-object-fit/README.md b/packages/hamo/src/use-object-fit/README.md similarity index 100% rename from packages/react/src/use-object-fit/README.md rename to packages/hamo/src/use-object-fit/README.md diff --git a/packages/hamo/src/use-object-fit/index.test.ts b/packages/hamo/src/use-object-fit/index.test.ts new file mode 100644 index 0000000..497dc22 --- /dev/null +++ b/packages/hamo/src/use-object-fit/index.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from 'bun:test' +import { useObjectFit } from './index' + +describe('useObjectFit', () => { + it('returns [1, 1] when parentWidth is zero (guard branch)', () => { + // A zero dimension means no meaningful container — the hook must not + // divide by zero; returning [1, 1] is the documented safe fallback. + expect(useObjectFit(0, 100, 100, 100)).toEqual([1, 1]) + }) + + it('returns [1, 1] when parentHeight is zero (guard branch)', () => { + expect(useObjectFit(100, 0, 100, 100)).toEqual([1, 1]) + }) + + it('returns [1, 1] when childWidth is zero (guard branch)', () => { + expect(useObjectFit(100, 100, 0, 100)).toEqual([1, 1]) + }) + + it('returns [1, 1] when childHeight is zero (guard branch)', () => { + expect(useObjectFit(100, 100, 100, 0)).toEqual([1, 1]) + }) + + it('cover: scales to fill parent when child is wider than parent ratio', () => { + // parent 100×100 (ratio 1), child 200×100 (ratio 2). + // childRatio > parentRatio → width = parentHeight * childRatio = 200 + // height = 200 / 2 = 100 + // scaleX = width / parentWidth = 2, scaleY = height / parentHeight = 1 + expect(useObjectFit(100, 100, 200, 100, 'cover')).toEqual([2, 1]) + }) + + it('contain: scales to fit parent when child is wider than parent ratio', () => { + // parent 100×100 (ratio 1), child 200×100 (ratio 2). + // childRatio > parentRatio → width = parentWidth = 100 + // height = 100 / 2 = 50 + // scaleX = 100 / 100 = 1, scaleY = 50 / 100 = 0.5 + expect(useObjectFit(100, 100, 200, 100, 'contain')).toEqual([1, 0.5]) + }) + + it('cover: identical ratios produce [1, 1]', () => { + // parent and child same ratio → no scaling needed + expect(useObjectFit(200, 100, 400, 200, 'cover')).toEqual([1, 1]) + }) + + it('contain: identical ratios produce [1, 1]', () => { + expect(useObjectFit(200, 100, 400, 200, 'contain')).toEqual([1, 1]) + }) + + it('defaults to cover when objectFit is omitted', () => { + // Same geometry as the explicit cover test above + expect(useObjectFit(100, 100, 200, 100)).toEqual([2, 1]) + }) +}) diff --git a/packages/react/src/use-object-fit/index.ts b/packages/hamo/src/use-object-fit/index.ts similarity index 94% rename from packages/react/src/use-object-fit/index.ts rename to packages/hamo/src/use-object-fit/index.ts index 57148dd..c464ada 100644 --- a/packages/react/src/use-object-fit/index.ts +++ b/packages/hamo/src/use-object-fit/index.ts @@ -1,39 +1,37 @@ -import { useMemo } from 'react' - -/** - * @name useObjectFit - * @description - * A hook that allows you to calculate the scale of a child element based on the parent element and the object fit type. - * @param {number} parentWidth - The width of the parent element. - * @param {number} parentHeight - The height of the parent element. - * @param {number} childWidth - The width of the child element. - * @param {number} childHeight - The height of the child element. - * @param {string} objectFit - The object fit type. Can be 'contain' or 'cover'. - * @returns {array} [width, height] - The scale of the child element. - */ - -export function useObjectFit( - parentWidth = 1, - parentHeight = 1, - childWidth = 1, - childHeight = 1, - objectFit: 'contain' | 'cover' = 'cover' -) { - if (!parentWidth || !parentHeight || !childWidth || !childHeight) { - return [1, 1] - } - - const parentRatio = parentWidth / parentHeight - const childRatio = childWidth / childHeight - let width: number - if (objectFit === 'contain') { - width = parentRatio > childRatio ? parentHeight * childRatio : parentWidth - } else if (objectFit === 'cover') { - width = parentRatio > childRatio ? parentWidth : parentHeight * childRatio - } else { - return [1, 1] - } - const height = width / childRatio - - return [1 / (parentWidth / width), 1 / (parentHeight / height)] -} +/** + * @name useObjectFit + * @description + * A hook that allows you to calculate the scale of a child element based on the parent element and the object fit type. + * @param {number} parentWidth - The width of the parent element. + * @param {number} parentHeight - The height of the parent element. + * @param {number} childWidth - The width of the child element. + * @param {number} childHeight - The height of the child element. + * @param {string} objectFit - The object fit type. Can be 'contain' or 'cover'. + * @returns {array} [width, height] - The scale of the child element. + */ + +export function useObjectFit( + parentWidth = 1, + parentHeight = 1, + childWidth = 1, + childHeight = 1, + objectFit: 'contain' | 'cover' = 'cover' +) { + if (!parentWidth || !parentHeight || !childWidth || !childHeight) { + return [1, 1] + } + + const parentRatio = parentWidth / parentHeight + const childRatio = childWidth / childHeight + let width: number + if (objectFit === 'contain') { + width = parentRatio > childRatio ? parentHeight * childRatio : parentWidth + } else if (objectFit === 'cover') { + width = parentRatio > childRatio ? parentWidth : parentHeight * childRatio + } else { + return [1, 1] + } + const height = width / childRatio + + return [1 / (parentWidth / width), 1 / (parentHeight / height)] +} diff --git a/packages/react/src/use-rect/README.md b/packages/hamo/src/use-rect/README.md similarity index 100% rename from packages/react/src/use-rect/README.md rename to packages/hamo/src/use-rect/README.md diff --git a/packages/hamo/src/use-rect/emitter.ts b/packages/hamo/src/use-rect/emitter.ts new file mode 100644 index 0000000..fb4d935 --- /dev/null +++ b/packages/hamo/src/use-rect/emitter.ts @@ -0,0 +1,26 @@ +type Listener = (...args: unknown[]) => void + +function createEmitter() { + const listeners = new Map>() + + return { + on(event: string, listener: Listener): () => void { + let set = listeners.get(event) + if (!set) { + set = new Set() + listeners.set(event, set) + } + set.add(listener) + return () => { + set?.delete(listener) + } + }, + emit(event: string, ...args: unknown[]): void { + const set = listeners.get(event) + if (!set) return + for (const listener of set) listener(...args) + }, + } +} + +export const emitter = createEmitter() diff --git a/packages/hamo/src/use-rect/index.test.tsx b/packages/hamo/src/use-rect/index.test.tsx new file mode 100644 index 0000000..0a0401e --- /dev/null +++ b/packages/hamo/src/use-rect/index.test.tsx @@ -0,0 +1,49 @@ +import { describe, expect, it } from 'bun:test' +import { act, renderHook } from '@testing-library/react' +import { useRect } from './index' + +describe('useRect (smoke)', () => { + it('mounts without throwing and returns a [setRef, rect, setWrapperRef] tuple', () => { + const { result, unmount } = renderHook(() => useRect()) + + expect(result.current).toHaveLength(3) + const [setRef, rect, setWrapperRef] = result.current + expect(typeof setRef).toBe('function') + expect(typeof rect).toBe('object') + expect(typeof setWrapperRef).toBe('function') + + expect(() => unmount()).not.toThrow() + }) + + it('lazy mode: second element is a getter function', () => { + const { result, unmount } = renderHook(() => useRect({ lazy: true })) + + const [, getRect] = result.current + expect(typeof getRect).toBe('function') + + expect(() => unmount()).not.toThrow() + }) + + it('setRef with null does not throw', () => { + const { result } = renderHook(() => useRect()) + const [setRef] = result.current + + expect(() => { + act(() => { + setRef(null) + }) + }).not.toThrow() + }) + + it('accepts all options without throwing', () => { + const { unmount } = renderHook(() => + useRect({ + ignoreTransform: true, + ignoreSticky: false, + debounce: 100, + callback: () => {}, + }) + ) + expect(() => unmount()).not.toThrow() + }) +}) diff --git a/packages/react/src/use-rect/index.ts b/packages/hamo/src/use-rect/index.ts similarity index 88% rename from packages/react/src/use-rect/index.ts rename to packages/hamo/src/use-rect/index.ts index 34f64f4..b9eea26 100644 --- a/packages/react/src/use-rect/index.ts +++ b/packages/hamo/src/use-rect/index.ts @@ -1,254 +1,259 @@ -import { useEffect, useCallback, useRef, useState } from 'react' -import { - addParentSticky, - offsetLeft, - offsetTop, - removeParentSticky, - scrollLeft, - scrollTop, -} from './utils' -import { emitter } from './emitter' -import { useResizeObserver } from '../use-resize-observer' - -export type Rect = { - top?: number - y?: number - left?: number - x?: number - width?: number - height?: number - bottom?: number - right?: number - resize?: () => void - element?: HTMLElement | null -} - -let defaultDebounceDelay = 500 - -function setDebounce(delay: number) { - defaultDebounceDelay = delay -} - -/** - * @name useRect - * @description - * A hook that allows you to get the bounding client rect of an element. - * @param {object} options - The options for the hook. - * @param {boolean} options.ignoreTransform - Whether to ignore the transform property of the element. - * @param {boolean} options.ignoreSticky - Whether to ignore the sticky property of the element. - * @param {number} options.debounce - The debounce delay (in milliseconds) before the callback function is executed. - * @param {boolean} options.lazy - Whether to lazy load the rect. - * @param {function} options.callback - The callback function to be executed after the rect is updated. - * @param {array} deps - The dependencies array for the hook. - * @function resize - A function that allows you to manually trigger the rect update. - * @function setDebounce - A function that allows you to set the debounce delay. - * @returns {array} [setElementRef, options.lazy ? getRectRef : rect, setWrapperElementRef] - */ - -export function useRect( - { - ignoreTransform = false, - ignoreSticky = true, - debounce: debounceDelay = defaultDebounceDelay, - lazy = false as L, - callback, - }: { - ignoreTransform?: boolean - ignoreSticky?: boolean - debounce?: number - lazy?: L - callback?: (rect: Rect) => void - } = {}, - deps: any[] = [] -): [ - (element: HTMLElement | null) => void, - L extends true ? () => Rect : Rect, - (element: HTMLElement | null) => void, -] { - const [wrapperElement, setWrapperElement] = useState(null) - const [element, setElement] = useState(null) - - const callbackRef = useRef(callback) - callbackRef.current = callback - - const updateRect = useCallback( - ({ - top, - left, - width, - height, - element, - }: { - top?: number - left?: number - width?: number - height?: number - element?: HTMLElement | null - }) => { - top = top ?? rectRef.current.top - left = left ?? rectRef.current.left - width = width ?? rectRef.current.width - height = height ?? rectRef.current.height - element = element ?? rectRef.current.element - - if ( - top === rectRef.current.top && - left === rectRef.current.left && - width === rectRef.current.width && - height === rectRef.current.height && - element === rectRef.current.element - ) - return - - const y = top - const x = left - let bottom: number | undefined - let right: number | undefined - - if (top !== undefined && height !== undefined) bottom = top + height - if (left !== undefined && width !== undefined) right = left + width - - rectRef.current = { - ...rectRef.current, - top, - y, - left, - x, - width, - height, - bottom, - right, - element, - } - - callbackRef.current?.(rectRef.current) - - if (!lazy) { - setRect(rectRef.current) - } - }, - [lazy, ...deps] - ) - - const computeCoordinates = useCallback(() => { - if (!element || !wrapperElement) return - - let top: number - let left: number - - if (ignoreSticky) removeParentSticky(element) - if (ignoreTransform) { - top = offsetTop(element) - left = offsetLeft(element) - } else { - const rect = element.getBoundingClientRect() - - top = rect.top + scrollTop(wrapperElement) - left = rect.left + scrollLeft(wrapperElement) - } - if (ignoreSticky) addParentSticky(element) - - updateRect({ - top, - left, - }) - }, [element, ignoreSticky, ignoreTransform, wrapperElement, updateRect]) - - const computeDimensions = useCallback(() => { - if (!element) return - - const rect = element.getBoundingClientRect() - - const width = rect.width - const height = rect.height - - updateRect({ width, height }) - }, [element, updateRect]) - - const resize = useCallback(() => { - computeCoordinates() - computeDimensions() - }, [computeCoordinates, computeDimensions]) - - const rectRef = useRef({} as Rect) - const [rect, setRect] = useState({} as Rect) - - useEffect(() => { - rectRef.current.resize = resize - setRect(rectRef.current) - - return emitter.on('resize', resize) - }, [resize]) - - const [setResizeObserverRef] = useResizeObserver( - { - lazy: true, - debounce: debounceDelay, - callback: (entry?: ResizeObserverEntry) => { - if (!entry) return - - const { inlineSize: width, blockSize: height } = - entry.borderBoxSize[0] ?? {} - - updateRect({ - width, - height, - }) - }, - }, - [element, lazy, updateRect] - ) - - const [setWrapperResizeObserverRef] = useResizeObserver( - { - lazy: true, - debounce: debounceDelay, - callback: computeCoordinates, - }, - [computeCoordinates] - ) - - useEffect(() => { - // @ts-ignore - setWrapperResizeObserverRef((v) => { - if (v && v !== document.body) return v - return document.body - }) - - setWrapperElement((v) => { - if (v && v !== document.body) return v - return document.body - }) - }, [setWrapperResizeObserverRef]) - - const getRectRef = useCallback(() => rectRef.current, []) - - const setElementRef = useCallback( - (node: HTMLElement | null) => { - setResizeObserverRef(node) - setElement(node) - updateRect({ - element: node, - }) - }, - [setResizeObserverRef, updateRect] - ) - - const setWrapperElementRef = useCallback( - (node: HTMLElement | null) => { - setWrapperResizeObserverRef(node) - setWrapperElement(node) - }, - [setWrapperResizeObserverRef] - ) - - return [setElementRef, lazy ? getRectRef : rect, setWrapperElementRef] as [ - typeof setElementRef, - L extends true ? () => Rect : Rect, - typeof setWrapperElementRef, - ] -} - -useRect.resize = () => emitter.emit('resize') - -useRect.setDebounce = setDebounce +'use client' + +import { + type DependencyList, + useCallback, + useEffect, + useRef, + useState, +} from 'react' +import { createDebounceConfig } from '../debounce-config' +import { useEffectEvent } from '../use-effect-event' +import { useResizeObserver } from '../use-resize-observer' +import { emitter } from './emitter' +import { + addParentSticky, + offsetLeft, + offsetTop, + removeParentSticky, + scrollLeft, + scrollTop, +} from './utils' + +export type Rect = { + top?: number + y?: number + left?: number + x?: number + width?: number + height?: number + bottom?: number + right?: number + resize?: () => void + element?: HTMLElement | null +} + +const rectDebounce = createDebounceConfig() + +/** + * @name useRect + * @description + * A hook that allows you to get the bounding client rect of an element. + * @param {object} options - The options for the hook. + * @param {boolean} options.ignoreTransform - Whether to ignore the transform property of the element. + * @param {boolean} options.ignoreSticky - Whether to ignore the sticky property of the element. + * @param {number} options.debounce - The debounce delay (in milliseconds) before the callback function is executed. + * @param {boolean} options.lazy - Whether to lazy load the rect. + * @param {function} options.callback - The callback function to be executed after the rect is updated. + * @param {array} deps - The dependencies array for the hook. + * @function resize - A function that allows you to manually trigger the rect update. + * @function setDebounce - A function that allows you to set the debounce delay. + * @returns {array} [setElementRef, options.lazy ? getRectRef : rect, setWrapperElementRef] + */ + +export function useRect( + { + ignoreTransform = false, + ignoreSticky = true, + debounce: debounceDelay = rectDebounce.getDelay(), + lazy = false as L, + callback, + }: { + ignoreTransform?: boolean + ignoreSticky?: boolean + debounce?: number + lazy?: L + callback?: (rect: Rect) => void + } = {}, + deps: DependencyList = [] +): [ + (element: HTMLElement | null) => void, + L extends true ? () => Rect : Rect, + (element: HTMLElement | null) => void, +] { + const [wrapperElement, setWrapperElement] = useState(null) + const [element, setElement] = useState(null) + + const onUpdate = useEffectEvent((rect: Rect) => callback?.(rect)) + + const updateRect = useCallback( + ({ + top, + left, + width, + height, + element, + }: { + top?: number + left?: number + width?: number + height?: number + element?: HTMLElement | null + }) => { + top = top ?? rectRef.current.top + left = left ?? rectRef.current.left + width = width ?? rectRef.current.width + height = height ?? rectRef.current.height + element = element ?? rectRef.current.element + + if ( + top === rectRef.current.top && + left === rectRef.current.left && + width === rectRef.current.width && + height === rectRef.current.height && + element === rectRef.current.element + ) + return + + const y = top + const x = left + let bottom: number | undefined + let right: number | undefined + + if (top !== undefined && height !== undefined) bottom = top + height + if (left !== undefined && width !== undefined) right = left + width + + rectRef.current = { + ...rectRef.current, + top, + y, + left, + x, + width, + height, + bottom, + right, + element, + } + + onUpdate(rectRef.current) + + if (!lazy) { + setRect(rectRef.current) + } + }, + [onUpdate, lazy, ...deps] + ) + + const computeCoordinates = useCallback(() => { + if (!element || !wrapperElement) return + + let top: number + let left: number + + if (ignoreSticky) removeParentSticky(element) + if (ignoreTransform) { + top = offsetTop(element) + left = offsetLeft(element) + } else { + const rect = element.getBoundingClientRect() + + top = rect.top + scrollTop(wrapperElement) + left = rect.left + scrollLeft(wrapperElement) + } + if (ignoreSticky) addParentSticky(element) + + updateRect({ + top, + left, + }) + }, [element, ignoreSticky, ignoreTransform, wrapperElement, updateRect]) + + const computeDimensions = useCallback(() => { + if (!element) return + + const rect = element.getBoundingClientRect() + + const width = rect.width + const height = rect.height + + updateRect({ width, height }) + }, [element, updateRect]) + + const resize = useCallback(() => { + computeCoordinates() + computeDimensions() + }, [computeCoordinates, computeDimensions]) + + const rectRef = useRef({}) + const [rect, setRect] = useState({}) + + useEffect(() => { + rectRef.current.resize = resize + setRect(rectRef.current) + + return emitter.on('resize', resize) + }, [resize]) + + const [setResizeObserverRef] = useResizeObserver( + { + lazy: true, + debounce: debounceDelay, + callback: (entry?: ResizeObserverEntry) => { + if (!entry) return + + const { inlineSize: width, blockSize: height } = + entry.borderBoxSize[0] ?? {} + + updateRect({ + width, + height, + }) + }, + }, + [element, lazy, updateRect] + ) + + const [setWrapperResizeObserverRef] = useResizeObserver( + { + lazy: true, + debounce: debounceDelay, + callback: computeCoordinates, + }, + [computeCoordinates] + ) + + useEffect(() => { + // @ts-expect-error setWrapperResizeObserverRef accepts a state updater at runtime + setWrapperResizeObserverRef((v) => { + if (v && v !== document.body) return v + return document.body + }) + + setWrapperElement((v) => { + if (v && v !== document.body) return v + return document.body + }) + }, [setWrapperResizeObserverRef]) + + const getRectRef = useCallback(() => rectRef.current, []) + + const setElementRef = useCallback( + (node: HTMLElement | null) => { + setResizeObserverRef(node) + setElement(node) + updateRect({ + element: node, + }) + }, + [setResizeObserverRef, updateRect] + ) + + const setWrapperElementRef = useCallback( + (node: HTMLElement | null) => { + setWrapperResizeObserverRef(node) + setWrapperElement(node) + }, + [setWrapperResizeObserverRef] + ) + + return [setElementRef, lazy ? getRectRef : rect, setWrapperElementRef] as [ + typeof setElementRef, + L extends true ? () => Rect : Rect, + typeof setWrapperElementRef, + ] +} + +useRect.resize = () => emitter.emit('resize') + +useRect.setDebounce = rectDebounce.setDebounce diff --git a/packages/react/src/use-rect/utils.ts b/packages/hamo/src/use-rect/utils.ts similarity index 100% rename from packages/react/src/use-rect/utils.ts rename to packages/hamo/src/use-rect/utils.ts diff --git a/packages/react/src/use-resize-observer/README.md b/packages/hamo/src/use-resize-observer/README.md similarity index 100% rename from packages/react/src/use-resize-observer/README.md rename to packages/hamo/src/use-resize-observer/README.md diff --git a/packages/hamo/src/use-resize-observer/index.test.tsx b/packages/hamo/src/use-resize-observer/index.test.tsx new file mode 100644 index 0000000..d478b5f --- /dev/null +++ b/packages/hamo/src/use-resize-observer/index.test.tsx @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'bun:test' +import { act, renderHook } from '@testing-library/react' +import { useResizeObserver } from './index' + +describe('useResizeObserver (smoke)', () => { + it('mounts without throwing and returns a [setRef, entry] tuple', () => { + const { result, unmount } = renderHook(() => useResizeObserver()) + + const [setRef, entry] = result.current + expect(typeof setRef).toBe('function') + // Non-lazy: entry is undefined until an element is observed + expect(entry).toBeUndefined() + + expect(() => unmount()).not.toThrow() + }) + + it('lazy mode: second element of tuple is a getter function', () => { + const { result, unmount } = renderHook(() => + useResizeObserver({ lazy: true }) + ) + + const [setRef, getEntry] = result.current + expect(typeof setRef).toBe('function') + expect(typeof getEntry).toBe('function') + // Getter returns undefined before any observation + expect((getEntry as () => unknown)()).toBeUndefined() + + expect(() => unmount()).not.toThrow() + }) + + it('setRef with null does not throw', () => { + const { result } = renderHook(() => useResizeObserver()) + const [setRef] = result.current + + expect(() => { + act(() => { + setRef(null) + }) + }).not.toThrow() + }) + + it('accepts a callback option without throwing', () => { + const { unmount } = renderHook(() => + useResizeObserver({ callback: () => {} }) + ) + expect(() => unmount()).not.toThrow() + }) +}) diff --git a/packages/react/src/use-resize-observer/index.ts b/packages/hamo/src/use-resize-observer/index.ts similarity index 80% rename from packages/react/src/use-resize-observer/index.ts rename to packages/hamo/src/use-resize-observer/index.ts index a4c906e..651aef1 100644 --- a/packages/react/src/use-resize-observer/index.ts +++ b/packages/hamo/src/use-resize-observer/index.ts @@ -1,142 +1,145 @@ -import { - type DependencyList, - useCallback, - useEffect, - useRef, - useState, -} from 'react' -import { debounce } from '../use-debounce' - -let defaultDebounceDelay = 500 - -function setDebounce(delay: number) { - defaultDebounceDelay = delay -} - -/** - * @name useResizeObserver - * @description A React hook that listens to element size changes. - * @param {object} options - The options for the hook. - * @param {boolean} options.lazy - If true, the resize observer will not trigger state changes. - * @param {number} options.debounce - The delay (in milliseconds) before the resize event is processed. This helps to optimize performance by reducing the number of times the callback function is called during resizing. Alternatively, you can set the global `useResizeObserver.setDebounce` function to change the default debounce delay. - * @param {function} options.callback - The callback function to call when the element size changes. - * @param {array} deps - The dependencies to be used in the callback function. - * @function setDebounce - A function that allows you to set the debounce delay. - * @returns {array} [setResizeObserverRef, options.lazy ? getEntryRef : entry] - */ - -const callbacksMap = new Map< - Element, - ((entry: ResizeObserverEntry) => void)[] ->() - -let sharedObserver: ResizeObserver | null = null -function getSharedObserver() { - if (!sharedObserver) { - sharedObserver = new ResizeObserver((entries) => { - for (const entry of entries) { - const callbacks = callbacksMap.get(entry.target) - if (callbacks) { - for (const cb of callbacks) { - cb(entry) - } - } - } - }) - } - return sharedObserver -} - -function observeElement( - el: Element, - callback: (entry: ResizeObserverEntry) => void, - debounceDelay: number = defaultDebounceDelay -) { - if (!el) return () => {} - - let first = true - const debouncedCallback = debounce(callback, debounceDelay) - const wrappedCallback = (entry: ResizeObserverEntry) => { - if (first) { - first = false - callback(entry) - } else { - debouncedCallback(entry) - } - } - - const callbacks = callbacksMap.get(el) || [] - callbacks.push(wrappedCallback) - callbacksMap.set(el, callbacks) - const sharedObserver = getSharedObserver() - sharedObserver.observe(el) - - return () => { - const callbacks = callbacksMap.get(el) - if (callbacks) { - const index = callbacks.indexOf(wrappedCallback) - if (index > -1) { - callbacks.splice(index, 1) - } - if (callbacks.length === 0) { - callbacksMap.delete(el) - sharedObserver.unobserve(el) - } - } - if (callbacksMap.size === 0) { - sharedObserver.disconnect() - } - } -} - -export function useResizeObserver( - { - lazy = false as L, - debounce: debounceDelay = defaultDebounceDelay, - callback = () => {}, - }: { - lazy?: L - debounce?: number - callback?: (entry: ResizeObserverEntry) => void - } = {}, - deps: DependencyList = [] -): [ - (element: HTMLElement | null) => void, - L extends true - ? () => ResizeObserverEntry | undefined - : ResizeObserverEntry | undefined, -] { - const [element, setElement] = useState() - const [entry, setEntry] = useState() - const entryRef = useRef() - - const callbackRef = useRef(callback) - callbackRef.current = callback - - useEffect(() => { - if (!element) return - - return observeElement( - element, - (entry: ResizeObserverEntry) => { - callbackRef.current(entry) - entryRef.current = entry - if (!lazy) { - setEntry(entry) - } - }, - debounceDelay - ) - }, [element, debounceDelay, lazy, ...deps]) - - const getEntryRef = useCallback(() => entryRef.current, []) - - return [setElement, lazy ? getEntryRef : entry] as [ - typeof setElement, - L extends true - ? () => ResizeObserverEntry | undefined - : ResizeObserverEntry | undefined, - ] -} - -useResizeObserver.setDebounce = setDebounce +'use client' + +import { + type DependencyList, + useCallback, + useEffect, + useRef, + useState, +} from 'react' +import { createDebounceConfig } from '../debounce-config' +import { debounce } from '../use-debounce' +import { useEffectEvent } from '../use-effect-event' + +const resizeObserverDebounce = createDebounceConfig() + +/** + * @name useResizeObserver + * @description A React hook that listens to element size changes. + * @param {object} options - The options for the hook. + * @param {boolean} options.lazy - If true, the resize observer will not trigger state changes. + * @param {number} options.debounce - The delay (in milliseconds) before the resize event is processed. This helps to optimize performance by reducing the number of times the callback function is called during resizing. Alternatively, you can set the global `useResizeObserver.setDebounce` function to change the default debounce delay. + * @param {function} options.callback - The callback function to call when the element size changes. + * @param {array} deps - The dependencies to be used in the callback function. + * @function setDebounce - A function that allows you to set the debounce delay. + * @returns {array} [setResizeObserverRef, options.lazy ? getEntryRef : entry] + */ + +const callbacksMap = new Map< + Element, + ((entry: ResizeObserverEntry) => void)[] +>() + +let sharedObserver: ResizeObserver | null = null +function getSharedObserver() { + if (!sharedObserver) { + sharedObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + const callbacks = callbacksMap.get(entry.target) + if (callbacks) { + for (const cb of callbacks) { + cb(entry) + } + } + } + }) + } + return sharedObserver +} + +function observeElement( + el: Element, + callback: (entry: ResizeObserverEntry) => void, + debounceDelay: number = resizeObserverDebounce.getDelay() +) { + if (!el) return () => {} + + let first = true + const debouncedCallback = debounce(callback, debounceDelay) + const wrappedCallback = (entry: ResizeObserverEntry) => { + if (first) { + first = false + callback(entry) + } else { + debouncedCallback(entry) + } + } + + const callbacks = callbacksMap.get(el) || [] + callbacks.push(wrappedCallback) + callbacksMap.set(el, callbacks) + const observer = getSharedObserver() + observer.observe(el) + + return () => { + debouncedCallback.cancel() + const callbacks = callbacksMap.get(el) + if (callbacks) { + const index = callbacks.indexOf(wrappedCallback) + if (index > -1) { + callbacks.splice(index, 1) + } + if (callbacks.length === 0) { + callbacksMap.delete(el) + observer.unobserve(el) + } + } + if (callbacksMap.size === 0) { + observer.disconnect() + // Reset so a later observe re-creates the observer instead of reusing a + // disconnected one. + sharedObserver = null + } + } +} + +export function useResizeObserver( + { + lazy = false as L, + debounce: debounceDelay = resizeObserverDebounce.getDelay(), + callback = () => {}, + }: { + lazy?: L + debounce?: number + callback?: (entry: ResizeObserverEntry) => void + } = {}, + deps: DependencyList = [] +): [ + (element: HTMLElement | null) => void, + L extends true + ? () => ResizeObserverEntry | undefined + : ResizeObserverEntry | undefined, +] { + const [element, setElement] = useState() + const [entry, setEntry] = useState() + const entryRef = useRef(undefined) + + const onResize = useEffectEvent(callback) + + useEffect(() => { + if (!element) return + + return observeElement( + element, + (entry: ResizeObserverEntry) => { + onResize(entry) + entryRef.current = entry + if (!lazy) { + setEntry(entry) + } + }, + debounceDelay + ) + }, [element, debounceDelay, lazy, onResize, ...deps]) + + const getEntryRef = useCallback(() => entryRef.current, []) + + return [setElement, lazy ? getEntryRef : entry] as [ + typeof setElement, + L extends true + ? () => ResizeObserverEntry | undefined + : ResizeObserverEntry | undefined, + ] +} + +useResizeObserver.setDebounce = resizeObserverDebounce.setDebounce diff --git a/packages/hamo/src/use-scroll-trigger/README.md b/packages/hamo/src/use-scroll-trigger/README.md new file mode 100644 index 0000000..0f27a85 --- /dev/null +++ b/packages/hamo/src/use-scroll-trigger/README.md @@ -0,0 +1,369 @@ +# useScrollTrigger + +A high-performance, transparent scroll progress tracker for React. + +GSAP ScrollTrigger is powerful but it's a black box — you hand it an element and hope for the best. `useScrollTrigger` gives you the same scroll-triggered progress tracking with full visibility into how it works, what it reads, and when it fires. + +## Philosophy + +Every design decision serves performance and transparency: + +- **No per-frame DOM reads.** Element positions are computed once (via `useRect`) and cached. GSAP calls `getBoundingClientRect()` on every scroll frame for every trigger — that forces layout recalculation and kills performance on pages with dozens of scroll-driven elements. +- **No React re-renders.** Progress updates flow through `useLazyState` and refs, never through `setState`. Your scroll callbacks fire at 60fps without touching the React render cycle. +- **No magic.** You get `progress`, `direction`, `isActive`, and `steps` — raw values you wire up yourself. No hidden timeline binding, no implicit CSS changes, no side effects you didn't ask for. +- **Composable.** Built on `useRect`, `useLazyState`, `useEffectEvent`, and `useTransform` — hooks you can use independently. If `useScrollTrigger` doesn't do what you want, you have the building blocks to make your own. + +### TransformProvider: the trade-off + +Because positions are cached (not read per-frame), programmatic transforms on a parent element (like parallax `translateY`) would make the cached position stale. `TransformProvider` solves this — you tell it what you moved, and child scroll triggers compensate automatically. + +This is an explicit trade-off: one extra component wrapper in exchange for zero layout thrashing. On a scrollytelling page with 20+ triggers, this is the difference between smooth and janky. + +## Usage + +```jsx +import { useScrollTrigger } from 'hamo' + +function FadeIn() { + const elementRef = useRef(null) + + const [setRef] = useScrollTrigger({ + onProgress: ({ progress }) => { + elementRef.current.style.opacity = `${progress}` + }, + }) + + return ( +
{ elementRef.current = node; setRef(node) }}> + Fades in as you scroll +
+ ) +} +``` + +## Parameters + +### Options + +- `start`: (string, default: `'bottom bottom'`) Start position: `"element-position viewport-position"`. +- `end`: (string, default: `'top top'`) End position: `"element-position viewport-position"`. +- `offset`: (number, default: `0`) Pixel offset added to element positions. +- `disabled`: (boolean, default: `false`) Disables the scroll trigger. +- `onEnter`: (function) Called when entering the trigger zone. Receives `{ progress, direction }`. +- `onLeave`: (function) Called when leaving the trigger zone. Receives `{ progress, direction }`. +- `onProgress`: (function) Called on every scroll update. Receives `{ height, isActive, progress, lastProgress, direction, steps }`. +- `steps`: (number, default: `1`) Subdivides progress into N discrete sub-ranges. +- `debug`: (boolean | string, default: `false`) Registers the trigger in the debug store. Pass a string to use as label in the Debugger minimap. +- `rect`: (Rect) External rect from `useRect` — pass this to share a single `useRect` across multiple triggers on the same element. + +### Dependencies + +- `deps`: (array, default: `[]`) Dependencies that trigger recalculation. + +## Return Value + +Returns `[setRef, rect]`: + +1. `setRef` — Callback ref to attach to the target element. +2. `rect` — The element's current rect (from `useRect` internally). + +## Position Syntax + +Format: `"element-position viewport-position"` + +| Keyword | Element | Viewport | +|---------|---------|----------| +| `top` | Top edge | Top of viewport | +| `center` | Vertical center | Center of viewport | +| `bottom` | Bottom edge | Bottom of viewport | +| `number` | Pixel offset from top | Pixel offset from top | + +### Common Combinations + +| Start / End | Description | +|-------------|-------------| +| `'bottom bottom'` / `'top top'` | Full element traversal (default) | +| `'bottom bottom'` / `'top center'` | From entering viewport to reaching center | +| `'center center'` / `'top top'` | From center alignment to reaching top | + +## onProgress Callback Data + +| Property | Type | Description | +|----------|------|-------------| +| `progress` | `number` | Clamped progress from 0 to 1 | +| `lastProgress` | `number` | Previous progress value | +| `direction` | `1 \| -1` | Scroll direction (1 = down, -1 = up) | +| `isActive` | `boolean` | Whether progress is between 0 and 1 | +| `height` | `number` | Scroll distance in pixels between start and end | +| `steps` | `number[]` | Array of per-step progress values | + +## How It Works + +``` +useScrollTrigger + ├── useRect() → computes element position once, caches it + ├── useWindowSize() → viewport height for position keywords + ├── useTransform() → reads parent transform offset (if any) + ├── useLenis() → subscribes to Lenis scroll (or falls back to native) + ├── useLazyState() → tracks progress without re-renders + └── useEffectEvent() → stable callbacks, no effect churn + +On every scroll tick: + 1. Read scroll position (from Lenis or window.scrollY) + 2. Subtract parent transform offset (from TransformProvider) + 3. Map scroll into [0, 1] progress using cached element position + 4. Fire onEnter/onLeave/onProgress if progress changed + 5. Zero DOM reads. Zero re-renders. +``` + +## Examples + +### Enter / Leave with Direction + +```jsx +import { useScrollTrigger } from 'hamo' + +function Section() { + const [setRef] = useScrollTrigger({ + onEnter: ({ direction }) => { + console.log(direction === 1 ? 'Entered scrolling down' : 'Entered scrolling up') + }, + onLeave: ({ direction }) => { + console.log(direction === 1 ? 'Left scrolling down' : 'Left scrolling up') + }, + }) + + return
Tracked section
+} +``` + +### Steps for Staggered Animations + +```jsx +import { useRef } from 'react' +import { useScrollTrigger } from 'hamo' + +function StaggeredList() { + const itemRefs = useRef([]) + + const [setRef] = useScrollTrigger({ + steps: 5, + onProgress: ({ steps }) => { + steps.forEach((stepProgress, i) => { + if (itemRefs.current[i]) { + itemRefs.current[i].style.opacity = `${stepProgress}` + itemRefs.current[i].style.transform = `translateY(${(1 - stepProgress) * 20}px)` + } + }) + }, + }) + + return ( +
    + {Array.from({ length: 5 }).map((_, i) => ( +
  • { itemRefs.current[i] = el }}> + Item {i + 1} +
  • + ))} +
+ ) +} +``` + +### With Lenis + +When Lenis is installed, the hook automatically uses it. No configuration needed. + +```jsx +import { ReactLenis } from 'lenis/react' +import { useScrollTrigger } from 'hamo' + +function App() { + return ( + + + + ) +} + +function AnimatedSection() { + const elementRef = useRef(null) + + const [setRef] = useScrollTrigger({ + onProgress: ({ progress }) => { + elementRef.current.style.transform = `translateX(${progress * 100}px)` + }, + }) + + return ( +
{ elementRef.current = node; setRef(node) }}> + Slides in with smooth scroll +
+ ) +} +``` + +### CSS Custom Property + +```jsx +import { useScrollTrigger } from 'hamo' + +function ParallaxSection() { + const elementRef = useRef(null) + + const [setRef] = useScrollTrigger({ + onProgress: ({ progress }) => { + elementRef.current.style.setProperty('--progress', `${progress}`) + }, + }) + + return ( +
{ elementRef.current = node; setRef(node) }} + style={{ transform: 'translateY(calc(var(--progress, 0) * -50px))' }} + > + Parallax content +
+ ) +} +``` + +### With TransformProvider (Parallax Compensation) + +When a parent is translated programmatically, child scroll triggers need to know. Wrap the parent in `TransformProvider` and report your transforms — children compensate automatically. + +```jsx +import { useRef } from 'react' +import { useScrollTrigger, TransformProvider } from 'hamo' +import type { TransformRef } from 'hamo' + +function ParallaxWrapper({ children }) { + const transformRef = useRef(null) + const elementRef = useRef(null) + + const [setRef] = useScrollTrigger({ + onProgress: ({ progress }) => { + const y = (progress - 0.5) * -100 + transformRef.current?.setTranslate(0, y) + elementRef.current.style.transform = `translateY(${y}px)` + }, + }) + + return ( + +
{ elementRef.current = node; setRef(node) }}> + {children} +
+
+ ) +} + +function ChildTrigger() { + const [setRef] = useScrollTrigger({ + onProgress: ({ progress }) => { + // Accurate despite parent parallax — no getBoundingClientRect needed + console.log('progress:', progress) + }, + }) + + return
Child content
+} + +function Page() { + return ( + + + + ) +} +``` + +### Progressive Text Reveal + +```jsx +import { useRef } from 'react' +import { useScrollTrigger } from 'hamo' + +function TextReveal({ text }) { + const containerRef = useRef(null) + const words = text.split(' ') + + const [setRef] = useScrollTrigger({ + start: 'bottom bottom', + end: 'center center', + steps: words.length, + onProgress: ({ steps }) => { + if (!containerRef.current) return + const spans = containerRef.current.querySelectorAll('span') + spans.forEach((span, i) => { + span.style.opacity = `${steps[i]}` + }) + }, + }) + + return ( +

{ containerRef.current = node; setRef(node) }}> + {words.map((word, i) => ( + + {word}{' '} + + ))} +

+ ) +} +``` + +## Debugger + +A minimap overlay that visualizes all active scroll triggers on the page. Each trigger with `debug` enabled appears as a colored rectangle (element position) and a bar (start/end range). Hover a rectangle to see a tooltip with the trigger's id, start/end positions, progress, and active state. + +```jsx +import { Debugger } from 'hamo/scroll-trigger' + +function App() { + return ( + <> + + {/* your content */} + + ) +} +``` + +### Props + +- `theme`: (`'light' | 'dark'`, default: `'dark'`) Color theme. + +### How it works + +Triggers with `debug` enabled register themselves in a shared store. The Debugger subscribes to that store and renders a fixed minimap that: + +- Mirrors the page body shape using the body's aspect ratio +- Scrolls in sync via a CSS custom property (`--p`) +- Shows each trigger's element as a colored rectangle, offset by `translateY` from `TransformProvider` +- Shows each trigger's start/end scroll range as a colored bar aligned to its element +- Displays a tooltip on hover with trigger details (id, start, end, progress, active) + +### Enabling debug on a trigger + +```jsx +const [setRef] = useScrollTrigger({ + start: 'bottom bottom', + end: 'top top', + debug: 'my-section', // label shown in tooltip + onProgress: ({ progress }) => { /* ... */ }, +}) +``` + +## vs GSAP ScrollTrigger + +| | GSAP ScrollTrigger | useScrollTrigger | +|---|---|---| +| DOM reads per frame | `getBoundingClientRect()` on every tick | Zero — positions cached via `useRect` | +| React re-renders | N/A (not React-aware) | Zero — updates via refs and `useLazyState` | +| Lenis integration | Manual `scrollerProxy` setup | Automatic | +| Transform awareness | Implicit (reads DOM) | Explicit via `TransformProvider` | +| Bundle size | ~55KB (GSAP core + plugin) | ~1KB (inlined in hamo) | +| Pinning, scrub, snap | Yes | No — use GSAP for those | +| License | Custom (restrictions on some commercial use) | MIT | +| Transparency | Black box | Every hook is composable and inspectable | diff --git a/packages/hamo/src/use-scroll-trigger/debugger.tsx b/packages/hamo/src/use-scroll-trigger/debugger.tsx new file mode 100644 index 0000000..ac80775 --- /dev/null +++ b/packages/hamo/src/use-scroll-trigger/debugger.tsx @@ -0,0 +1,207 @@ +'use client' + +import { useLenis } from 'lenis/react' +import { useEffect, useRef, useState, useSyncExternalStore } from 'react' +import { useWindowSize } from '../use-window-size' +import { scrollTriggerStore } from './store' + +const COLORS = [ + '#3b82f6', + '#a855f7', + '#f59e0b', + '#14b8a6', + '#f97316', + '#ec4899', +] + +const BAR_W = 4 + +interface DebuggerProps { + theme?: 'light' | 'dark' +} + +export function Debugger({ theme = 'dark' }: DebuggerProps) { + const fg = theme === 'dark' ? '0,0,0' : '255,255,255' + const bg = theme === 'dark' ? '255,255,255' : '0,0,0' + const [hovered, setHovered] = useState(null) + const ref = useRef(null) + const lenis = useLenis() + const { width: ww = 0, height: wh = 0 } = useWindowSize() + + const triggers = useSyncExternalStore( + (cb) => scrollTriggerStore.subscribe(cb), + () => scrollTriggerStore.getSnapshot(), + () => scrollTriggerStore.getSnapshot() + ) + + useEffect(() => { + function update() { + if (!ref.current) return + const docH = lenis + ? lenis.limit + wh + : document.documentElement.scrollHeight + const scroll = lenis ? lenis.scroll : window.scrollY + const p = docH > wh ? scroll / (docH - wh) : 0 + ref.current.style.setProperty('--p', p.toString()) + } + + update() + + if (lenis) { + lenis.on('scroll', update) + return () => { + lenis.off('scroll', update) + } + } + + window.addEventListener('scroll', update, { passive: true }) + return () => { + window.removeEventListener('scroll', update) + } + }, [lenis, wh]) + + useEffect(() => { + if (!ref.current) return + const ro = new ResizeObserver(([e]) => { + if (!e || !ref.current) return + ref.current.style.setProperty( + '--br', + (e.contentRect.width / e.contentRect.height).toFixed(4) + ) + }) + ro.observe(document.body) + return () => ro.disconnect() + }, []) + + const vr = ww && wh ? ww / wh : 1 + const h = 200 / vr + const docH = lenis + ? lenis.limit + wh + : typeof document !== 'undefined' + ? document.documentElement.scrollHeight + : 1 + + return ( +
+ {/* Body */} +
+ {triggers.map((t, i) => { + const color = COLORS[i % COLORS.length] + const top = ((t.rect.top + t.translateY) / docH) * 100 + const left = (t.rect.left / ww) * 100 + const w = (t.rect.width / ww) * 100 + const rh = (t.rect.height / docH) * 100 + const startPct = ((t.startPx + t.translateY) / docH) * 100 + const endPct = ((t.endPx + t.translateY) / docH) * 100 + const barTop = Math.min(startPct, endPct) + const barH = Math.abs(endPct - startPct) + + const isHovered = hovered === t.id + + return ( +
+ {/* Element rectangle */} +
setHovered(t.id)} + onMouseLeave={() => setHovered(null)} + style={{ + position: 'absolute', + top: `${top}%`, + left: `${left}%`, + width: `${w}%`, + height: `${rh}%`, + border: `1px solid ${color}`, + opacity: isHovered ? 1 : t.isActive ? 0.8 : 0.2, + transition: 'opacity 150ms', + backgroundColor: `rgba(${fg},0.1)`, + cursor: 'default', + zIndex: isHovered ? 1 : 0, + }} + > + {/* Tooltip */} + {isHovered && ( +
+ {t.id} +
+ start: {t.start} ({Math.round(t.startPx)}px) +
+ end: {t.end} ({Math.round(t.endPx)}px) +
+ progress: {t.progress.toFixed(3)} +
+ active: {t.isActive ? 'true' : 'false'} +
+ )} +
+ {/* Bar */} +
+
+ ) + })} +
+ + {/* Border */} +
+
+ ) +} diff --git a/packages/hamo/src/use-scroll-trigger/index.ts b/packages/hamo/src/use-scroll-trigger/index.ts new file mode 100644 index 0000000..410e6b6 --- /dev/null +++ b/packages/hamo/src/use-scroll-trigger/index.ts @@ -0,0 +1,343 @@ +'use client' + +import { useLenis } from 'lenis/react' +import { useEffect, useId } from 'react' +import { useEffectEvent } from '../use-effect-event' +import { useLazyState } from '../use-lazy-state' +import { type Rect, useRect } from '../use-rect' +import { useTransform } from '../use-transform' +import { useWindowSize } from '../use-window-size' +import { scrollTriggerStore } from './store' + +// Math utilities (inlined to avoid external dependency) +function clamp(min: number, input: number, max: number): number { + return Math.max(min, Math.min(input, max)) +} + +function mapRange( + inMin: number, + inMax: number, + input: number, + outMin: number, + outMax: number +): number { + return ((input - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin +} + +// Resolves a trigger position keyword ('top' | 'center' | 'bottom') to a pixel +// value against the provided anchors, or parses a numeric string. Falls back to +// 0 for anything unrecognized. +function resolveAnchor( + keyword: string | number | undefined, + anchors: { top: number; center: number; bottom: number } +): number { + if (typeof keyword === 'number') return keyword + if (keyword === 'top') return anchors.top + if (keyword === 'center') return anchors.center + if (keyword === 'bottom') return anchors.bottom + const parsed = Number.parseFloat(keyword ?? '') + return Number.isFinite(parsed) ? parsed : 0 +} + +function modulo(n: number, d: number) { + if (d === 0) return n + if (d < 0) return Number.NaN + return ((n % d) + d) % d +} + +type TriggerPosition = 'top' | 'center' | 'bottom' | number +type TriggerPositionCombination = `${TriggerPosition} ${TriggerPosition}` + +export type UseScrollTriggerOptions = { + /** External rect from useRect — pass this to share a single useRect across multiple triggers on the same element */ + rect?: Rect + /** Start position: "element-position viewport-position" (default: "bottom bottom") */ + start?: TriggerPositionCombination + /** End position: "element-position viewport-position" (default: "top top") */ + end?: TriggerPositionCombination + /** Pixel offset added to element positions */ + offset?: number + /** Disable the scroll trigger */ + disabled?: boolean + /** Called when element enters the trigger zone */ + onEnter?: (data: { progress: number; direction: 1 | -1 }) => void + /** Called when element leaves the trigger zone */ + onLeave?: (data: { progress: number; direction: 1 | -1 }) => void + /** Called on every scroll progress update */ + onProgress?: (data: { + height: number + isActive: boolean + progress: number + lastProgress: number + direction: 1 | -1 + steps: number[] + }) => void + /** Number of discrete steps to subdivide progress into */ + steps?: number + /** Enable debug mode — registers this trigger to the Minimap. Pass a string to use as label. */ + debug?: boolean | string +} + +/** + * Hook for creating scroll-based animations and triggers. + * + * Provides scroll-triggered progress tracking with GSAP ScrollTrigger-like + * position syntax. Integrates with Lenis when available, falls back to + * native scroll events. + * + * Position format: "element-position viewport-position" + * Available positions: 'top', 'center', 'bottom', or pixel values + * + * @param options - Configuration options + * @param deps - Dependencies that trigger recalculation + * + * @returns [setRef, rect] - A ref setter (undefined if external rect provided) and the element's rect + * + * @example + * ```tsx + * // Basic usage — creates its own useRect internally + * const [setRef] = useScrollTrigger({ + * onProgress: ({ progress }) => console.log(progress), + * }) + * return
...
+ * ``` + * + * @example + * ```tsx + * // Shared rect — multiple triggers on the same element, single useRect + * const [setRef, rect] = useRect() + * useScrollTrigger({ rect, end: 'center center', onEnter: handleEnter }) + * useScrollTrigger({ rect, start: 'center center', onProgress: handleParallax }) + * return
...
+ * ``` + */ +export function useScrollTrigger( + { + rect: externalRect, + start = 'bottom bottom', + end = 'top top', + offset = 0, + disabled = false, + onEnter, + onLeave, + onProgress, + steps = 1, + debug = false, + }: UseScrollTriggerOptions = {}, + deps: unknown[] = [] +) { + const [setRectRef, internalRect] = useRect({}) + const rect = externalRect ?? internalRect + const getTransform = useTransform() + const lenis = useLenis() + const autoId = useId() + const debugId = typeof debug === 'string' ? debug : autoId + + const { height: windowHeight = 0 } = useWindowSize() + + const isReady = rect?.top !== undefined + + const [elementStartKeyword, viewportStartKeyword] = + typeof start === 'string' ? start.split(' ') : [start] + const [elementEndKeyword, viewportEndKeyword] = + typeof end === 'string' ? end.split(' ') : [end] + + const viewportAnchors = { + top: 0, + center: windowHeight * 0.5, + bottom: windowHeight, + } + const viewportStart = resolveAnchor(viewportStartKeyword, viewportAnchors) + const viewportEnd = resolveAnchor(viewportEndKeyword, viewportAnchors) + + const elementTop = rect?.top || 0 + const elementAnchors = { + top: elementTop, + center: elementTop + (rect?.height || 0) * 0.5, + bottom: rect?.bottom || 0, + } + const elementStart = + resolveAnchor(elementStartKeyword, elementAnchors) + offset + const elementEnd = resolveAnchor(elementEndKeyword, elementAnchors) + offset + + const startValue = elementStart - viewportStart + const endValue = elementEnd - viewportEnd + + const handleProgress = useEffectEvent( + (progress: number, lastProgress: number) => { + const direction: 1 | -1 = progress >= lastProgress ? 1 : -1 + const clampedProgress = clamp(0, progress, 1) + const isActive = progress >= 0 && progress <= 1 + + onProgress?.({ + height: endValue - startValue, + isActive, + progress: clampedProgress, + lastProgress, + direction, + steps: Array.from({ length: steps }).map((_, i) => + clamp(0, mapRange(i / steps, (i + 1) / steps, progress, 0, 1), 1) + ), + }) + + if (debug) { + const { translate } = getTransform() + scrollTriggerStore.update(debugId, { + progress: clampedProgress, + isActive, + startPx: startValue, + endPx: endValue, + rect: { + top: rect?.top || 0, + left: rect?.left || 0, + width: rect?.width || 0, + height: rect?.height || 0, + }, + translateY: translate.y, + }) + } + } + ) + + const handleEnter = useEffectEvent( + (progress: number, lastProgress: number) => { + const direction: 1 | -1 = progress >= lastProgress ? 1 : -1 + onEnter?.({ progress: clamp(0, progress, 1), direction }) + } + ) + + const handleLeave = useEffectEvent( + (progress: number, lastProgress: number) => { + const direction: 1 | -1 = progress >= lastProgress ? 1 : -1 + onLeave?.({ progress: clamp(0, progress, 1), direction }) + } + ) + + const [setProgress] = useLazyState( + Number.NaN, + (progress: number, lastProgress: number | undefined) => { + if (Number.isNaN(progress) || progress === undefined) return + if (lastProgress === undefined) return + + if ( + (progress >= 0 && lastProgress < 0) || + (progress <= 1 && lastProgress > 1) + ) { + handleEnter(progress, lastProgress) + } + + if (!(clamp(0, progress, 1) === clamp(0, lastProgress, 1))) { + handleProgress(progress, lastProgress) + } + + if ( + (progress < 0 && lastProgress >= 0) || + (progress > 1 && lastProgress <= 1) + ) { + handleLeave(progress, lastProgress) + } + }, + [endValue, startValue, steps] + ) + + const update = useEffectEvent(() => { + if (disabled) return + if (!isReady) return + + const scroll = lenis ? Math.floor(lenis.scroll) : window.scrollY + const { translate } = getTransform() + + // modulo wraps the scroll position for Lenis infinite scroll; with no Lenis + // limit (limit ?? 0 === 0) modulo is a no-op and this reduces to subtraction + const progress = mapRange( + 0, + endValue - startValue, + modulo(scroll - translate.y - startValue, lenis?.limit ?? 0), + 0, + 1 + ) + + setProgress(progress) + }) + + useEffect(() => { + if (lenis) { + lenis.on('scroll', update) + return () => { + lenis.off('scroll', update) + } + } + + // Fallback to native scroll + update() + window.addEventListener('scroll', update, false) + + return () => { + window.removeEventListener('scroll', update, false) + } + }, [lenis, update, ...deps]) + + // Recalculate when parent transforms change + useTransform(update) + + // Run update when deps change (update is a stable useEffectEvent) + useEffect(update, [update, ...deps]) + + // Debug: register/unregister from store + // biome-ignore lint/correctness/useExhaustiveDependencies: registers a one-time snapshot per trigger; the sync effect below keeps positions/rect current + useEffect(() => { + if (!debug) return + + scrollTriggerStore.register(debugId, { + id: debugId, + start, + end, + startPx: startValue, + endPx: endValue, + progress: 0, + isActive: false, + rect: { + top: rect?.top || 0, + left: rect?.left || 0, + width: rect?.width || 0, + height: rect?.height || 0, + }, + translateY: 0, + }) + + return () => { + scrollTriggerStore.unregister(debugId) + } + }, [debug, debugId]) + + // Debug: sync store when rect/positions change + useEffect(() => { + if (!debug) return + + scrollTriggerStore.update(debugId, { + start, + end, + startPx: startValue, + endPx: endValue, + rect: { + top: rect?.top || 0, + left: rect?.left || 0, + width: rect?.width || 0, + height: rect?.height || 0, + }, + }) + }, [ + debug, + debugId, + start, + end, + startValue, + endValue, + rect?.top, + rect?.left, + rect?.width, + rect?.height, + ]) + + return [setRectRef, rect] as const +} diff --git a/packages/hamo/src/use-scroll-trigger/store.ts b/packages/hamo/src/use-scroll-trigger/store.ts new file mode 100644 index 0000000..0b623dd --- /dev/null +++ b/packages/hamo/src/use-scroll-trigger/store.ts @@ -0,0 +1,60 @@ +// A tiny synchronous pub/sub used only by the debug overlay. Inlined (instead +// of nanoevents) to keep hamo free of runtime dependencies. + +export interface TriggerEntry { + id: string + start: string + end: string + startPx: number + endPx: number + progress: number + isActive: boolean + rect: { top: number; left: number; width: number; height: number } + translateY: number +} + +const subscribers = new Set<() => void>() +const triggers = new Map() +let snapshot: TriggerEntry[] = [] + +function updateSnapshot() { + snapshot = Array.from(triggers.values()) +} + +function emit() { + for (const callback of subscribers) callback() +} + +export const scrollTriggerStore = { + register(id: string, entry: TriggerEntry) { + triggers.set(id, entry) + updateSnapshot() + emit() + }, + + update(id: string, partial: Partial) { + const existing = triggers.get(id) + if (existing) { + Object.assign(existing, partial) + updateSnapshot() + emit() + } + }, + + unregister(id: string) { + triggers.delete(id) + updateSnapshot() + emit() + }, + + getSnapshot(): TriggerEntry[] { + return snapshot + }, + + subscribe(callback: () => void) { + subscribers.add(callback) + return () => { + subscribers.delete(callback) + } + }, +} diff --git a/packages/hamo/src/use-transform/README.md b/packages/hamo/src/use-transform/README.md new file mode 100644 index 0000000..71ae4b9 --- /dev/null +++ b/packages/hamo/src/use-transform/README.md @@ -0,0 +1,135 @@ +# useTransform + +A context-based transform accumulation system. `TransformProvider` tracks programmatic transforms (translate, rotate, scale) and propagates them down the component tree. `useTransform` lets child components read the accumulated transform or react to changes. + +The primary use case is **scroll trigger compensation** — when a parent element is translated programmatically (e.g., parallax), child `useScrollTrigger` hooks need to know about that offset to compute accurate progress values. + +## Usage + +```jsx +import { useRef } from 'react' +import { TransformProvider, useTransform } from 'hamo' +import type { TransformRef } from 'hamo' + +function ParallaxSection() { + const transformRef = useRef(null) + + // Set transforms imperatively (e.g., from a scroll callback) + function onScroll(y) { + transformRef.current?.setTranslate(0, y) + } + + return ( + + + + ) +} + +function ChildComponent() { + // Read accumulated transform from all parent providers + const getTransform = useTransform() + const { translate } = getTransform() + // translate.y reflects the parent's offset + + // Or react to changes: + useTransform((transform) => { + console.log('Parent moved to:', transform.translate.y) + }) + + return
Content
+} +``` + +## TransformProvider + +Wraps a subtree with a transform context. Nested providers accumulate transforms: +- **Translate** and **rotate**: additive +- **Scale**: multiplicative + +### Props + +- `children`: (ReactNode) Child components. +- `ref`: (Ref\) Optional imperative handle. + +### TransformRef Methods + +- `setTranslate(x?, y?, z?)` — Set translation (defaults: 0). +- `setRotate(x?, y?, z?)` — Set rotation in degrees (defaults: 0). +- `setScale(x?, y?, z?)` — Set scale (defaults: 1). + +## useTransform + +### Without callback + +Returns a `getTransform()` function to read the current accumulated transform on demand. + +```jsx +const getTransform = useTransform() +const { translate, rotate, scale } = getTransform() +``` + +### With callback + +Registers a callback that fires whenever any ancestor `TransformProvider` updates its transform. + +```jsx +useTransform((transform) => { + element.style.transform = `translateY(${transform.translate.y}px)` +}) +``` + +### Parameters + +- `callback`: (function, optional) Fired with the accumulated `Transform` on every change. +- `deps`: (array, default: `[]`) Dependencies for the callback effect. + +### Return Value + +Returns `getTransform` — a function that returns the current accumulated `Transform`. + +## Transform Type + +```ts +type Transform = { + translate: { x: number; y: number; z: number } + rotate: { x: number; y: number; z: number } + scale: { x: number; y: number; z: number } +} +``` + +## Example: Nested Providers + +```jsx +import { useRef } from 'react' +import { TransformProvider, useTransform } from 'hamo' +import type { TransformRef } from 'hamo' + +function Outer() { + const ref = useRef(null) + ref.current?.setTranslate(0, 100) // y = 100 + + return ( + + + + ) +} + +function Inner() { + const ref = useRef(null) + ref.current?.setTranslate(0, 50) // y = 50 + + return ( + + + + ) +} + +function Leaf() { + const getTransform = useTransform() + const { translate } = getTransform() + // translate.y === 150 (100 + 50, accumulated from both parents) +} +``` diff --git a/packages/hamo/src/use-transform/index.tsx b/packages/hamo/src/use-transform/index.tsx new file mode 100644 index 0000000..7f468d8 --- /dev/null +++ b/packages/hamo/src/use-transform/index.tsx @@ -0,0 +1,244 @@ +'use client' + +import { + createContext, + type DependencyList, + forwardRef, + type ReactNode, + useContext, + useEffect, + useImperativeHandle, + useRef, +} from 'react' +import { useEffectEvent } from '../use-effect-event' + +export type Transform = { + translate: { x: number; y: number; z: number } + rotate: { x: number; y: number; z: number } + scale: { x: number; y: number; z: number } + userData: Record +} + +function createTransform(): Transform { + return { + translate: { x: 0, y: 0, z: 0 }, + rotate: { x: 0, y: 0, z: 0 }, + scale: { x: 1, y: 1, z: 1 }, + userData: {}, + } +} + +type TransformCallback = (transform: Transform) => void + +export type TransformRef = { + setTranslate: (x?: number, y?: number, z?: number) => void + setRotate: (x?: number, y?: number, z?: number) => void + setScale: (x?: number, y?: number, z?: number) => void + setUserData: (data: Record) => void +} + +type TransformContextType = { + getTransform: () => Transform + addCallback: (callback: TransformCallback) => void + removeCallback: (callback: TransformCallback) => void + setTranslate: (x?: number, y?: number, z?: number) => void + setRotate: (x?: number, y?: number, z?: number) => void + setScale: (x?: number, y?: number, z?: number) => void + setUserData: (data: Record) => void +} + +export const TransformContext = createContext({ + getTransform: () => createTransform(), + addCallback: () => {}, + removeCallback: () => {}, + setTranslate: () => {}, + setRotate: () => {}, + setScale: () => {}, + setUserData: () => {}, +}) + +type TransformProviderProps = { + children: ReactNode +} + +/** + * Provider for managing element transforms in a composable hierarchy. + * + * Nested providers accumulate transforms — translate and rotate are additive, + * scale is multiplicative. This lets child components account for parent + * transforms (e.g., parallax offsets) when computing scroll positions. + * + * @example + * ```tsx + * import { TransformProvider, useTransform } from 'hamo' + * + * function ParallaxWrapper({ children }) { + * const ref = useRef(null) + * + * // Update transform on scroll + * useScrollTrigger({ + * onProgress: ({ progress }) => { + * ref.current?.setTranslate(0, progress * -100) + * }, + * }) + * + * return ( + * + * {children} + * + * ) + * } + * ``` + */ +export const TransformProvider = forwardRef< + TransformRef, + TransformProviderProps +>(function TransformProvider({ children }, ref) { + const parentTransformRef = useRef(createTransform()) + const transformRef = useRef(createTransform()) + + function getTransform(): Transform { + const parent = parentTransformRef.current + const self = transformRef.current + + // Accumulate parent + self into a fresh object on every call (no clone): + // translate/rotate are additive, scale is multiplicative, userData merges. + return { + translate: { + x: parent.translate.x + self.translate.x, + y: parent.translate.y + self.translate.y, + z: parent.translate.z + self.translate.z, + }, + rotate: { + x: parent.rotate.x + self.rotate.x, + y: parent.rotate.y + self.rotate.y, + z: parent.rotate.z + self.rotate.z, + }, + scale: { + x: parent.scale.x * self.scale.x, + y: parent.scale.y * self.scale.y, + z: parent.scale.z * self.scale.z, + }, + userData: { ...parent.userData, ...self.userData }, + } + } + + const callbacksRef = useRef([]) + + const addCallback = useEffectEvent((callback: TransformCallback) => { + callbacksRef.current.push(callback) + }) + + const removeCallback = useEffectEvent((callback: TransformCallback) => { + callbacksRef.current = callbacksRef.current.filter((c) => c !== callback) + }) + + const update = useEffectEvent(() => { + const transform = getTransform() + for (const callback of callbacksRef.current) { + callback(transform) + } + }) + + function setTranslate(x = 0, y = 0, z = 0) { + if (!Number.isNaN(x)) transformRef.current.translate.x = Number(x) + if (!Number.isNaN(y)) transformRef.current.translate.y = Number(y) + if (!Number.isNaN(z)) transformRef.current.translate.z = Number(z) + + update() + } + + function setRotate(x = 0, y = 0, z = 0) { + if (!Number.isNaN(x)) transformRef.current.rotate.x = Number(x) + if (!Number.isNaN(y)) transformRef.current.rotate.y = Number(y) + if (!Number.isNaN(z)) transformRef.current.rotate.z = Number(z) + update() + } + + function setScale(x = 1, y = 1, z = 1) { + if (!Number.isNaN(x)) transformRef.current.scale.x = Number(x) + if (!Number.isNaN(y)) transformRef.current.scale.y = Number(y) + if (!Number.isNaN(z)) transformRef.current.scale.z = Number(z) + update() + } + + function setUserData(data: Record) { + Object.assign(transformRef.current.userData, data) + update() + } + + // Inherit parent transforms. Stable identity (useEffectEvent) so the + // subscription isn't torn down and recreated on every render. getTransform + // builds a fresh object per call, so storing the inherited transform by + // reference is safe — nothing mutates it after it's handed to callbacks. + const inheritParentTransform = useEffectEvent((transform: Transform) => { + parentTransformRef.current = transform + update() + }) + useTransform(inheritParentTransform) + + useImperativeHandle(ref, () => ({ + setTranslate, + setRotate, + setScale, + setUserData, + })) + + return ( + + {children} + + ) +}) + +/** + * Hook to access and react to transform changes from TransformProvider. + * + * Without a callback, returns a `getTransform()` function to read the current + * accumulated transform. With a callback, it fires whenever any ancestor + * TransformProvider updates its transform. + * + * @param callback - Optional callback fired on transform changes + * @param deps - Dependencies for the callback effect + * @returns Function to get current accumulated transform + * + * @example + * ```tsx + * // Read transform on demand + * const getTransform = useTransform() + * const { translate } = getTransform() + * + * // React to transform changes + * useTransform((transform) => { + * element.style.transform = `translateY(${transform.translate.y}px)` + * }) + * ``` + */ +export function useTransform( + callback?: TransformCallback, + deps: DependencyList = [] +) { + const { getTransform, addCallback, removeCallback } = + useContext(TransformContext) + + useEffect(() => { + if (!callback) return + + addCallback(callback) + return () => { + removeCallback(callback) + } + }, [callback, addCallback, removeCallback, ...deps]) + + return getTransform +} diff --git a/packages/react/src/use-window-size/README.md b/packages/hamo/src/use-window-size/README.md similarity index 100% rename from packages/react/src/use-window-size/README.md rename to packages/hamo/src/use-window-size/README.md diff --git a/packages/hamo/src/use-window-size/index.test.tsx b/packages/hamo/src/use-window-size/index.test.tsx new file mode 100644 index 0000000..0830c2f --- /dev/null +++ b/packages/hamo/src/use-window-size/index.test.tsx @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'bun:test' +import { renderHook } from '@testing-library/react' +import { useWindowSize } from './index' + +describe('useWindowSize (smoke)', () => { + it('mounts without throwing and returns { width, height, dpr }', () => { + const { result, unmount } = renderHook(() => useWindowSize()) + + const { width, height, dpr } = result.current + + // After the mount effect runs in happy-dom, the values may be numbers. + // What must hold is that the shape is correct and no throw occurred. + expect(typeof width === 'number' || width === undefined).toBe(true) + expect(typeof height === 'number' || height === undefined).toBe(true) + expect(typeof dpr === 'number' || dpr === undefined).toBe(true) + + expect(() => unmount()).not.toThrow() + }) + + it('accepts a custom debounce delay without throwing', () => { + const { unmount } = renderHook(() => useWindowSize(100)) + expect(() => unmount()).not.toThrow() + }) +}) diff --git a/packages/react/src/use-window-size/index.ts b/packages/hamo/src/use-window-size/index.ts similarity index 65% rename from packages/react/src/use-window-size/index.ts rename to packages/hamo/src/use-window-size/index.ts index 6eae1ec..a84ec68 100644 --- a/packages/react/src/use-window-size/index.ts +++ b/packages/hamo/src/use-window-size/index.ts @@ -1,68 +1,69 @@ -import { useEffect, useState } from 'react' -import { debounce } from '../use-debounce' - -/** - * @name useWindowSize - * @description A React hook that listens to window size. - * @param {number} debounce- The delay (in milliseconds) before the resize event is processed. This helps to optimize performance by reducing the number of times the callback function is called during resizing. Alternatively, you can set the global `useWindowSize.setDebounce` function to change the default debounce delay. - * @returns {object} { width, height, dpr } - */ - -let defaultDebounceDelay = 500 - -function setDebounce(delay: number) { - defaultDebounceDelay = delay -} - -function windowSize( - callback: ({ - width, - height, - dpr, - }: { width: number; height: number; dpr: number }) => void, - debounceDelay: number = defaultDebounceDelay -) { - function onWindowResize() { - const width = Math.min( - window.innerWidth, - document.documentElement.clientWidth - ) - const height = Math.min( - window.innerHeight, - document.documentElement.clientHeight - ) - const dpr = window.devicePixelRatio - callback({ width, height, dpr }) - } - const debouncedOnWindowRezise = debounce(onWindowResize, debounceDelay) - - const abortController = new AbortController() - window.addEventListener('resize', debouncedOnWindowRezise, { - signal: abortController.signal, - }) - - onWindowResize() - - return () => { - abortController.abort() - debouncedOnWindowRezise.cancel() - } -} - -export function useWindowSize(debounceDelay: number = defaultDebounceDelay) { - const [width, setWidth] = useState() - const [height, setHeight] = useState() - const [dpr, setDpr] = useState() - - useEffect(() => { - return windowSize(({ width, height, dpr }) => { - setWidth(width) - setHeight(height) - setDpr(dpr) - }, debounceDelay) - }, [debounceDelay]) - - return { width, height, dpr } -} - -useWindowSize.setDebounce = setDebounce +'use client' + +import { useEffect, useState } from 'react' +import { createDebounceConfig } from '../debounce-config' +import { debounce } from '../use-debounce' + +/** + * @name useWindowSize + * @description A React hook that listens to window size. + * @param {number} debounce- The delay (in milliseconds) before the resize event is processed. This helps to optimize performance by reducing the number of times the callback function is called during resizing. Alternatively, you can set the global `useWindowSize.setDebounce` function to change the default debounce delay. + * @returns {object} { width, height, dpr } + */ + +const windowSizeDebounce = createDebounceConfig() + +function windowSize( + callback: ({ + width, + height, + dpr, + }: { + width: number + height: number + dpr: number + }) => void, + debounceDelay: number = windowSizeDebounce.getDelay() +) { + function onWindowResize() { + const width = Math.min( + window.innerWidth, + document.documentElement.clientWidth + ) + const height = Math.min( + window.innerHeight, + document.documentElement.clientHeight + ) + const dpr = window.devicePixelRatio + callback({ width, height, dpr }) + } + const debouncedOnWindowRezise = debounce(onWindowResize, debounceDelay) + + const abortController = new AbortController() + window.addEventListener('resize', debouncedOnWindowRezise, { + signal: abortController.signal, + }) + + onWindowResize() + + return () => { + abortController.abort() + debouncedOnWindowRezise.cancel() + } +} + +type WindowSize = { width?: number; height?: number; dpr?: number } + +export function useWindowSize( + debounceDelay: number = windowSizeDebounce.getDelay() +) { + const [size, setSize] = useState({}) + + useEffect(() => { + return windowSize((next) => setSize(next), debounceDelay) + }, [debounceDelay]) + + return size +} + +useWindowSize.setDebounce = windowSizeDebounce.setDebounce diff --git a/packages/hamo/test/setup.ts b/packages/hamo/test/setup.ts new file mode 100644 index 0000000..a3e66f1 --- /dev/null +++ b/packages/hamo/test/setup.ts @@ -0,0 +1,47 @@ +import { GlobalRegistrator } from '@happy-dom/global-registrator' + +GlobalRegistrator.register() + +// React 19 act() environment flag +// @ts-expect-error test-only global +globalThis.IS_REACT_ACT_ENVIRONMENT = true + +// happy-dom does not implement these observers; provide no-op stubs so the hooks +// mount and clean up without throwing. +if (!('ResizeObserver' in globalThis)) { + class ResizeObserverStub { + observe() { + /* no-op */ + } + unobserve() { + /* no-op */ + } + disconnect() { + /* no-op */ + } + } + // @ts-expect-error test-only global + globalThis.ResizeObserver = ResizeObserverStub +} + +if (!('IntersectionObserver' in globalThis)) { + class IntersectionObserverStub { + root = null + rootMargin = '' + thresholds = [] + observe() { + /* no-op */ + } + unobserve() { + /* no-op */ + } + disconnect() { + /* no-op */ + } + takeRecords() { + return [] + } + } + // @ts-expect-error test-only global + globalThis.IntersectionObserver = IntersectionObserverStub +} diff --git a/packages/hamo/test/ssr.test.tsx b/packages/hamo/test/ssr.test.tsx new file mode 100644 index 0000000..ccd1820 --- /dev/null +++ b/packages/hamo/test/ssr.test.tsx @@ -0,0 +1,88 @@ +/** + * SSR safety tests. + * + * Each browser hook is rendered to a string via renderToString(). Effects do + * not run during SSR, so the render path must not throw even when browser + * globals (window, document, ResizeObserver, …) are absent. + * + * These tests prove that hamo hooks are safe to use in server-rendered + * React trees (Next.js App Router, Remix, etc.). + */ + +import { describe, expect, it } from 'bun:test' +import { renderToString } from 'react-dom/server' +import { useIntersectionObserver } from '../src/use-intersection-observer' +import { useMediaQuery } from '../src/use-media-query' +import { useRect } from '../src/use-rect' +import { useResizeObserver } from '../src/use-resize-observer' +import { useWindowSize } from '../src/use-window-size' + +// --------------------------------------------------------------------------- +// Tiny wrapper components — the hook is called; the result is discarded. +// The only assertion is: renderToString returns a string without throwing. +// --------------------------------------------------------------------------- + +function WindowSizeSSR() { + const { width, height, dpr } = useWindowSize() + return ( + + {width}-{height}-{dpr} + + ) +} + +function MediaQuerySSR() { + const match = useMediaQuery('(min-width: 768px)') + return {String(match)} +} + +function ResizeObserverSSR() { + const [, entry] = useResizeObserver() + return {String(entry)} +} + +function RectSSR() { + const [, rect] = useRect() + return {String(rect)} +} + +function IntersectionObserverSSR() { + const [, entry] = useIntersectionObserver() + return {String(entry)} +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe('SSR safety', () => { + it('useWindowSize renders to string without throwing', () => { + const html = renderToString() + expect(typeof html).toBe('string') + expect(html.length).toBeGreaterThan(0) + }) + + it('useMediaQuery renders to string without throwing', () => { + const html = renderToString() + expect(typeof html).toBe('string') + expect(html.length).toBeGreaterThan(0) + }) + + it('useResizeObserver renders to string without throwing', () => { + const html = renderToString() + expect(typeof html).toBe('string') + expect(html.length).toBeGreaterThan(0) + }) + + it('useRect renders to string without throwing', () => { + const html = renderToString() + expect(typeof html).toBe('string') + expect(html.length).toBeGreaterThan(0) + }) + + it('useIntersectionObserver renders to string without throwing', () => { + const html = renderToString() + expect(typeof html).toBe('string') + expect(html.length).toBeGreaterThan(0) + }) +}) diff --git a/tsconfig.json b/packages/hamo/tsconfig.json similarity index 91% rename from tsconfig.json rename to packages/hamo/tsconfig.json index e25b322..dc01a13 100644 --- a/tsconfig.json +++ b/packages/hamo/tsconfig.json @@ -1,5 +1,5 @@ { - "exclude": ["dist", "playground", "website"], + "exclude": ["dist", "**/*.test.ts", "**/*.test.tsx", "test"], "compilerOptions": { /* Base Options: */ "skipLibCheck": true, diff --git a/packages/hamo/tsdown.config.ts b/packages/hamo/tsdown.config.ts new file mode 100644 index 0000000..ad8d002 --- /dev/null +++ b/packages/hamo/tsdown.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from 'tsdown' + +// Library build, delegating minification/polyfilling to the consuming framework +// (Next.js, React Router, TanStack, …). We ship modern, unminified, dual ESM+CJS +// output. `unbundle` preserves the per-file `"use client"` directives so the pure +// utilities (useObjectFit) stay usable in Server Components. +export default defineConfig({ + entry: { + hamo: 'src/index.ts', + 'use-scroll-trigger/debugger': 'src/use-scroll-trigger/debugger.tsx', + }, + outDir: 'dist', + target: 'es2022', + platform: 'neutral', + format: ['esm', 'cjs'], + // lenis is an optional peer (useScrollTrigger falls back to native scroll); + // never bundle it. + external: [/^lenis(\/|$)/], + unbundle: true, + dts: true, + sourcemap: true, + clean: true, + outExtensions: ({ format }) => ({ + js: format === 'es' ? '.mjs' : '.cjs', + dts: format === 'es' ? '.d.ts' : '.d.cts', + }), +}) diff --git a/packages/react/README.md b/packages/react/README.md deleted file mode 100644 index f74b2d7..0000000 --- a/packages/react/README.md +++ /dev/null @@ -1,157 +0,0 @@ -# lenis/react - -## Introduction -lenis/react provides a `` component that creates a [Lenis](https://github.com/darkroomengineering/lenis) instance and provides it to its children via context. This allows you to use Lenis in your React app without worrying about passing the instance down through props. It also provides a `useLenis` hook that allows you to access the Lenis instance from any component in your app. - - -## Installation - -```bash -npm i lenis -``` - -## Usage - -### Basic - -```jsx -import { ReactLenis, useLenis } from 'lenis/react' - -function Component() { - const lenis = useLenis(({ scroll }) => { - // called every scroll - }) - - return ( - - { /* content */ } - - ) -} -``` - -## Props -- `options`: [Lenis options](https://github.com/darkroomengineering/lenis#instance-settings). -- `root`: Lenis will be instanciate using `` scroll. Default: `false`. -- `className`: Class name for the wrapper div. Default: `''`. - - - -## Hooks -Once the Lenis context is set (components mounted inside ``) you can use these handy hooks: - -`useLenis` is a hook that returns the Lenis instance - -The hook takes three argument: -- `callback`: The function to be called whenever a scroll event is emitted -- `deps`: Trigger callback on change -- `priority`: Manage callback execution order - -## Examples - -### Custom requestAnimationFrame loop: - -```jsx -import { ReactLenis } from 'lenis/react' -import { useEffect, useRef } from 'react' - -function Component() { - const lenisRef = useRef() - - useEffect(() => { - function update(time) { - lenisRef.current?.lenis?.raf(time) - } - - const rafId = requestAnimationFrame(update) - - return () => cancelAnimationFrame(rafId) - }, []) - - return ( - - { /* content */ } - - ) -} -``` - - -### GSAP integration - -```jsx -import gsap from 'gsap' -import { ReactLenis } from 'lenis/react' -import { useEffect, useRef } from 'react' - -function Component() { - const lenisRef = useRef() - - useEffect(() => { - function update(time) { - lenisRef.current?.lenis?.raf(time * 1000) - } - - gsap.ticker.add(update) - - return () => gsap.ticker.remove(update) - }, []) - - return ( - - { /* content */ } - - ) -} -``` - -### Framer Motion integration: -```jsx -import { ReactLenis } from 'lenis/react' -import { cancelFrame, frame } from 'framer-motion'; -import { useEffect, useRef } from 'react'; - -function Component() { - const lenisRef = useRef() - - useEffect(() => { - function update(time) { - lenisRef.current?.lenis?.raf(time) - } - - frame.update(update, true) - - return () => cancelFrame(update) - }, []) - - - return ( - - { /* content */ } - - ) -} -``` - -## lenis/react in use - -- [@darkroom.engineering/satus](https://github.com/darkroomengineering/satus) Our starter kit. - -
- -## Authors - -This tool is maintained by the darkroom.engineering team: - -- Clément Roche ([@clementroche\_](https://twitter.com/clementroche_)) – [darkroom.engineering](https://darkroom.engineering) -- Guido Fier ([@uido15](https://twitter.com/uido15)) – [darkroom.engineering](https://darkroom.engineering) -- Leandro Soengas ([@lsoengas](https://twitter.com/lsoengas)) - [darkroom.engineering](https://darkroom.engineering) -- Fermin Fernandez ([@Fermin_FerBridd](https://twitter.com/Fermin_FerBridd)) - [darkroom.engineering](https://darkroom.engineering) -- Felix Mayr ([@feledori](https://twitter.com/feledori)) - [darkroom.engineering](https://darkroom.engineering) -- Franco Arza ([@arzafran](https://twitter.com/arzafran)) - [darkroom.engineering](https://darkroom.engineering) - -
- -## License - -[The MIT License.](https://opensource.org/licenses/MIT) diff --git a/packages/react/index.ts b/packages/react/index.ts deleted file mode 100644 index 48afd8a..0000000 --- a/packages/react/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export { useResizeObserver } from './src/use-resize-observer' -export { useWindowSize } from './src/use-window-size' -export { useMediaQuery } from './src/use-media-query' -export { useLazyState } from './src/use-lazy-state' -export { useRect } from './src/use-rect' -export { - useDebouncedEffect, - useDebouncedState, - useDebouncedCallback, - useTimeout, -} from './src/use-debounce' -export { useObjectFit } from './src/use-object-fit' -export { useIntersectionObserver } from './src/use-intersection-observer' -export type { Rect } from './src/use-rect' diff --git a/packages/react/package.json b/packages/react/package.json deleted file mode 100644 index b9b9828..0000000 --- a/packages/react/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "hamo", - "type": "module", - "devDependencies": { - "react": "^18.2.0", - "@types/react": "^18.2.77" - } -} diff --git a/packages/react/src/use-intersection-observer/index.ts b/packages/react/src/use-intersection-observer/index.ts deleted file mode 100644 index cdb35fe..0000000 --- a/packages/react/src/use-intersection-observer/index.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react' - -/** - * @name useIntersectionObserver - * @description A React hook that oberves element visibility using IntersectionObserver. - * @param {HTMLElement} root (optional) - * @param {string} rootMargin (optional, default: `0px`) - * @param {number} threshold (optional, default: `0`) - * @param {boolean} once (optional, default: `false`) - * @param {boolean} lazy (optional, default: `false`) - * @param {function} callback (optional) - * @param {array} deps (optional) - * @returns {array} [setElement, entry] - */ - -export function useIntersectionObserver( - { - root = null, - rootMargin = '0px', - threshold = 0, - once = false, - lazy = false, - callback = () => {}, - }: { - root?: HTMLElement | null - rootMargin?: string - threshold?: number - once?: boolean - lazy?: boolean - callback?: (entry: IntersectionObserverEntry | undefined) => void - } = {}, - deps = [] -) { - const entryRef = useRef() - const [entry, setEntry] = useState() - const [element, setElement] = useState() - - useEffect(() => { - if (!element) return - - const intersection = new IntersectionObserver( - ([entry]) => { - if (lazy) { - entryRef.current = entry - } else { - setEntry(entry) - } - - callback(entry) - - if (once && entry?.isIntersecting) intersection.disconnect() - }, - { - root, - rootMargin, - threshold, - } - ) - intersection.observe(element) - - return () => { - intersection.disconnect() - } - }, [element, root, rootMargin, threshold, lazy, once, ...deps]) - - const get = useCallback(() => entryRef.current, []) - - return [ - setElement, - (lazy ? get : entry) as IntersectionObserverEntry, // fix type - ] as const -} diff --git a/packages/react/src/use-rect/emitter.ts b/packages/react/src/use-rect/emitter.ts deleted file mode 100644 index 273b5fb..0000000 --- a/packages/react/src/use-rect/emitter.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createNanoEvents } from 'nanoevents' - -export const emitter = createNanoEvents() diff --git a/playground/package.json b/playground/package.json index da5379a..88a7c88 100644 --- a/playground/package.json +++ b/playground/package.json @@ -10,15 +10,14 @@ "astro": "astro" }, "dependencies": { - "@astrojs/check": "^0.9.3", - "@astrojs/react": "^3.6.2", - "@types/react": "^18.3.4", - "@types/react-dom": "^18.3.0", - "astro": "^4.16.1", - "hamo": "*", - "lorem-ipsum": "^2.0.8", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "typescript": "^5.5.4" + "@astrojs/check": "^0.9.9", + "@astrojs/react": "^5.0.7", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "astro": "^6.4.4", + "hamo": "workspace:*", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "typescript": "^6.0.3" } } diff --git a/playground/react/scroll-trigger-app.tsx b/playground/react/scroll-trigger-app.tsx new file mode 100644 index 0000000..ccf4f12 --- /dev/null +++ b/playground/react/scroll-trigger-app.tsx @@ -0,0 +1,730 @@ +import { useScrollTrigger, TransformProvider, useRect } from 'hamo' +import { Debugger } from 'hamo/scroll-trigger/debugger' +import type { TransformRef, UseScrollTriggerOptions } from 'hamo' +import { useRef, useState } from 'react' + +function Section({ + title, + children, +}: { + title: string + children: React.ReactNode +}) { + return ( +
+

{title}

+
{children}
+
+ ) +} + +function Value({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+ {label} + {value ?? '---'} +
+ ) +} + +// 1. Hero / Intro +function HeroSection() { + return ( +
+

+ Scroll-based progress tracking with GSAP ScrollTrigger-like position + syntax. Integrates with Lenis when available, falls back to native + scroll events. Scroll down to explore each feature. +

+

+ Position syntax:{' '} + "element-position viewport-position" +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PositionElementViewport
topTop edge of elementTop of viewport
centerCenter of elementCenter of viewport
bottomBottom edge of elementBottom of viewport
numberPixel offset from topPixel offset from top
+
+ ) +} + +// 2. Basic Progress +function BasicProgressDemo() { + const progressBarRef = useRef(null) + const boxRef = useRef(null) + const progressValueRef = useRef(null) + const activeValueRef = useRef(null) + const heightValueRef = useRef(null) + const directionValueRef = useRef(null) + + const [setRef] = useScrollTrigger({ + start: 'bottom bottom', + end: 'top top', + debug: 'basic', + onEnter: () => { + if (boxRef.current) boxRef.current.dataset.active = 'true' + }, + onLeave: () => { + if (boxRef.current) boxRef.current.dataset.active = 'false' + }, + onProgress: ({ progress, isActive, height, direction }) => { + if (progressBarRef.current) { + progressBarRef.current.style.transform = `scaleX(${progress})` + } + if (progressValueRef.current) { + progressValueRef.current.textContent = progress.toFixed(3) + } + if (activeValueRef.current) { + activeValueRef.current.textContent = isActive ? 'true' : 'false' + } + if (heightValueRef.current) { + heightValueRef.current.textContent = `${Math.round(height)}px` + } + if (directionValueRef.current) { + directionValueRef.current.textContent = + direction === 1 ? '\u2193 down' : '\u2191 up' + } + }, + }) + + return ( +
+

+ Tracks scroll progress from 0 to 1 as the element traverses the + viewport. Uses start: "bottom bottom" and{' '} + end: "top top" (the defaults) for full traversal. +

+
+ Scroll to see progress +
+
{ + setRef(el) + boxRef.current = el + }} + className="st-progress-box" + data-active="false" + > +
+
+ 0.000} + /> + false} + /> + ---} /> + ---} + /> +
+

start: "bottom bottom" / end: "top top"

+
+
+ Scroll back up +
+
+ ) +} + +// 3. Enter / Leave Events +function EnterLeaveDemo() { + const boxRef = useRef(null) + const logRef = useRef(null) + const eventsRef = useRef([]) + + const addEvent = ( + type: 'enter' | 'leave', + progress: number, + direction: 1 | -1 + ) => { + const time = new Date().toLocaleTimeString('en-US', { + hour12: false, + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }) + const className = type === 'enter' ? 'st-event-enter' : 'st-event-leave' + const label = type === 'enter' ? 'ENTER' : 'LEAVE' + const dir = direction === 1 ? '\u2193' : '\u2191' + eventsRef.current = [ + ...eventsRef.current.slice(-4), + `[${time}] ${label} ${dir} progress: ${progress.toFixed(3)}`, + ] + if (logRef.current) { + logRef.current.innerHTML = eventsRef.current.join('
') + } + } + + const [setRef] = useScrollTrigger({ + start: 'bottom bottom', + end: 'top top', + debug: 'events', + onEnter: ({ progress, direction }) => { + if (boxRef.current) boxRef.current.dataset.active = 'true' + addEvent('enter', progress, direction) + }, + onLeave: ({ progress, direction }) => { + if (boxRef.current) boxRef.current.dataset.active = 'false' + addEvent('leave', progress, direction) + }, + }) + + return ( +
+

+ onEnter fires when the element enters the trigger zone.{' '} + onLeave fires when it exits. Each callback receives{' '} + direction (1 = down, -1 = up). Scroll past this element and + back to see the event log update. +

+
+ Scroll to trigger enter/leave +
+
{ + setRef(el) + boxRef.current = el + }} + className="st-event-box" + data-active="false" + > + +
+ Waiting for events... +
+
+
+ Scroll back up +
+
+ ) +} + +// 4. Position Combinations +function PositionCard({ + startPos, + endPos, + label, +}: { + startPos: string + endPos: string + label: string +}) { + const progressBarRef = useRef(null) + const cardRef = useRef(null) + const valueRef = useRef(null) + + const [setRef] = useScrollTrigger({ + start: startPos as UseScrollTriggerOptions['start'], + end: endPos as UseScrollTriggerOptions['end'], + debug: label, + onEnter: () => { + if (cardRef.current) cardRef.current.dataset.active = 'true' + }, + onLeave: () => { + if (cardRef.current) cardRef.current.dataset.active = 'false' + }, + onProgress: ({ progress }) => { + if (progressBarRef.current) { + progressBarRef.current.style.transform = `scaleX(${progress})` + } + if (valueRef.current) { + valueRef.current.textContent = progress.toFixed(3) + } + }, + }) + + return ( +
{ + setRef(el) + cardRef.current = el + }} + className="st-position-card" + data-active="false" + > +
+
{label}
+
+ start: "{startPos}" / end: "{endPos}" +
+
+ 0.000 +
+
+ ) +} + +function PositionCombinationsDemo() { + return ( +
+

+ Different start / end positions change when + progress runs. Compare how each configuration behaves as you scroll. +

+
+ Scroll to compare positions +
+
+ + + +
+
+ Scroll back up +
+
+ ) +} + +// 5. Steps / Staggered Animation +function StepsDemo() { + const itemRefs = useRef<(HTMLDivElement | null)[]>([]) + const STEP_COUNT = 6 + + const [setRef] = useScrollTrigger({ + start: 'bottom bottom', + end: 'top top', + steps: STEP_COUNT, + debug: 'steps', + onProgress: ({ steps }) => { + for (let i = 0; i < steps.length; i++) { + const el = itemRefs.current[i] + if (el) { + const stepProgress = steps[i] + el.style.opacity = String(0.2 + stepProgress * 0.8) + el.style.transform = `translateY(${(1 - stepProgress) * 20}px)` + el.dataset.visible = stepProgress > 0 ? 'true' : 'false' + } + } + }, + }) + + return ( +
+

+ Using steps: {STEP_COUNT} to subdivide progress into{' '} + {STEP_COUNT} discrete sub-ranges. Each item animates based on its + individual step progress, creating a staggered effect. +

+
+ Scroll to see staggered animation +
+
+ {Array.from({ length: STEP_COUNT }).map((_, i) => ( +
{ + itemRefs.current[i] = el + }} + className="st-step-item" + data-visible="false" + > + Step {i + 1} / {STEP_COUNT} +
+ ))} +
+
+ Scroll back up +
+
+ ) +} + +// 6. Offset +function OffsetDemo() { + const cardARef = useRef(null) + const cardBRef = useRef(null) + const valueARef = useRef(null) + const valueBRef = useRef(null) + + const [setRefA] = useScrollTrigger({ + start: 'bottom bottom', + end: 'top top', + offset: 0, + debug: 'offset-0', + onEnter: () => { + if (cardARef.current) cardARef.current.dataset.active = 'true' + }, + onLeave: () => { + if (cardARef.current) cardARef.current.dataset.active = 'false' + }, + onProgress: ({ progress }) => { + if (valueARef.current) { + valueARef.current.textContent = progress.toFixed(3) + } + }, + }) + + const [setRefB] = useScrollTrigger({ + start: 'bottom bottom', + end: 'top top', + offset: -200, + debug: 'offset-200', + onEnter: () => { + if (cardBRef.current) cardBRef.current.dataset.active = 'true' + }, + onLeave: () => { + if (cardBRef.current) cardBRef.current.dataset.active = 'false' + }, + onProgress: ({ progress }) => { + if (valueBRef.current) { + valueBRef.current.textContent = progress.toFixed(3) + } + }, + }) + + return ( +
+

+ The offset option shifts element positions by a pixel + amount. Compare offset: 0 (default) vs{' '} + offset: -200 - the second triggers earlier. +

+
+ Scroll to compare offsets +
+
+
{ + setRefA(el) + cardARef.current = el + }} + className="st-offset-card" + data-active="false" + > +
offset: 0
+
+ 0.000 +
+
+
{ + setRefB(el) + cardBRef.current = el + }} + className="st-offset-card" + data-active="false" + > +
offset: -200
+
+ 0.000 +
+
+
+
+ Scroll back up +
+
+ ) +} + +// 7. Disabled Toggle +function DisabledDemo() { + const [disabled, setDisabled] = useState(false) + const progressBarRef = useRef(null) + const boxRef = useRef(null) + const valueRef = useRef(null) + + const [setRef] = useScrollTrigger({ + start: 'bottom bottom', + end: 'top top', + disabled, + debug: 'disabled', + onEnter: () => { + if (boxRef.current) boxRef.current.dataset.active = 'true' + }, + onLeave: () => { + if (boxRef.current) boxRef.current.dataset.active = 'false' + }, + onProgress: ({ progress }) => { + if (progressBarRef.current) { + progressBarRef.current.style.transform = `scaleX(${progress})` + } + if (valueRef.current) { + valueRef.current.textContent = progress.toFixed(3) + } + }, + }) + + return ( +
+

+ The disabled option stops progress updates. Toggle it to + see the progress bar freeze in place. +

+
+ +
+
+ Scroll to see progress {disabled ? '(disabled)' : ''} +
+
{ + setRef(el) + boxRef.current = el + }} + className="st-progress-box" + data-active="false" + > +
+
+ 0.000} /> + +
+
+
+ Scroll back up +
+
+ ) +} + +// 8. CSS Custom Property +function CSSPropertyDemo() { + const boxRef = useRef(null) + const valueRef = useRef(null) + + const [setRef] = useScrollTrigger({ + start: 'bottom bottom', + end: 'top top', + debug: 'css-prop', + onProgress: ({ progress }) => { + if (boxRef.current) { + boxRef.current.style.setProperty('--progress', String(progress)) + } + if (valueRef.current) { + valueRef.current.textContent = progress.toFixed(3) + } + }, + }) + + return ( +
+

+ Set --progress as a CSS custom property to drive transforms + purely through CSS. The box below uses translateY,{' '} + scale, rotate, and opacity all + driven by a single variable. +

+
+ Scroll to animate via CSS variable +
+
{ + setRef(el) + boxRef.current = el + }} + className="st-css-demo" + > +
+ + --progress: 0.000 + +
+
+ Scroll back up +
+
+ ) +} + +// 9. TransformProvider (Parallax Compensation) +function TransformChildTrigger() { + const progressBarRef = useRef(null) + const valueRef = useRef(null) + const boxRef = useRef(null) + + const [setRectRef, rect] = useRect({ + ignoreTransform: true, + }) + useScrollTrigger({ + rect, + debug: 'child', + onEnter: () => { + if (boxRef.current) boxRef.current.dataset.active = 'true' + }, + onLeave: () => { + if (boxRef.current) boxRef.current.dataset.active = 'false' + }, + onProgress: ({ progress }) => { + if (progressBarRef.current) { + progressBarRef.current.style.transform = `scaleX(${progress})` + } + if (valueRef.current) { + valueRef.current.textContent = progress.toFixed(3) + } + }, + }) + + return ( +
{ + setRectRef(el) + boxRef.current = el + }} + className="st-transform-child" + data-active="false" + > +
+ 0.000} /> +

+ This child compensates for the parent's translateY +

+
+ ) +} + +function TransformDemo() { + const transformRef = useRef(null) + const parentRef = useRef(null) + const offsetValueRef = useRef(null) + + const [setRef] = useScrollTrigger({ + start: 'bottom bottom', + end: 'top top', + debug: 'parallax', + onProgress: ({ progress }) => { + const y = (progress - 0.5) * -400 + transformRef.current?.setTranslate(0, y) + if (parentRef.current) { + parentRef.current.style.transform = `translateY(${y}px)` + } + if (offsetValueRef.current) { + offsetValueRef.current.textContent = `${Math.round(y)}px` + } + }, + }) + + return ( +
+

+ When a parent element is translated programmatically (e.g., parallax), + child useScrollTrigger hooks need to know about that + offset. TransformProvider propagates transform state down + the tree so child triggers compute accurate progress despite the parent + moving. +

+
+ Scroll to see parallax compensation +
+ +
{ + setRef(el) + parentRef.current = el + }} + className="st-transform-parent" + > +
+ 0px} + /> +
+ +
+
+
+ Scroll back up +
+
+ ) +} + +export default function ScrollTriggerApp() { + return ( +
+ +
+

useScrollTrigger

+

+ Scroll-based progress tracking with position syntax, enter/leave + callbacks, steps, offset, and CSS variable integration. +

+
+ + + + + + + + + + + + +
+ ) +} diff --git a/playground/react/scroll-trigger-style.css b/playground/react/scroll-trigger-style.css new file mode 100644 index 0000000..b9231b4 --- /dev/null +++ b/playground/react/scroll-trigger-style.css @@ -0,0 +1,434 @@ +body { + min-height: 100vh; +} + +.st-playground { + max-width: 800px; + margin: 0 auto; + padding: 0 24px 100px; +} + +.st-playground header { + padding: 40px 0; + text-align: center; + border-bottom: 1px solid var(--color-border); + margin-bottom: 40px; +} + +.st-playground header h1 { + font-family: monospace; + font-size: 24px; + font-weight: 400; + text-transform: uppercase; + letter-spacing: 0.1em; + margin: 0 0 8px; +} + +.st-playground header p { + color: var(--color-muted); + margin: 0; + font-size: 14px; + line-height: 1.6; +} + +.st-section { + margin-bottom: 48px; + padding-bottom: 48px; + border-bottom: 1px solid var(--color-border); +} + +.st-section h2 { + font-family: monospace; + font-size: 14px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-accent); + margin: 0 0 12px; +} + +.st-description { + font-size: 14px; + color: var(--color-muted); + margin: 0 0 20px; + line-height: 1.6; +} + +.st-description code { + background: rgba(255, 255, 255, 0.1); + padding: 2px 6px; + border-radius: 4px; + font-size: 12px; +} + +.st-hint { + font-size: 12px; + color: var(--color-accent); + margin: 12px 0 0; + font-style: italic; + font-family: monospace; +} + +.st-section-content { + display: flex; + flex-direction: column; + gap: 16px; +} + +.st-values-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 12px; +} + +.st-value { + display: flex; + flex-direction: column; + gap: 4px; + padding: 12px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid var(--color-border); + border-radius: 8px; +} + +.st-value-label { + font-family: monospace; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-muted); +} + +.st-value-data { + font-family: monospace; + font-size: 18px; + font-weight: 500; +} + +/* Spacer between sections */ +.st-spacer { + height: 60vh; + display: flex; + align-items: center; + justify-content: center; + color: var(--color-muted); + font-family: monospace; + font-size: 12px; +} + +/* Position syntax table */ +.st-syntax-table { + width: 100%; + border-collapse: collapse; + font-family: monospace; + font-size: 13px; + margin-top: 8px; +} + +.st-syntax-table th { + text-align: left; + padding: 8px 12px; + border-bottom: 1px solid var(--color-border); + color: var(--color-muted); + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 600; +} + +.st-syntax-table td { + padding: 8px 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.st-syntax-table td:first-child { + color: var(--color-accent); +} + +/* Progress box */ +.st-progress-box { + position: relative; + padding: 40px; + background: rgba(255, 255, 255, 0.03); + border: 2px solid var(--color-border); + border-radius: 8px; + overflow: hidden; + transition: border-color 0.3s ease; +} + +.st-progress-box[data-active="true"] { + border-color: var(--color-accent); +} + +.st-progress-bar { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: var(--color-accent); + transform-origin: left; + transform: scaleX(0); +} + +.st-progress-info { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: 12px; +} + +/* Enter/Leave events */ +.st-event-box { + position: relative; + padding: 40px; + background: rgba(255, 255, 255, 0.03); + border: 2px solid var(--color-border); + border-radius: 8px; + overflow: hidden; + transition: border-color 0.3s ease, background 0.3s ease; +} + +.st-event-box[data-active="true"] { + border-color: var(--color-accent); + background: rgba(227, 6, 19, 0.08); +} + +.st-event-log { + margin-top: 16px; + padding: 12px; + background: rgba(0, 0, 0, 0.3); + border-radius: 6px; + font-family: monospace; + font-size: 11px; + color: var(--color-muted); + min-height: 80px; + line-height: 1.8; +} + +.st-event-log .st-event-enter { + color: #4ade80; +} + +.st-event-log .st-event-leave { + color: #f87171; +} + +/* Position combinations */ +.st-positions-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 16px; +} + +.st-position-card { + position: relative; + padding: 24px; + background: rgba(255, 255, 255, 0.03); + border: 2px solid var(--color-border); + border-radius: 8px; + overflow: hidden; + transition: border-color 0.3s ease; +} + +.st-position-card[data-active="true"] { + border-color: var(--color-accent); +} + +.st-position-card .st-progress-bar { + height: 2px; +} + +.st-position-label { + font-family: monospace; + font-size: 11px; + color: var(--color-muted); + margin-bottom: 8px; +} + +.st-position-value { + font-family: monospace; + font-size: 24px; + font-weight: 500; +} + +/* Steps / Staggered animation */ +.st-steps-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.st-step-item { + padding: 20px 24px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid var(--color-border); + border-radius: 8px; + font-family: monospace; + font-size: 14px; + opacity: 0.2; + transform: translateY(20px); + transition: border-color 0.2s ease; +} + +.st-step-item[data-visible="true"] { + border-color: var(--color-accent); +} + +/* Offset comparison */ +.st-offset-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} + +.st-offset-card { + position: relative; + padding: 24px; + background: rgba(255, 255, 255, 0.03); + border: 2px solid var(--color-border); + border-radius: 8px; + overflow: hidden; + transition: border-color 0.3s ease; +} + +.st-offset-card[data-active="true"] { + border-color: var(--color-accent); +} + +.st-offset-label { + font-family: monospace; + font-size: 11px; + color: var(--color-muted); + margin-bottom: 8px; +} + +.st-offset-value { + font-family: monospace; + font-size: 24px; + font-weight: 500; +} + +/* Disabled toggle */ +.st-toggle-row { + display: flex; + gap: 12px; + align-items: center; +} + +.st-toggle-row button { + font-family: monospace; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 10px 16px; + border: 1px solid var(--color-border); + border-radius: 6px; + background: var(--color-bg); + color: var(--color-fg); + cursor: pointer; + transition: all 0.15s ease; +} + +.st-toggle-row button:hover { + border-color: var(--color-accent); + color: var(--color-accent); +} + +.st-toggle-row button:active { + transform: scale(0.98); +} + +.st-toggle-row button[data-active="true"] { + background: var(--color-accent); + border-color: var(--color-accent); + color: white; +} + +/* CSS custom property demo */ +.st-css-demo { + position: relative; + height: 300px; + background: rgba(255, 255, 255, 0.03); + border: 2px solid var(--color-border); + border-radius: 8px; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; +} + +.st-css-box { + width: 100px; + height: 100px; + background: var(--color-accent); + border-radius: 8px; + transform: + translateY(calc((1 - var(--progress, 0)) * 40px)) + scale(calc(0.5 + var(--progress, 0) * 0.5)) + rotate(calc(var(--progress, 0) * 180deg)); + opacity: calc(0.2 + var(--progress, 0) * 0.8); + transition: none; +} + +.st-css-label { + position: absolute; + bottom: 12px; + left: 12px; + font-family: monospace; + font-size: 11px; + color: var(--color-muted); +} + +/* TransformProvider demo */ +.st-transform-parent { + position: relative; + padding: 24px; + background: rgba(255, 255, 255, 0.03); + border: 2px dashed var(--color-border); + border-radius: 8px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.st-transform-header { + display: flex; + gap: 12px; +} + +.st-transform-child { + position: relative; + padding: 24px; + background: rgba(255, 255, 255, 0.03); + border: 2px solid var(--color-border); + border-radius: 8px; + overflow: hidden; + transition: border-color 0.3s ease; +} + +.st-transform-child[data-active="true"] { + border-color: var(--color-accent); +} + +/* Footer */ +.st-playground footer { + padding: 40px 0; + text-align: center; +} + +.st-playground footer p { + margin: 0; + font-family: monospace; + font-size: 12px; + color: var(--color-muted); +} + +.st-playground footer a { + color: var(--color-fg); + text-decoration: none; + transition: color 0.15s; +} + +.st-playground footer a:hover { + color: var(--color-accent); +} diff --git a/playground/www/layouts/Layout.astro b/playground/www/layouts/Layout.astro index 56331c0..a1b9082 100644 --- a/playground/www/layouts/Layout.astro +++ b/playground/www/layouts/Layout.astro @@ -22,6 +22,7 @@ const { title } = Astro.props React + ScrollTrigger
diff --git a/playground/www/pages/core.astro b/playground/www/pages/core.astro deleted file mode 100644 index 0178e2a..0000000 --- a/playground/www/pages/core.astro +++ /dev/null @@ -1,18 +0,0 @@ ---- -import '~/core/style.css' -import Layout from '../layouts/Layout.astro' -import pkg from '../../../package.json' ---- - - -
-
- - - - - -
diff --git a/playground/www/pages/index.astro b/playground/www/pages/index.astro index 467ba5b..c862952 100644 --- a/playground/www/pages/index.astro +++ b/playground/www/pages/index.astro @@ -1,6 +1,6 @@ --- import Layout from '../layouts/Layout.astro' -import pkg from '../../../package.json' +import pkg from '../../../packages/hamo/package.json' --- @@ -30,6 +30,9 @@ import pkg from '../../../package.json'
  • useDebouncedCallback Debounced callbacks
  • useDebouncedEffect Debounced effects
  • useObjectFit Contain/cover calculations
  • +
  • useScrollTrigger Scroll-based progress tracking
  • +
  • useTransform Nested transform accumulation
  • +
  • useEffectEvent Stable callback reference
  • diff --git a/playground/www/pages/react.astro b/playground/www/pages/react.astro index 52d5f68..75d3648 100644 --- a/playground/www/pages/react.astro +++ b/playground/www/pages/react.astro @@ -1,6 +1,6 @@ --- import Layout from '../layouts/Layout.astro' -import pkg from '../../../package.json' +import pkg from '../../../packages/hamo/package.json' import App from '~/react/app' import '~/react/style.css' --- diff --git a/playground/www/pages/scroll-trigger.astro b/playground/www/pages/scroll-trigger.astro new file mode 100644 index 0000000..fde3dbc --- /dev/null +++ b/playground/www/pages/scroll-trigger.astro @@ -0,0 +1,10 @@ +--- +import Layout from '../layouts/Layout.astro' +import pkg from '../../../package.json' +import ScrollTriggerApp from '~/react/scroll-trigger-app' +import '~/react/scroll-trigger-style.css' +--- + + + + diff --git a/tsdown.config.ts b/tsdown.config.ts deleted file mode 100644 index 7262997..0000000 --- a/tsdown.config.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { defineConfig } from 'tsdown' - -const shared = { - outDir: 'dist', - target: 'es2022' as const, - platform: 'browser' as const, - format: 'esm' as const, - sourcemap: true, - outExtensions: () => ({ js: '.mjs', dts: '.d.ts' }), -} - -export default defineConfig([ - // React ESM - { - ...shared, - entry: { hamo: 'packages/react/index.ts' }, - dts: true, - clean: true, - banner: '"use client";', - deps: { neverBundle: ['react', 'hamo'] }, - }, -])