{"_id":"3d-spinner-react","_rev":"7-2bcabde7d2ded47294d9d6f6e2ddfccc","name":"3d-spinner-react","dist-tags":{"latest":"1.0.1"},"versions":{"0.9.0":{"name":"3d-spinner-react","version":"0.9.0","keywords":["spinner","loader","loading","progress","3d","react","react-component","hook"],"author":{"name":"RuneL"},"license":"MIT","_id":"3d-spinner-react@0.9.0","maintainers":[{"name":"runelaang","email":"rune.laang@gmail.com"}],"homepage":"https://github.com/runelaang/3d-spinner-react#readme","bugs":{"url":"https://github.com/runelaang/3d-spinner-react/issues"},"dist":{"shasum":"be94f329990989c24b1edb163c6491980ea48585","tarball":"https://registry.npmjs.org/3d-spinner-react/-/3d-spinner-react-0.9.0.tgz","fileCount":11,"integrity":"sha512-KyyBKB0HZcWf7fmxFBydBDrmG9sgIjQ2ac9gz7ZGdmec+UmwVaLxEQ1lzLQVBigvpzZVEhL23nun8+DOmISAqA==","signatures":[{"sig":"MEUCIQDpZBVfq2UFnDPuVFC7ss8BGD18VBgxrrYl31L9Ozd9QgIgPa928/Ygevz98nfyt93ZVt+K92ru3pKCmofJ9P/Cn+Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":19209},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"1ed973579a636073453560182c5a1c3af1cc4c32","scripts":{"test":"node --test tests/*.test.mjs","build":"npm run clean && tsc","clean":"node -e \"fs.rmSync('dist', { recursive: true, force: true })\"","pretest":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"runelaang","email":"rune.laang@gmail.com"},"repository":{"url":"git+https://github.com/runelaang/3d-spinner-react.git","type":"git"},"_npmVersion":"11.12.0","description":"React bindings for 3d-spinner: a lightweight 3D spinner, loader, and progress indicator as a component and a hook.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","react":"^18.3.0","react-dom":"^18.3.1","3d-spinner":"^0.9.0","typescript":"^5.7.0","@types/react":"^18.3.0"},"peerDependencies":{"react":">=18","3d-spinner":">=0.9.0"},"_npmOperationalInternal":{"tmp":"tmp/3d-spinner-react_0.9.0_1782813738575_0.5304140132603987","host":"s3://npm-registry-packages-npm-production"}},"0.9.1":{"name":"3d-spinner-react","version":"0.9.1","keywords":["spinner","loader","loading","progress","3d","react","react-component","hook"],"author":{"name":"RuneL"},"license":"MIT","_id":"3d-spinner-react@0.9.1","maintainers":[{"name":"runelaang","email":"rune.laang@gmail.com"}],"homepage":"https://github.com/runelaang/3d-spinner-react#readme","bugs":{"url":"https://github.com/runelaang/3d-spinner-react/issues"},"dist":{"shasum":"bc693f0a2a162cc3379cf027a1cf6f956bf7bed4","tarball":"https://registry.npmjs.org/3d-spinner-react/-/3d-spinner-react-0.9.1.tgz","fileCount":11,"integrity":"sha512-dy/3B85WWlKaQMOP+lfC1329WdwAbyT7WsjH2w9/EMBnVkH9Xn+4nn3AVIhHn1H/FuhEwheaOe2l8EV2/tuPEQ==","signatures":[{"sig":"MEYCIQDaOxDIgeceErb4XP2DDQ5Q96tbYy7KyXcPPpTvDsg3hAIhAJ61wbApz0DBX5YpC3i5ien8OYjk2EEyRAm6lXc9RxgK","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":19209},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"8e3bf771499de37dc3e9fc3dcae75e86f3fe101b","scripts":{"test":"node --test tests/*.test.mjs","build":"npm run clean && tsc","clean":"node -e \"fs.rmSync('dist', { recursive: true, force: true })\"","pretest":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"runelaang","email":"rune.laang@gmail.com"},"repository":{"url":"git+https://github.com/runelaang/3d-spinner-react.git","type":"git"},"_npmVersion":"11.12.0","description":"React bindings for 3d-spinner: a lightweight 3D spinner, loader, and progress indicator as a component and a hook.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","react":"^18.3.0","react-dom":"^18.3.1","3d-spinner":"^0.9.2","typescript":"^5.7.0","@types/react":"^18.3.0"},"peerDependencies":{"react":">=18","3d-spinner":">=0.9.2"},"_npmOperationalInternal":{"tmp":"tmp/3d-spinner-react_0.9.1_1782952182117_0.7658388719313516","host":"s3://npm-registry-packages-npm-production"}},"0.9.2":{"name":"3d-spinner-react","version":"0.9.2","keywords":["spinner","loader","loading","progress","3d","react","react-component","hook"],"author":{"name":"RuneL"},"license":"MIT","_id":"3d-spinner-react@0.9.2","maintainers":[{"name":"runelaang","email":"rune.laang@gmail.com"}],"homepage":"https://github.com/runelaang/3d-spinner-react#readme","bugs":{"url":"https://github.com/runelaang/3d-spinner-react/issues"},"dist":{"shasum":"a10c51e045e03f67a8f15ff82042c4781ea44880","tarball":"https://registry.npmjs.org/3d-spinner-react/-/3d-spinner-react-0.9.2.tgz","fileCount":11,"integrity":"sha512-iWuvtc+vp0IpDRLh/haLWmBbRKF7D4ucrPE0j1AzLmUBYohs9dhAplMWLKSjE45/BypiPFnBdoFCEsvnua30Fw==","signatures":[{"sig":"MEUCIB38A7L5VJ0Bn77ZpQwOUix6n9/aFrLda8pk+TAvgeJkAiEA7QSZVrt/d6ePyduoeep7io5I+o4shcvbLwLE1gSOhCE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":23276},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"3bdbf76006c50c0e529425a8f6ee3df225b3a9e6","scripts":{"test":"node --test tests/*.test.mjs","build":"npm run clean && tsc","clean":"node -e \"fs.rmSync('dist', { recursive: true, force: true })\"","pretest":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"runelaang","email":"rune.laang@gmail.com"},"repository":{"url":"git+https://github.com/runelaang/3d-spinner-react.git","type":"git"},"_npmVersion":"11.12.0","description":"React bindings for 3d-spinner: a lightweight 3D spinner, loader, and progress indicator as a component and a hook.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","react":"^18.3.0","react-dom":"^18.3.1","3d-spinner":"^0.9.3","typescript":"^5.7.0","@types/react":"^18.3.0"},"peerDependencies":{"react":">=18","3d-spinner":">=0.9.3"},"_npmOperationalInternal":{"tmp":"tmp/3d-spinner-react_0.9.2_1783288927387_0.014160331299996054","host":"s3://npm-registry-packages-npm-production"}},"0.9.4":{"name":"3d-spinner-react","version":"0.9.4","keywords":["spinner","loader","loading","progress","3d","react","react-component","hook"],"author":{"name":"RuneL"},"license":"MIT","_id":"3d-spinner-react@0.9.4","maintainers":[{"name":"runelaang","email":"rune.laang@gmail.com"}],"homepage":"https://github.com/runelaang/3d-spinner-react#readme","bugs":{"url":"https://github.com/runelaang/3d-spinner-react/issues"},"dist":{"shasum":"9544116df05b4bf05d872dc9c19798489c5f383e","tarball":"https://registry.npmjs.org/3d-spinner-react/-/3d-spinner-react-0.9.4.tgz","fileCount":11,"integrity":"sha512-+B6eu1eXcftyzWUuipMS6jrkYfko6Awe8gVO4VAGZUi9kZVlQwUlinLftAc7x/gpy/3MiJXSPjGYyG+WPHaluQ==","signatures":[{"sig":"MEYCIQD2WkIlngSvJpPogsf3pjZZXy3AVnMf/gbv5yJbb1YzCQIhAIyLt0X/PIVlqxxbH8vqVA+uF3yVnuwwaq74/0nzjRE/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":23289},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"e7df4334e90a1313bb9ea57526a5dddca12e7f29","scripts":{"test":"node --test tests/*.test.mjs","build":"npm run clean && tsc","clean":"node -e \"fs.rmSync('dist', { recursive: true, force: true })\"","pretest":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"runelaang","email":"rune.laang@gmail.com"},"repository":{"url":"git+https://github.com/runelaang/3d-spinner-react.git","type":"git"},"_npmVersion":"11.12.0","description":"React bindings for 3d-spinner: a lightweight 3D spinner, loader, and progress indicator as a component and a hook.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","react":"^18.3.0","react-dom":"^18.3.1","3d-spinner":"^0.9.5","typescript":"^5.7.0","@types/react":"^18.3.0"},"peerDependencies":{"react":">=18","3d-spinner":">=0.9.5"},"_npmOperationalInternal":{"tmp":"tmp/3d-spinner-react_0.9.4_1783670233682_0.9791763041246149","host":"s3://npm-registry-packages-npm-production"}},"0.9.5":{"name":"3d-spinner-react","version":"0.9.5","keywords":["spinner","loader","loading","progress","3d","react","react-component","hook"],"author":{"name":"RuneL"},"license":"MIT","_id":"3d-spinner-react@0.9.5","maintainers":[{"name":"runelaang","email":"rune.laang@gmail.com"}],"homepage":"https://github.com/runelaang/3d-spinner-react#readme","bugs":{"url":"https://github.com/runelaang/3d-spinner-react/issues"},"dist":{"shasum":"325c03419604210cd28ca26d65ef678ea4409051","tarball":"https://registry.npmjs.org/3d-spinner-react/-/3d-spinner-react-0.9.5.tgz","fileCount":11,"integrity":"sha512-IgXjMctgpvlEcURtsVc5rwEAtFnlDuN3eG4ozzAwcaNCIgIiZtXXwSj8BQIA3oUU5P3O+C1EITtQ0FqNY2oGHw==","signatures":[{"sig":"MEUCIBdeHapjmkPlclBydNHsXecfZJGDaVb1ukyjOLRVKzHkAiEAqV1vpfv5p7O59zk5onJCsch+HxHGpIMEXj4lNTwpyxE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":23693},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"a5fae9ea1e2941a8687f5f2708b673bbc3aa9794","scripts":{"test":"node --test tests/*.test.mjs","build":"npm run clean && tsc","clean":"node -e \"fs.rmSync('dist', { recursive: true, force: true })\"","pretest":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"runelaang","email":"rune.laang@gmail.com"},"repository":{"url":"git+https://github.com/runelaang/3d-spinner-react.git","type":"git"},"_npmVersion":"11.16.0","description":"React bindings for 3d-spinner: a lightweight 3D spinner, loader, and progress indicator as a component and a hook.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","react":"^18.3.0","react-dom":"^18.3.1","3d-spinner":"^0.9.9","typescript":"^5.7.0","@types/react":"^18.3.0"},"peerDependencies":{"react":">=18","3d-spinner":">=0.9.9"},"_npmOperationalInternal":{"tmp":"tmp/3d-spinner-react_0.9.5_1785487502538_0.4597901463266343","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"3d-spinner-react","version":"1.0.0","keywords":["spinner","loader","loading","progress","3d","react","react-component","hook"],"author":{"name":"RuneL"},"license":"MIT","_id":"3d-spinner-react@1.0.0","maintainers":[{"name":"runelaang","email":"rune.laang@gmail.com"}],"homepage":"https://github.com/runelaang/3d-spinner-react#readme","bugs":{"url":"https://github.com/runelaang/3d-spinner-react/issues"},"dist":{"shasum":"e451c680235667ec6f0bac7df394e719fb950546","tarball":"https://registry.npmjs.org/3d-spinner-react/-/3d-spinner-react-1.0.0.tgz","fileCount":11,"integrity":"sha512-KHgFfkqTP5aVSjW5NxyGX266mLafFELavCfM/jFSZBDzDaGjiXlqLnvwSUMyvgFgmy6nPKpkPbsIzUpHixW38Q==","signatures":[{"sig":"MEQCIGBpTQZcahCkdUqlN0zhLMqIaHCZgD4yGet7DzhZkIy2AiAYCmr4lSD1BXIUdr78jR5lhRIFEEee00ZvdFJnW8oPWQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQDK9RVq167JENZidGSlYat0FLvcxjKaKNG1pkNJLEa+8AIgLWo/3x+wNdpqRFTrxJPtCgjO3LHLBS/ST9fDKi9fTS4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":24616},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"3dbf0a3f238a8d5b69529171c45dae0f4ee135cb","scripts":{"test":"node --test tests/*.test.mjs","build":"npm run clean && tsc","clean":"node -e \"fs.rmSync('dist', { recursive: true, force: true })\"","pretest":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"runelaang","email":"rune.laang@gmail.com"},"repository":{"url":"git+https://github.com/runelaang/3d-spinner-react.git","type":"git"},"_npmVersion":"11.19.0","description":"React bindings for 3d-spinner: a lightweight 3D spinner, loader, and progress indicator as a component and a hook.","directories":{},"sideEffects":false,"_nodeVersion":"24.20.0","_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","react":"^18.3.0","react-dom":"^18.3.1","3d-spinner":"^1.0.0","typescript":"^5.7.0","@types/react":"^18.3.0"},"peerDependencies":{"react":">=18","3d-spinner":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/3d-spinner-react_1.0.0_1790676360985_0.2570881998929271","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"_id":"3d-spinner-react@1.0.1","bugs":{"url":"https://github.com/runelaang/3d-spinner-react/issues"},"dist":{"shasum":"ba462fcc2a34a6c5cf3f94dfb1a8a37d045ed241","tarball":"https://registry.npmjs.org/3d-spinner-react/-/3d-spinner-react-1.0.1.tgz","fileCount":11,"integrity":"sha512-7Lc/cumaadlqOI0XkBLdZy+HNrWMr2uH29CHo+TyXUxvy4zQGRwoGMRtwQm5fJG2rxxPkU19ztvqtCDhWuUkcg==","signatures":[{"sig":"MEUCIQC3a8qMSEV8OVckUeuU6YJSA5bYKixi+/8TcHGrK0MUhAIgM1gqziEFeVLvJ9D3nGIvwQX/efxyUG1O2AMf4LZoxDs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC2XZPbsd63jcFNDs4ivcNEOgiBwJIf/zp2Gvp1KBzfgQIgEw3QaCWjhDtDZAjLwrdTezpnBs8FY/c1YIt3pNrfPis="}],"unpackedSize":25012},"main":"./dist/index.js","name":"3d-spinner-react","type":"module","types":"./dist/index.d.ts","author":{"name":"RuneL"},"module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"4cfdde36791426fc93a3472eee6c68476d26c7c5","license":"MIT","scripts":{"test":"node --test tests/*.test.mjs","build":"npm run clean && tsc","clean":"node -e \"fs.rmSync('dist', { recursive: true, force: true })\"","pretest":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"version":"1.0.1","_npmUser":{"name":"runelaang","email":"rune.laang@gmail.com"},"homepage":"https://github.com/runelaang/3d-spinner-react#readme","keywords":["spinner","loader","loading","progress","3d","react","react-component","hook"],"repository":{"url":"git+https://github.com/runelaang/3d-spinner-react.git","type":"git"},"_npmVersion":"11.19.0","description":"React bindings for 3d-spinner: a lightweight 3D spinner, loader, and progress indicator as a component and a hook.","directories":{},"maintainers":[{"name":"runelaang","email":"rune.laang@gmail.com"}],"sideEffects":false,"_nodeVersion":"24.20.0","_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","react":"^18.3.0","react-dom":"^18.3.1","3d-spinner":"^1.0.0","typescript":"^5.7.0","@types/react":"^18.3.0"},"peerDependencies":{"react":">=18","3d-spinner":"^1.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/3d-spinner-react_1.0.1_1790690108806_0.4238483691017494"}}},"time":{"created":"2026-06-30T10:02:18.499Z","modified":"2026-09-29T13:55:09.055Z","0.9.0":"2026-06-30T10:02:18.713Z","0.9.1":"2026-07-02T00:29:42.263Z","0.9.2":"2026-07-05T22:02:07.512Z","0.9.4":"2026-07-10T07:57:13.832Z","0.9.5":"2026-07-31T08:45:02.674Z","1.0.0":"2026-09-29T10:06:01.095Z","1.0.1":"2026-09-29T13:55:08.897Z"},"bugs":{"url":"https://github.com/runelaang/3d-spinner-react/issues"},"author":{"name":"RuneL"},"license":"MIT","homepage":"https://github.com/runelaang/3d-spinner-react#readme","keywords":["spinner","loader","loading","progress","3d","react","react-component","hook"],"repository":{"url":"git+https://github.com/runelaang/3d-spinner-react.git","type":"git"},"description":"React bindings for 3d-spinner: a lightweight 3D spinner, loader, and progress indicator as a component and a hook.","maintainers":[{"name":"runelaang","email":"rune.laang@gmail.com"}],"readme":"# 3d-spinner-react\r\n\r\n[![tests](https://img.shields.io/github/actions/workflow/status/runelaang/3d-spinner-react/ci.yml?label=tests&logo=github)](https://github.com/runelaang/3d-spinner-react/actions/workflows/ci.yml)\r\n[![npm](https://img.shields.io/npm/v/3d-spinner-react?logo=npm)](https://www.npmjs.com/package/3d-spinner-react)\r\n[![license](https://img.shields.io/github/license/runelaang/3d-spinner-react)](LICENSE)\r\n\r\nReact bindings for [`3d-spinner`](https://www.npmjs.com/package/3d-spinner) - a\r\nzero-dependency 3D spinner, loader, and progress indicator that draws to a\r\ncanvas. You get a `<Spinner>` component and a `useSpinner` hook for mounting,\r\ncleanup, and progress. Import the animations and prefabs you need from\r\n`3d-spinner`; only what you import ends up in your bundle.\r\n\r\n**[Live demo](https://runelaang.github.io/3d-spinner-react/)** - the prefabs and common React\r\npatterns, each shown with the complete component it runs.\r\n\r\n## Install\r\n\r\n```sh\r\nnpm install 3d-spinner-react 3d-spinner react\r\n```\r\n\r\n`3d-spinner` (1.x) and `react` (18+) are peer dependencies.\r\n\r\n## Screenshots\r\n\r\nSome ready-made prefabs from `3d-spinner/prefabs`, shown here through `<Spinner>`:\r\n\r\n| `starSwarm` with custom HTML | `pulsingStarfield` | `chargedOrb` at 74% |\r\n| --- | --- | --- |\r\n| ![starSwarm prefab with a custom HTML label](screenshots/shot1.png) | ![pulsingStarfield prefab with a Preparing launch label](screenshots/shot2.png) | ![chargedOrb progress prefab at 74 percent](screenshots/shot3.png) |\r\n\r\n| `ghostTrain` progress prefab | `ParticlesAnimation` with glow texture |\r\n| --- | --- |\r\n| ![ghostTrain progress prefab on a tilted track](screenshots/shot4.png) | ![particle loader with a Just a sec label](screenshots/shot5.png) |\r\n\r\n## Prefabs\r\n\r\n`3d-spinner/prefabs` ships complete setups - layered animation, labels, and\r\nsensible defaults already picked. Import one, pass its `animation` into\r\n`<Spinner>`, and give the container a width and height.\r\n\r\n> **Pass `animation` as a factory** (`() => prefab().animation`), not a bare\r\n> instance. Each mount gets a fresh animation - that is what keeps things working\r\n> under StrictMode and when props change. `3d-spinner` throws if the same\r\n> animation instance is mounted twice.\r\n\r\nIndeterminate prefab:\r\n\r\n```tsx\r\nimport { Spinner } from \"3d-spinner-react\";\r\nimport { starSwarm } from \"3d-spinner/prefabs\";\r\n\r\nfunction Loading() {\r\n  return (\r\n    <Spinner\r\n      type=\"indeterminate\"\r\n      animation={() => starSwarm({ label: \"Just a sec\" }).animation}\r\n      style={{ width: 240, height: 240 }}\r\n    />\r\n  );\r\n}\r\n```\r\n\r\nProgress prefab - set `progress` from state; at `1` the outro plays:\r\n\r\n```tsx\r\nimport { useState } from \"react\";\r\nimport { Spinner } from \"3d-spinner-react\";\r\nimport { chargedOrb } from \"3d-spinner/prefabs\";\r\n\r\nfunction Upload() {\r\n  const [progress, setProgress] = useState(0.74);\r\n  return (\r\n    <Spinner\r\n      progress={progress}\r\n      animation={() => chargedOrb({ label: \"Uploading\" }).animation}\r\n      style={{ width: 240, height: 240 }}\r\n    />\r\n  );\r\n}\r\n```\r\n\r\nYou can override `backend`, `label`, and `fadeLabel` on the prefab. Labels fade\r\nwith the intro and outro by default; set `fadeLabel: false` to keep one fully\r\nvisible. Motion prefabs also take `object` and `particles` options. A label can\r\nbe plain text or an `HTMLElement`. If your factory closes over a prop, add it to\r\n`deps` so the spinner rebuilds when it changes.\r\n\r\nOnly the prefab's `animation` reaches `<Spinner>`, so spinner options such as\r\n`periodMs`, `loop`, `timeoutMs`, and `ariaLabel` go on `<Spinner>` itself.\r\n\r\n```tsx\r\nimport { Spinner } from \"3d-spinner-react\";\r\nimport { planeStarTrail } from \"3d-spinner/prefabs\";\r\n\r\nfunction LaunchPreview({ rate }: { rate: number }) {\r\n  return (\r\n    <Spinner\r\n      type=\"indeterminate\"\r\n      animation={() =>\r\n        planeStarTrail({ label: \"Preparing preview\", particles: { rate } }).animation\r\n      }\r\n      deps={[rate]}\r\n      style={{ width: 280, height: 280 }}\r\n    />\r\n  );\r\n}\r\n```\r\n\r\nAvailable prefabs (each at `3d-spinner/prefabs`):\r\n\r\n| Export | Mode | Description |\r\n| --- | --- | --- |\r\n| `starSwarm` | indeterminate | Bright star particles around a centered message. |\r\n| `pulsingStarfield` | indeterminate | High-shine particles around a slowly pulsing label. |\r\n| `planeStarTrail` | indeterminate | A small plane looping through a star-particle stream. |\r\n| `monochromeStreak` | indeterminate | Minimal streak motion with particles. |\r\n| `crystalComet` | indeterminate | Comet with a particle tail. |\r\n| `rocketLaunch` | progress | Rocket launch with exhaust particles. |\r\n| `chargedOrb` | progress | Orb that grows satellites as progress climbs. |\r\n| `ghostTrain` | progress | Ice-cube train on a tilted track with a star trail. |\r\n| `gridAssembly` | progress | Grid pieces assemble as progress advances. |\r\n\r\nSee [`3d-spinner`'s docs](https://www.npmjs.com/package/3d-spinner) for how prefabs\r\nare built, plus custom animations, shapes, motion paths, and rendering backends.\r\n\r\n## Quick start\r\n\r\nAn indeterminate spinner runs until you unmount it. Give the container a width\r\nand height - the canvas fills that box.\r\n\r\n```tsx\r\nimport { Spinner } from \"3d-spinner-react\";\r\nimport { SpinAnimation } from \"3d-spinner/animations/spin\";\r\n\r\nfunction Loading() {\r\n  return (\r\n    <Spinner\r\n      type=\"indeterminate\"\r\n      animation={() => new SpinAnimation({ color: \"#3b82f6\" })}\r\n      style={{ width: 120, height: 120 }}\r\n    />\r\n  );\r\n}\r\n```\r\n\r\n## Reporting progress\r\n\r\nLeave out `type` (it defaults to `\"progress\"`) and update `progress` from state.\r\nChanges call `setProgress` without rebuilding the spinner; at `1` the outro plays.\r\nAdd `progressAnimation` to `SpinAnimation` if you want it to pop in and scale with\r\nprogress.\r\n\r\n```tsx\r\nimport { useState } from \"react\";\r\nimport { Spinner } from \"3d-spinner-react\";\r\nimport { SpinAnimation } from \"3d-spinner/animations/spin\";\r\n\r\nfunction Upload() {\r\n  const [progress, setProgress] = useState(0);\r\n  return (\r\n    <Spinner\r\n      progress={progress}\r\n      animation={() => new SpinAnimation({ progressAnimation: {} })}\r\n      style={{ width: 160, height: 160 }}\r\n    />\r\n  );\r\n}\r\n```\r\n\r\n## Choosing a shape or animation\r\n\r\nThis wrapper does not bundle the 3D engine - import shapes and animations from\r\n`3d-spinner` yourself.\r\n\r\n```tsx\r\nimport { Spinner } from \"3d-spinner-react\";\r\nimport { SpinAnimation } from \"3d-spinner/animations/spin\";\r\nimport { tetrahedron } from \"3d-spinner/engines/little-3d-engine\";\r\n\r\nfunction Themed({ color }: { color: string }) {\r\n  return (\r\n    <Spinner\r\n      type=\"indeterminate\"\r\n      animation={() => new SpinAnimation({ shape: tetrahedron(), color })}\r\n      deps={[color]}\r\n      style={{ width: 120, height: 120 }}\r\n    />\r\n  );\r\n}\r\n```\r\n\r\nEach animation is imported from its own subpath:\r\n\r\n| Import | Class | Description |\r\n| --- | --- | --- |\r\n| `3d-spinner/animations/spin` | `SpinAnimation` | A spinning 3D shape, a cube by default. |\r\n| `3d-spinner/animations/object-motion` | `ObjectMotionAnimation` | A mesh that follows a motion path, with an intro/outro you choose. |\r\n| `3d-spinner/animations/particles` | `ParticlesAnimation` | Camera-facing billboard particles: burst, fountain, snow, confetti. |\r\n| `3d-spinner/animations/charged-orb` | `ChargedOrbAnimation` | Progress-driven orb with satellite pop-outs. |\r\n| `3d-spinner/animations/ghost-train` | `GhostTrainAnimation` | Progress story: translucent train gains a car per 2%, then blasts off. |\r\n| `3d-spinner/animations/grid-assembly` | `GridAssemblyAnimation` | Grid pieces that assemble with progress. |\r\n| `3d-spinner/animations/rocket-launch` | `RocketLaunchAnimation` | Progress story: rockets line up on the pad, then blast off at 100%. |\r\n| `3d-spinner/composite-animation` | `CompositeAnimation` | Several animations as layers (how the prefabs combine effects). |\r\n\r\nShapes exported from `3d-spinner/engines/little-3d-engine` include `cube`,\r\n`tetrahedron`, `octahedron`, `pyramid`, `quad`, `planeMesh`, and several spheres\r\n(`uvSphere`, `icosphere`, `octaSphere`, `cubeSphere`).\r\n\r\n## The `useSpinner` hook\r\n\r\nUse the hook when you already have a container ref or want to call\r\n`stop()` / `setProgress()` from code. It returns a stable handle.\r\n\r\n```tsx\r\nimport { useRef } from \"react\";\r\nimport { useSpinner } from \"3d-spinner-react\";\r\nimport { starSwarm } from \"3d-spinner/prefabs\";\r\n\r\nfunction ManualControl() {\r\n  const ref = useRef<HTMLDivElement>(null);\r\n  const spinner = useSpinner(ref, {\r\n    type: \"indeterminate\",\r\n    animation: () => starSwarm().animation,\r\n  });\r\n\r\n  return (\r\n    <>\r\n      <div ref={ref} style={{ width: 240, height: 240 }} />\r\n      <button onClick={() => spinner.stop()}>Stop</button>\r\n    </>\r\n  );\r\n}\r\n```\r\n\r\nThe `<Spinner>` component forwards a ref to the same handle:\r\n\r\n```tsx\r\nconst ref = useRef<SpinnerHandle>(null);\r\n<Spinner ref={ref} animation={() => new SpinAnimation()} /* ... */ />;\r\nref.current?.stop();\r\n```\r\n\r\n## API\r\n\r\n### `<Spinner>`\r\n\r\nA `div` that hosts the spinner. Takes the options below plus normal `div` props\r\n(`className`, `style`, `aria-*`, ...). Defaults to `position: relative` and\r\n**needs a size** - with no height, you get a blank 0x0 canvas.\r\n\r\n### `useSpinner(targetRef, config, deps?)`\r\n\r\nMounts a spinner into `targetRef.current` and keeps it in sync. Returns a\r\n`SpinnerHandle`.\r\n\r\n### Options (`SpinnerConfig`)\r\n\r\n| Option | Type | Description |\r\n| --- | --- | --- |\r\n| `animation` | `SpinnerAnimation \\| () => SpinnerAnimation` | The visual to play. Prefer a factory. Required. |\r\n| `type` | `\"progress\" \\| \"indeterminate\"` | Mode. Default `\"progress\"`. |\r\n| `progress` | `number` | Progress `0..1` (progress mode). Reactive. |\r\n| `timeoutMs` | `number` | Auto-complete after this many ms (progress mode). |\r\n| `until` | `Date` | Auto-complete at this time (progress mode). |\r\n| `loop` | `\"bounce\" \\| \"restart\"` | Loop style (indeterminate mode). Default `\"bounce\"`. |\r\n| `periodMs` | `number` | Ms for one sweep (indeterminate mode). Default `2000`. |\r\n| `ariaLabel` | `string` | Accessible name of the spinner's progress bar. Default `\"Loading\"`. |\r\n| `deps` | `DependencyList` | Extra rebuild triggers for values captured in an `animation` factory. Default `[]`. |\r\n\r\nStructural options (`type`, `loop`, `periodMs`, `timeoutMs`, `until`, `ariaLabel`)\r\nrebuild the spinner automatically when they change. `progress` is applied without\r\na rebuild.\r\n\r\n`ariaLabel` names the progress bar that screen readers announce. An `aria-label`\r\nprop on `<Spinner>` goes on the host `div` like any other `div` attribute.\r\n\r\n### `SpinnerHandle`\r\n\r\n| Method | Description |\r\n| --- | --- |\r\n| `setProgress(target)` | Advance progress toward `target` (`0..1`). No-op when indeterminate. |\r\n| `stop()` | Play the outro, then stop. Keeps the element. |\r\n| `destroy()` | Stop now and remove the element. Called automatically on unmount. |\r\n\r\n## Requirements\r\n\r\nReact 18+. The spinner draws to a canvas, so it only runs in the browser - not\r\nduring SSR. It mounts in a client effect after hydration.\r\n\r\nThe package is marked `\"use client\"`, so in Next.js App Router you can import\r\n`<Spinner>` without adding your own client wrapper.\r\n\r\n## Development\r\n\r\n```sh\r\nnpm install\r\nnpm run build      # compile src/ to dist/ (ESM + type declarations)\r\nnpm run typecheck  # type-check without emitting\r\nnpm test           # build, then run the unit tests\r\n```\r\n\r\nThe live demo is a separate Vite app in `demo/` that runs the source in `src/`:\r\n\r\n```sh\r\ncd demo\r\nnpm install\r\nnpm run dev        # http://localhost:5173/3d-spinner-react/\r\n```\r\n\r\n## License\r\n\r\nMIT (c) Rune Laang\r\n","readmeFilename":"README.md"}