{"_id":"@baole-space/svg-motion","_rev":"2-c5d5d54d4714f130a1bd14d0933e504c","name":"@baole-space/svg-motion","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@baole-space/svg-motion","version":"0.1.0","license":"MIT","_id":"@baole-space/svg-motion@0.1.0","maintainers":[{"name":"baolq","email":"bao.lq.it@gmail.com"}],"homepage":"https://github.com/unique01082/svg-draw-line#readme","bugs":{"url":"https://github.com/unique01082/svg-draw-line/issues"},"dist":{"shasum":"99b37a6be7ceaae722b60149454de59d454a0ca5","tarball":"https://registry.npmjs.org/@baole-space/svg-motion/-/svg-motion-0.1.0.tgz","fileCount":9,"integrity":"sha512-0FSo+nazmh2lk4A+eNm8+nJBjYvd426OeX+Ark+Ocl0qzbi5e9HZ3dnKsixbattPEgzviNtjBIYpap99DrX2/A==","signatures":[{"sig":"MEUCIQDaqQAQHUULYdUxv9iKYLbWBIPQEQ8gUsru5NhPIOvFMwIgEvyEtxbZIWODP8hlnj0Noix/wA9ReUhB7Fda0NX1oj8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@baole-space%2fsvg-motion@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":277866},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"}},"gitHead":"65d60fccd3dfebf50d67f7628a5a097b07197aab","scripts":{"lint":"eslint src test scripts vite.config.ts playwright.config.ts","test":"vitest run --exclude 'test/browser/**'","build":"vite build","format":"prettier --check . --ignore-unknown","verify":"pnpm format && pnpm lint && pnpm typecheck && pnpm test && pnpm test:browser && pnpm build && pnpm verify:package && pnpm test:consumer","typecheck":"tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json && tsc --noEmit -p tsconfig.node-next.json","format:write":"prettier --write . --ignore-unknown","test:browser":"playwright test","test:consumer":"node scripts/consumer-smoke.mjs","verify:package":"node test/package-runtime.mjs && tsc --noEmit -p tsconfig.package.json"},"_npmUser":{"name":"baolq","email":"bao.lq.it@gmail.com"},"repository":{"url":"git+https://github.com/unique01082/svg-draw-line.git","type":"git"},"_npmVersion":"11.11.1","description":"Framework-agnostic SVG preparation and motion primitives","directories":{},"sideEffects":false,"_nodeVersion":"22.23.2","dependencies":{"dompurify":"^3.4.13"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.33.0","devDependencies":{"vite":"^8.2.1","jsdom":"^29.0.0","react":"^18.3.1","eslint":"^10.8.1","vitest":"^4.1.10","globals":"^17.11.0","prettier":"^3.9.6","react-dom":"^18.3.1","@eslint/js":"^10.0.1","typescript":"^5.9.3","@types/node":"^22.20.1","@types/react":"^18.3.31","vite-plugin-dts":"^5.0.3","@playwright/test":"^1.62.1","@types/react-dom":"^18.3.7","typescript-eslint":"^8.67.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/svg-motion_0.1.0_1786771576762_0.5954907281781605","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Moved to @baolq/svg-motion"}},"time":{"created":"2026-08-15T05:26:16.533Z","modified":"2026-08-15T12:23:46.621Z","0.1.0":"2026-08-15T05:26:16.911Z"},"bugs":{"url":"https://github.com/unique01082/svg-draw-line/issues"},"license":"MIT","homepage":"https://github.com/unique01082/svg-draw-line#readme","repository":{"url":"git+https://github.com/unique01082/svg-draw-line.git","type":"git"},"description":"Framework-agnostic SVG preparation and motion primitives","maintainers":[{"name":"baolq","email":"bao.lq.it@gmail.com"}],"readme":"# @baole-space/svg-motion\n\n[![CI](https://github.com/unique01082/svg-draw-line/actions/workflows/ci.yml/badge.svg)](https://github.com/unique01082/svg-draw-line/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/@baole-space/svg-motion)](https://www.npmjs.com/package/@baole-space/svg-motion)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)\n\nSafe, browser-native animation for any SVG. `@baole-space/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[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 @baole-space/svg-motion\n```\n\nAdd `react >=18` only when using the React entry.\n\n## Vanilla\n\n```ts\nimport { mountSvgMotion } from \"@baole-space/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 \"@baole-space/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 initial `0.1.0` release bootstraps publication with a short-lived granular npm token stored as the `NPM_TOKEN` Actions secret. After the package exists, configure [npm Trusted Publisher](https://docs.npmjs.com/trusted-publishers/) for `unique01082/svg-draw-line` 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"}