{"_id":"@benjc/rehype-gif-controls","name":"@benjc/rehype-gif-controls","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@benjc/rehype-gif-controls","version":"1.0.0","description":"A rehype plugin that automatically adds interactive controls to GIF images with responsive canvas rendering, auto-play limits, and click-to-replay functionality","keywords":["rehype","plugin","gif","interactive","controls","mdx","markdown","html","canvas","responsive","self-contained","bem","web-component"],"homepage":"https://github.com/benjamincharity/rehype-gif-controls","bugs":{"url":"https://github.com/benjamincharity/rehype-gif-controls/issues"},"repository":{"type":"git","url":"git+https://github.com/benjamincharity/rehype-gif-controls.git"},"funding":{"type":"github","url":"https://github.com/sponsors/benjamincharity"},"license":"MIT","author":{"name":"Benjamin Charity","email":"ben@benjamincharity.com","url":"https://benjamincharity.com"},"sideEffects":["./lib/client.js","./src/client.ts"],"type":"module","exports":{".":{"development":"./src/index.ts","default":"./lib/index.js"},"./client":{"development":"./src/client.ts","default":"./lib/client.js"}},"main":"./lib/index.js","types":"./lib/index.d.ts","scripts":{"build":"tsc --project tsconfig.build.json && tsc-alias -p tsconfig.build.json && cp -r src/gif-player lib/","changeset":"changeset","changeset:version":"changeset version && npm install --package-lock-only","changeset:publish":"npm run build && changeset publish","clean":"rimraf lib","dev":"tsc --project tsconfig.build.json --watch","format":"prettier --write .","lint":"xo","prebuild":"npm run clean","prepack":"npm run build","release":"npm run build && npm run lint && npm run test && changeset publish","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit"},"dependencies":{"unist-util-visit":"^5.0.0"},"peerDependencies":{"unified":">=10.0.0 || >=11.0.0"},"devDependencies":{"@changesets/cli":"^2.29.7","@types/hast":"^3.0.4","@types/node":"^20.11.16","@types/unist":"^3.0.2","prettier":"^3.2.5","rehype":"^13.0.0","rehype-parse":"^9.0.0","rehype-stringify":"^10.0.0","rimraf":"^5.0.5","tsc-alias":"^1.8.8","typescript":"^5.3.3","unified":"^11.0.0","vitest":"^1.2.2","xo":"^0.56.0"},"packageManager":"npm@10.2.4","publishConfig":{"access":"public","provenance":true},"xo":{"prettier":true,"prettierOptions":{"semi":true,"singleQuote":true,"tabWidth":2,"trailingComma":"es5","useTabs":false,"bracketSpacing":true},"ignores":["src/gif-player/"],"rules":{"unicorn/prefer-node-protocol":"off","@typescript-eslint/prefer-nullish-coalescing":"off","@typescript-eslint/no-unsafe-assignment":"off","@typescript-eslint/no-unsafe-call":"off","unicorn/no-array-for-each":"off","import/no-anonymous-default-export":"off","unicorn/prefer-top-level-await":"off","n/prefer-global/process":"off","no-await-in-loop":"off"},"space":2},"_id":"@benjc/rehype-gif-controls@1.0.0","gitHead":"e6b672bdc787cc0e8f9f6070eebd9b78a07bd314","_nodeVersion":"20.19.5","_npmVersion":"10.8.2","dist":{"integrity":"sha512-PdEWNGg5Rm2WXW3tm/15MvlLRIj/S6ORyBQrVJbj5mzE2jmboUNrjj94ApNI3nrm7lJY7agb3MLKqxsmvuvX0g==","shasum":"46403f5d19381e6cd4eacdcbf97409c2e93d3f24","tarball":"https://registry.npmjs.org/@benjc/rehype-gif-controls/-/rehype-gif-controls-1.0.0.tgz","fileCount":22,"unpackedSize":82256,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@benjc%2frehype-gif-controls@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDTACGZOT/sUqp5tkYEJenmFs6EVBD36pNT1YywGchengIhAKzdJQ2XGWy1NxBuDOfre1sh+jZwU9A/DJ4asyUSY+k8"}]},"_npmUser":{"name":"benjamincharity","email":"ben.charity@hey.com"},"directories":{},"maintainers":[{"name":"benjamincharity","email":"ben.charity@hey.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/rehype-gif-controls_1.0.0_1758484527393_0.2820258114082319"},"_hasShrinkwrap":false}},"time":{"created":"2025-09-21T19:55:27.299Z","1.0.0":"2025-09-21T19:55:27.581Z","modified":"2025-09-21T19:55:28.474Z"},"maintainers":[{"name":"benjamincharity","email":"ben.charity@hey.com"}],"description":"A rehype plugin that automatically adds interactive controls to GIF images with responsive canvas rendering, auto-play limits, and click-to-replay functionality","homepage":"https://github.com/benjamincharity/rehype-gif-controls","keywords":["rehype","plugin","gif","interactive","controls","mdx","markdown","html","canvas","responsive","self-contained","bem","web-component"],"repository":{"type":"git","url":"git+https://github.com/benjamincharity/rehype-gif-controls.git"},"author":{"name":"Benjamin Charity","email":"ben@benjamincharity.com","url":"https://benjamincharity.com"},"bugs":{"url":"https://github.com/benjamincharity/rehype-gif-controls/issues"},"license":"MIT","readme":"# @benjc/rehype-gif-controls\n\n[![npm version](https://badge.fury.io/js/@benjc%2Frehype-gif-controls.svg)](https://badge.fury.io/js/@benjc%2Frehype-gif-controls)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nA [rehype](https://github.com/rehypejs/rehype) plugin that automatically transforms GIF images into interactive, responsive web components with advanced playback controls. Features include auto-play limits, viewport detection, click-to-replay functionality, and efficient canvas rendering.\n\n## Features\n\n- 🎮 **Interactive Controls**: Click to pause/resume, frame scrubbing, speed control\n- 🖼️ **Responsive Canvas Rendering**: High-performance GIF playback with auto-sizing\n- 🔢 **Auto-play Control**: Configurable play counts and viewport-based triggering\n- 👁️ **Viewport Detection**: Smart auto-play when GIFs enter the visible area\n- 🔒 **Security First**: Domain validation and content sanitization\n- ⚡ **Self-Contained**: No external dependencies - all GIF processing built-in\n- 🎨 **BEM CSS Classes**: Clean, predictable styling with proper naming conventions\n- 📱 **Mobile Optimized**: Touch-friendly controls and responsive behavior\n- 🔧 **Framework Agnostic**: Works with any rehype/unified setup\n\n## Installation\n\n```bash\nnpm install @benjc/rehype-gif-controls\n```\n\n## Quick Start\n\n### 1. Add the Plugin\n\n```typescript\nimport { unified } from 'unified';\nimport rehypeGifControls from '@benjc/rehype-gif-controls';\n\nconst processor = unified().use(rehypeGifControls);\n```\n\n### 2. That's it!\n\nThe plugin automatically injects the client-side script when GIFs are found. No additional imports needed for basic usage.\n\n**Optional**: For manual control, you can import the client directly:\n\n```typescript\n// Optional: For manual initialization control\nimport '@benjc/rehype-gif-controls/client';\n```\n\n### 3. Markdown Input\n\n```markdown\n![Animated demo](./demo.gif)\n![Loading animation](https://example.com/loader.gif)\n```\n\n### 4. Generated Output (with Auto-Injected Script)\n\n```html\n<div\n  class=\"gif-controls\"\n  data-gif-controls=\"true\"\n  data-gif-controls-delay=\"500\"\n  data-gif-controls-autoplay=\"true\"\n>\n  <gif-player\n    src=\"./demo.gif\"\n    class=\"gif-controls__player\"\n    repeat\n    alt=\"Animated demo\"\n  >\n  </gif-player>\n</div>\n\n<!-- Auto-injected script -->\n<script\n  type=\"module\"\n  data-gif-controls-script=\"true\"\n  src=\"./lib/client.js\"\n></script>\n```\n\n## Usage Example\n\n### Basic Rehype Pipeline\n\n```typescript\nimport { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkRehype from 'remark-rehype';\nimport rehypeGifControls from '@benjc/rehype-gif-controls';\nimport rehypeStringify from 'rehype-stringify';\n\nconst processor = unified()\n  .use(remarkParse)\n  .use(remarkRehype)\n  .use(rehypeGifControls, {\n    gifPlayer: {\n      delay: 500,\n      autoplay: true,\n    },\n  })\n  .use(rehypeStringify);\n\nconst markdown = '![Animation](./demo.gif)';\nconst result = await processor.process(markdown);\n```\n\nThe plugin automatically injects the client script when GIFs are found. No additional imports needed for basic usage.\n\n## Configuration Options\n\n### Complete Options Interface\n\n```typescript\ninterface RehypeGifControlsOptions {\n  // GIF player behavior configuration\n  gifPlayer?: {\n    delay?: number; // Delay before auto-play (ms) - default: 500\n    autoplay?: boolean; // Enable auto-play - default: true\n    preload?: boolean; // Preload GIF frames - default: true\n    showLoader?: boolean; // Show loading spinner - default: true\n    wrapperClasses?: string[]; // Custom CSS classes for wrapper\n    gifClasses?: string[]; // Custom CSS classes for gif-player\n  };\n\n  // File detection\n  selector?: string; // Custom selector override\n  extensions?: string[]; // File extensions to treat as GIFs - default: ['gif']\n\n  // Script injection (automatic by default)\n  injectScript?: boolean; // default: true\n\n  // Data attributes\n  dataAttributes?: Record<string, string>; // Custom data attributes\n\n  // Security\n  security?: {\n    allowedDomains?: string[]; // Allowed domains for GIF sources\n    sanitizeAttributes?: boolean; // Sanitize attributes - default: true\n  };\n}\n```\n\n## BEM CSS Classes & Styling\n\nThis package follows [BEM methodology](http://getbem.com/) for CSS class naming:\n\n### CSS Class Structure\n\n```css\n/* Block: Main wrapper */\n.gif-controls {\n}\n\n/* Element: GIF player component */\n.gif-controls__player {\n}\n\n/* Internal gif-player elements (in shadow DOM) */\n.gif-player__canvas {\n}\n.gif-player__spinner {\n}\n```\n\n### Data Attributes (BEM-style)\n\nAll data attributes follow BEM naming conventions:\n\n- `data-gif-controls=\"true\"` - Identifies processed GIFs\n- `data-gif-controls-delay=\"500\"` - Auto-play delay value\n- `data-gif-controls-autoplay=\"true\"` - Auto-play enabled state\n- `data-gif-controls-preload=\"true\"` - Preload setting\n- `data-gif-controls-show-loader=\"true\"` - Loader visibility setting\n- `data-gif-controls-width=\"640\"` - Original width (if available)\n- `data-gif-controls-height=\"480\"` - Original height (if available)\n- `data-gif-controls-aspect-ratio=\"75.00\"` - Calculated aspect ratio percentage\n- `data-gif-controls-alt=\"Alternative text\"` - Sanitized alt text\n\n### Responsive Styling (Recommended)\n\nThe gif-player web component handles responsive behavior automatically with these built-in styles:\n\n```css\n/* These styles are applied automatically by the component */\n.gif-player__canvas {\n  height: auto;\n  width: auto;\n  max-width: 100%;\n}\n```\n\n## gif-player Web Component Features\n\nThe generated `<gif-player>` elements support these attributes:\n\n### Supported Attributes\n\n- `src` - GIF source URL (required)\n- `play` - Boolean attribute to start/stop playback\n- `repeat` - Boolean attribute for infinite looping\n- `speed` - Playback speed multiplier (e.g., \"0.5\" for half speed)\n- `frame` - Jump to specific frame number\n- `size` - Sizing mode: \"auto\", \"cover\", \"contain\", \"stretch\"\n- `bounce` - Boolean for back-and-forth playback\n- `direction` - Playback direction: 1 (forward) or -1 (reverse)\n- `prerender` - Boolean to prerender frames during idle time\n\n### Interactive Features\n\n- **Mouse/Touch Controls**: Scrub through frames by dragging\n- **Click to Play/Pause**: Click anywhere to toggle playback\n- **Smooth Animation**: Canvas-based rendering for optimal performance\n- **Frame-accurate Control**: Precise frame navigation and speed control\n\n## Client-Side Integration\n\n### Automatic Initialization (Default)\n\nThe plugin automatically injects and initializes the client script when GIFs are found. No manual imports needed!\n\n### Manual Integration (Advanced)\n\nFor advanced use cases where you want to disable auto-injection:\n\n```typescript\n// 1. Disable auto-injection in plugin config\n.use(rehypeGifControls, { injectScript: false })\n\n// 2. Manually import the client\nimport '@benjc/rehype-gif-controls/client';\n```\n\n## Security Considerations\n\n**🔒 Security-First Design**: This plugin has undergone comprehensive security hardening to protect against XSS, injection attacks, and other vulnerabilities.\n\n### Secure Script Injection\n\nThe plugin uses secure script injection that:\n\n- ✅ Only injects the bundled client script (no arbitrary URLs)\n- ✅ Uses CSP-friendly `type=\"module\"` scripts\n- ✅ Prevents duplicate script injection\n- ✅ No external dependencies or CDN risks\n\n### Domain Validation\n\nRestrict GIF sources to trusted domains:\n\n```typescript\n.use(rehypeGifControls, {\n  security: {\n    allowedDomains: ['cdn.mysite.com', 'images.trusted.com'],\n  },\n});\n```\n\n- Empty array (default) allows all domains\n- Supports subdomain matching (e.g., 'example.com' matches 'cdn.example.com')\n- Data URIs are automatically validated for GIF format\n\n### Enhanced XSS Protection\n\nMulti-layered XSS protection includes:\n\n```typescript\n// Dangerous input examples that are automatically sanitized:\n'<img src=\"evil.gif\" alt=\"<script>alert(\\'xss\\')</script>\" />';\n'<img src=\"evil.gif\" alt=\"javascript:alert(1)\" />';\n'<img src=\"evil.gif\" alt=\"onclick=malicious()\" />';\n'<img src=\"evil.gif\" alt=\"data:text/html,<script>alert(1)</script>\" />';\n\n// Safe sanitized output\n'data-gif-controls-alt=\"scriptalert(xss)\"';\n'data-gif-controls-alt=\"javascriptalert(1)\"';\n'data-gif-controls-alt=\"onclickmalicious()\"';\n'data-gif-controls-alt=\"alert(1)\"';\n```\n\n**Protection Features**:\n\n- ✅ Removes HTML tags and dangerous characters\n- ✅ Blocks JavaScript protocols and event handlers\n- ✅ Prevents HTML entity bypass attacks\n- ✅ Limits attribute length to prevent DoS\n- ✅ Safe handling of data URIs\n\n### Content Security Policy\n\nIf using CSP, ensure these directives:\n\n```\nContent-Security-Policy: script-src 'self' 'unsafe-inline'; worker-src 'self' blob:; connect-src 'self' data:;\n```\n\n## Performance Optimization\n\n### Loading Performance\n\n```typescript\n// Optimize for large GIFs\n.use(rehypeGifControls, {\n  gifPlayer: {\n    preload: false,    // Don't preload all frames\n    showLoader: true,  // Show loading indicator\n    autoplay: false,   // Manual start only\n  },\n});\n```\n\n### Memory Usage\n\n- Canvas-based rendering reduces memory usage vs native GIF elements\n- Frame-by-frame decoding prevents loading entire GIF into memory\n- Automatic garbage collection of unused frames\n\n## Development\n\n### Setup\n\n```bash\n# Install dependencies\nnpm install\n\n# Run tests\nnpm test\n\n# Run tests in watch mode\nnpm run test:watch\n\n# Build the package\nnpm run build\n\n# Lint and format\nnpm run lint\nnpm run format\n\n# Type checking\nnpm run typecheck\n```\n\n### Project Structure\n\n```\nsrc/\n├── index.ts              # Main plugin entry point\n├── client.ts             # Client-side initialization\n├── utils.ts              # Plugin utilities and BEM helpers\n├── types.ts              # TypeScript definitions\n└── gif-player/           # Self-contained GIF player\n    ├── index.js          # Web component initialization\n    ├── gif-player.js     # Main web component with BEM styles\n    └── omggif.js         # GIF decoding library\n```\n\n### Testing\n\n```bash\n# Run specific test file\nnpm test -- test/index.test.ts\n\n# Run with coverage\nnpm test -- --coverage\n\n# Debug tests\nnpm test -- --inspect-brk\n```\n\n## Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Add tests for your changes\n4. Run tests (`npm test`)\n5. Run linting (`npm run lint`)\n6. Commit your changes (`git commit -m 'Add amazing feature'`)\n7. Push to the branch (`git push origin feature/amazing-feature`)\n8. Open a Pull Request\n\n## License\n\nMIT © [Benjamin Charity](https://github.com/benjamincharity)\n\n### Third-Party Licenses\n\n- **omggif**: MIT License © Dean McNamee - GIF encoding/decoding library\n- **gif-player**: MIT License © Simon Green - Original web component design\n\n## Related Projects\n\n- [rehype-semantic-images](https://github.com/benjamincharity/rehype-semantic-images) - Enhanced semantic image processing\n- [rehype-scroll-to-top](https://github.com/benjamincharity/rehype-scroll-to-top) - Auto-generated scroll-to-top links\n- [unified](https://github.com/unifiedjs/unified) - Universal syntax tree processor\n- [rehype](https://github.com/rehypejs/rehype) - HTML processor built on unified\n","readmeFilename":"README.md","_rev":"1-de44de25574089847a9cf82f01bdff77"}