{"_id":"@baolq/svg-motion","name":"@baolq/svg-motion","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@baolq/svg-motion","version":"0.1.0","description":"Framework-agnostic SVG preparation and motion primitives","type":"module","license":"MIT","sideEffects":false,"engines":{"node":">=22"},"packageManager":"pnpm@10.33.0","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"}},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/unique01082/svg-motion.git"},"bugs":{"url":"https://github.com/unique01082/svg-motion/issues"},"homepage":"https://github.com/unique01082/svg-motion#readme","scripts":{"build":"vite build","lint":"eslint src test scripts apps/docs/src apps/docs/scripts apps/docs/vite.config.ts apps/docs/playwright.config.ts vite.config.ts playwright.config.ts","format":"prettier --check . --ignore-unknown","format:write":"prettier --write . --ignore-unknown","typecheck":"tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json && tsc --noEmit -p tsconfig.node-next.json","verify:package":"node test/package-runtime.mjs && tsc --noEmit -p tsconfig.package.json","test":"vitest run --exclude 'test/browser/**' --exclude 'apps/docs/**'","test:browser":"playwright test","test:consumer":"node scripts/consumer-smoke.mjs","docs:dev":"pnpm --dir apps/docs dev","docs:build":"pnpm --dir apps/docs build","docs:test":"pnpm --dir apps/docs verify","docs:api":"pnpm build && pnpm --dir apps/docs api:generate","docs:api:check":"pnpm build && pnpm --dir apps/docs api:check","docs:docker:smoke":"node scripts/docker-smoke.mjs","docs:candidate:verify":"node scripts/docs-candidate-smoke.mjs","docs:scaffold":"pnpm --dir apps/docs scaffold:version","demo":"pnpm docs:dev","demo:build":"pnpm docs:build","demo:test":"pnpm docs:test","verify":"pnpm format && pnpm lint && pnpm typecheck && pnpm test && pnpm test:browser && pnpm build && pnpm verify:package && pnpm test:consumer"},"dependencies":{"dompurify":"^3.4.13"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"devDependencies":{"@eslint/js":"^10.0.1","@playwright/test":"^1.62.1","@types/node":"^22.20.1","@types/react":"^18.3.31","@types/react-dom":"^18.3.7","eslint":"^10.8.1","globals":"^17.11.0","jsdom":"^29.0.0","prettier":"^3.9.6","react":"^18.3.1","react-dom":"^18.3.1","typescript":"^5.9.3","typescript-eslint":"^8.67.0","vite":"^8.2.1","vite-plugin-dts":"^5.0.3","vitest":"^4.1.10"},"gitHead":"219a1b9e8d8dc005815dd32da8adfbf3558e58f0","_id":"@baolq/svg-motion@0.1.0","_nodeVersion":"22.23.2","_npmVersion":"11.11.1","dist":{"integrity":"sha512-BLupRmmkgGqaEozJLnS776Y+lwg6+X/4cER35jHa+UFO1MtZbUUuXnlB6kDTWEtWj3ZOxHlF+Pmhp6X77SqieA==","shasum":"7f593da6773f453926c05058e6fc741747b8b81e","tarball":"https://registry.npmjs.org/@baolq/svg-motion/-/svg-motion-0.1.0.tgz","fileCount":9,"unpackedSize":279345,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@baolq%2fsvg-motion@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIAiPYucdp34UN2+IBFMVSlkiYkNJ2ZixNHcHPMmge2lwAiEA4bJQHcLBTMhc9p2HjiGKBtR6dcJVi185zG0gRMldZI4="}]},"_npmUser":{"name":"baolq","email":"bao.lq.it@gmail.com"},"directories":{},"maintainers":[{"name":"baolq","email":"bao.lq.it@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/svg-motion_0.1.0_1786795982510_0.8306953162412021"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-15T12:13:02.332Z","0.1.0":"2026-08-15T12:13:02.673Z","modified":"2026-08-15T12:13:03.005Z"},"maintainers":[{"name":"baolq","email":"bao.lq.it@gmail.com"}],"description":"Framework-agnostic SVG preparation and motion primitives","homepage":"https://github.com/unique01082/svg-motion#readme","repository":{"type":"git","url":"git+https://github.com/unique01082/svg-motion.git"},"bugs":{"url":"https://github.com/unique01082/svg-motion/issues"},"license":"MIT","readme":"# @baolq/svg-motion\n\n[![CI](https://github.com/unique01082/svg-motion/actions/workflows/ci.yml/badge.svg)](https://github.com/unique01082/svg-motion/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/@baolq/svg-motion)](https://www.npmjs.com/package/@baolq/svg-motion)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)\n\nSafe, browser-native animation for any SVG. `@baolq/svg-motion` is an ESM-only TypeScript library with a framework-agnostic core and an optional React adapter.\n\n| Capability              | What it provides                                                           |\n| ----------------------- | -------------------------------------------------------------------------- |\n| Any SVG source          | Markup, URL, `File`, `Blob`, or an existing `SVGSVGElement`                |\n| Framework-agnostic      | Prepare, animate, or mount SVGs without a UI framework                     |\n| React adapter           | `<SvgMotion>` and `useSvgMotion()` from a separate optional entry          |\n| Secure by default       | Sanitization, resource hardening, byte limits, and namespaced internal IDs |\n| Web Animations API      | Native playback control with no Anime.js dependency                        |\n| Verified across engines | Real-browser coverage in evergreen Chromium, Firefox, and WebKit           |\n\n[Documentation](https://svg-motion.baole.space/docs/0.1/getting-started) · [Playground](https://svg-motion.baole.space/playground) · [Install](#install) · [Vanilla](#vanilla) · [React](#react) · [Security](#security-cors-and-csp) · [Contributing](./CONTRIBUTING.md) · [Security policy](./SECURITY.md) · [Changelog](./CHANGELOG.md)\n\n## Install\n\n```sh\npnpm add @baolq/svg-motion\n```\n\nAdd `react >=18` only when using the React entry.\n\n## Documentation site\n\nThe production documentation application lives in `apps/docs`. It installs the\npublished `0.1.0` package from the registry rather than linking repository source:\n\n```bash\npnpm --dir apps/docs install --frozen-lockfile\npnpm docs:dev\n```\n\nUse `pnpm docs:test` for unit, prerender and three-engine browser checks.\n`pnpm docs:docker:smoke` verifies the production nginx image. See the\n[deployment guide](./docs/deployment/svg-motion.baole.space.md) for the later\nself-hosted rollout; repository implementation does not modify live DNS or servers.\n\n## Vanilla\n\n```ts\nimport { mountSvgMotion } from \"@baolq/svg-motion\";\n\nconst container = document.querySelector(\"#logo\")!;\nconst motion = await mountSvgMotion(container, \"/logo.svg\", {\n  preset: \"draw\",\n  duration: 1200,\n});\n\nmotion.controller.pause();\nmotion.controller.seek(0.5);\nmotion.controller.play();\n\n// Restores temporary styles, cancels animations and removes the mounted SVG.\nmotion.destroy();\n```\n\nFor a caller-owned SVG node, use `animateSvg(svg, options)`. To prepare without mounting, use `await prepareSvg(source, options)`.\n\n## React\n\n```tsx\nimport { useRef } from \"react\";\nimport { SvgMotion, type SvgMotionHandle } from \"@baolq/svg-motion/react\";\n\nexport function Logo() {\n  const motion = useRef<SvgMotionHandle>(null);\n\n  return (\n    <SvgMotion\n      ref={motion}\n      source=\"/logo.svg\"\n      preset=\"draw\"\n      autoplay\n      svgProps={{ \"aria-label\": \"Bao Le\" }}\n      fallback={<span>Logo unavailable</span>}\n    />\n  );\n}\n```\n\n`useSvgMotion(options)` returns `containerRef`, `svg`, `controller`, `status`, `error`, and `diagnostics` for custom render trees. Source changes are aborted and replaced; unmount destroys the active instance.\n\n## Sources\n\n`SvgSource` accepts:\n\n- SVG markup strings\n- same-origin or CORS-enabled URL strings and `URL` objects\n- `File` and `Blob` values\n- an `SVGSVGElement`\n\nAPIs accepting `SvgSource` always clone the artwork. `animateSvg()` is the only API that operates directly on a caller-provided node. The default size limit is 5 MiB and can be changed with `maxBytes`; remote loading accepts an `AbortSignal` through `prepareSvg()` and `mountSvgMotion()`.\n\n## Motion API\n\nPresets are `draw`, `fade`, `scale`, `stagger`, and `pulse`. Defaults are `draw`, autoplay, `duration: 1200`, `delay: 0`, `easing: \"ease-in-out\"`, one iteration, document order, and automatic staggering. Automatic staggering distributes starts across at most 600 ms; pass a millisecond number for a fixed step.\n\nOptions also include `direction`, `selector`, `order: \"document\" | \"reverse\"`, and `iterations`. Pulse repeats forever only when `iterations: Infinity` is explicit. During draw, text, image, use, and other visible leaf content fades; an SVG without drawable geometry falls back to a root fade and emits `NO_DRAWABLE_GEOMETRY`.\n\n`SvgMotionController` exposes:\n\n```ts\ncontroller.play();\ncontroller.pause();\ncontroller.reverse();\ncontroller.restart();\ncontroller.finish();\ncontroller.cancel();\ncontroller.seek(0.5); // 0..1\ncontroller.destroy();\n\ncontroller.state;\nawait controller.finished;\ncontroller.diagnostics;\n```\n\n`finish()` restores the original artwork at its final state. `cancel()` restores the initial artwork. `destroy()` cancels all animations and removes temporary inline styles. `SvgMotionInstance.destroy()` also removes the SVG appended by `mountSvgMotion()`.\n\nPreparation failures are `SvgPreparationError` instances with a safe `code`: `ABORTED`, `FETCH_FAILED`, `INVALID_SVG`, `SANITIZATION_FAILED`, `SOURCE_TOO_LARGE`, `UNSUPPORTED_ENVIRONMENT`, or `UNSUPPORTED_SOURCE`. Diagnostics use `REMOVED_UNSAFE_CONTENT`, `REMOVED_EXTERNAL_REFERENCE`, and `NO_DRAWABLE_GEOMETRY` with counts only.\n\nAnimation failures are `SvgAnimationError` instances. Their safe `code` is `INVALID_SVG`, `UNSUPPORTED_ENVIRONMENT`, `ANIMATION_SETUP_FAILED`, or `ANIMATION_FAILED`. A synchronous setup failure makes `animateSvg()` throw; `mountSvgMotion()` preserves that typed error and removes the SVG it appended. If `play()`, `reverse()`, or `restart()` cannot create or activate a new run, the method throws while the new `controller.finished` rejects with the same typed error. An unexpected native completion failure changes `controller.state` to `failed`, restores the original artwork, removes owned animations, and rejects `controller.finished` with `SvgAnimationError`. Await or catch `controller.finished` when application behavior depends on completion.\n\n## Security, CORS, and CSP\n\n`trust: \"sanitize\"` is the default. It uses DOMPurify's SVG/filter profile plus stricter rules that remove scripts, event handlers, `foreignObject`, SMIL, external stylesheets/resources, unsafe CSS, and non-local `url(...)` references. Embedded PNG, JPEG, GIF, WebP, and AVIF data images are allowed; embedded SVG images are not. IDs and local references are namespaced for every prepared instance.\n\nUse `trust: \"trusted\"` only for fully trusted SVG. Trusted mode skips filtering, so dangerous markup remains dangerous; it still clones the input, applies the byte limit, and namespaces IDs. Diagnostics contain only codes and counts, never source markup.\n\nRemote SVG fetches follow browser CORS rules. Configure `connect-src` for SVG origins. If using embedded bitmap data URLs, allow the required `data:` image type in `img-src`. A strict `style-src-attr` policy may also affect SVG presentation attributes or temporary styles; validate the library under your deployed CSP.\n\n## Accessibility and reduced motion\n\nThe React adapter preserves source `<title>` and valid semantic roles. Supplying `aria-label` or `aria-labelledby` makes the SVG an image. An SVG without an accessible name is marked `aria-hidden=\"true\"`. The component creates no replay button, click behavior, or tab stop.\n\nReduced-motion policy belongs to the consumer in v0.1.0:\n\n```ts\nconst reduce = matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\nawait mountSvgMotion(\n  node,\n  source,\n  reduce ? { preset: \"fade\", duration: 0 } : {},\n);\n```\n\n## Runtime support\n\nThe runtime targets evergreen Chromium, Firefox, and WebKit browsers with Web Animations API and `SVGGeometryElement.getTotalLength()`. Package imports are SSR-safe, but preparing, mounting, or animating SVG requires a browser DOM. The package does not include Anime.js or Ant Design.\n\n## Releasing\n\nPull requests and pushes run `pnpm verify`. Tags matching `vX.Y.Z` run the same gates, reject a tag that differs from `package.json`, and publish with public access and provenance.\n\nThe one-time scope migration publishes `@baolq/svg-motion@0.1.0` from the guarded `npm-baolq-v0.1.0` tag with a short-lived granular npm token stored as the `NPM_TOKEN` Actions secret. The workflow verifies the registry artifact before applying the migration notice to the previous package identity. After publication, configure [npm Trusted Publisher](https://docs.npmjs.com/trusted-publishers/) for `unique01082/svg-motion` and the `publish.yml` workflow, restricted to `npm publish`, then revoke and remove the bootstrap token. Later tags authenticate through GitHub OIDC (`id-token: write`) and retain provenance.\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md) for the development workflow and [CHANGELOG.md](./CHANGELOG.md) for release history.\n\n## License\n\n[MIT](./LICENSE)\n","readmeFilename":"README.md","_rev":"1-d93296e07cac360a974e0950ae78f886"}