{"_id":"@rcaferati/wac","_rev":"2-5eb323c50fa394f505a7ffe6f84c8a43","name":"@rcaferati/wac","dist-tags":{"latest":"1.2.0"},"versions":{"1.0.0":{"name":"@rcaferati/wac","version":"1.0.0","keywords":["web animation","animation callback","transition end","ontransitionend","onanimationend"],"license":"MIT","_id":"@rcaferati/wac@1.0.0","maintainers":[{"name":"rcaferati","email":"rafael@caferati.me"}],"homepage":"https://web-animation.caferati.me","bugs":{"url":"https://github.com/rcaferati/wac/issues"},"dist":{"shasum":"7505671559d576dd8c1f7c9a3b0cfea1fa6cd554","tarball":"https://registry.npmjs.org/@rcaferati/wac/-/wac-1.0.0.tgz","fileCount":13,"integrity":"sha512-7dVbzvcfL9o4UnrCaNeoIs7Ri9Ol+OMfpYYCJJV1dfm+5+pVM6DrvFjPzqifd2ACVABpXmwTQKy9KFxNm/7IdA==","signatures":[{"sig":"MEUCIEctsG1mRJSi7ZsFnhh6JD7wS00caqOFz4kpy1MHKgvdAiEAlcms4+DCNgd+yiNexVR1cQx4VdEug8scopxW44qjO4M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":386646,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjU8s7ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpXsg/9HaUqI8Zo1hOKYHqr3FOxsXHeZBXK97oufpm3rgeUqF8CRn+V\r\ndkP+U5CgOmsUFYzpsL3SXu/DDmGLXIjW/7yrb2kPPeeclVEEOBjAixGC52t6\r\ni4qIkzeC5+u24qj1d/ZA2q2C10giF8s0Mk6oKSUgG39a1uAxPPkH+qw8xnWg\r\n++cALmGJUzMku1JjHquMvZYW0JSJr2B8O3RUI2waOd2UR6U0wCLEojeBwKXe\r\nhqT8uaNp81RBQdIHITHfrz5NIMXs/y3gT5KsXFVgfIoXqOVjs9hmd2jjbeMi\r\nPudo8sU5kSVupIeRCBO5Om46cd7H3/fyCkS68P2mrqVo2IHcTBJ2301zDdDG\r\nHQB8KWKHV1x8IdGYloawBcOsuT8wZHFh7eJ62NwzZvfO5DgI/qVXyezq4W7B\r\nq6fwB6NOSrR/tYunjkwaRSVsh9zYxlT/TyqkgdPJuBRbnhKIp3m31uAYMKEU\r\nE8Dc/Vri4i0Q4APaRIuPYMqMPhxZ9UEJ7c3pxXu3QsrodtdTQFfIWDGGEjKS\r\nnQWwYf4p3xYoBx/tIteiUMk1p9bVvUw2L7QxfOJ8gthSfJ9bJoKl4OTvq3Gl\r\nPsS1Vskm59FxhzHN9y4ssXokJ0oezZ1glMPIEakX9QHeipea1WX87YPTYjd9\r\nf59uvisxWJW0qoc9SlZqHr98A6Mg3ImBxmw=\r\n=rJ2I\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","types":"./dist/index.d.ts","module":"dist/index.js","gitHead":"eb23940f52e51dc84e1adb878a37c4a88027a5d6","scripts":{"test":"echo true","build":"webpack --mode development","build-min":"webpack --mode production --config webpack.prod.js","prepublish":"npm run build && npm run build-min","build-watch":"webpack --mode development --watch"},"_npmUser":{"name":"rcaferati","email":"rafael@caferati.me"},"repository":{"url":"git+https://github.com/rcaferati/wac.git","type":"git"},"_npmVersion":"8.19.2","description":"CSS controlled animations with transitionEnd, onTransitionEnd, animationend, onAnimationEnd events and frame throwing. Tiny javascript library with cross-browser methods to handle css animation/transition callbacks and event loop frame throwing.","directories":{},"_nodeVersion":"18.11.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^8.25.0","webpack":"^5.74.0","ts-loader":"^9.4.1","@babel/cli":"^7.19.3","typescript":"^4.8.4","@babel/core":"^7.19.3","webpack-cli":"^4.10.0","babel-loader":"^8.2.5","@babel/preset-env":"^7.19.4","webpack-dev-server":"^4.11.1","eslint-config-airbnb":"^19.0.4","eslint-plugin-import":"^2.26.0","terser-webpack-plugin":"^5.3.6","eslint-plugin-jsx-a11y":"^6.6.1","@babel/preset-typescript":"^7.18.6"},"_npmOperationalInternal":{"tmp":"tmp/wac_1.0.0_1666435899369_0.01733253722193595","host":"s3://npm-registry-packages"}},"1.2.0":{"name":"@rcaferati/wac","version":"1.2.0","description":"Tiny JavaScript utility for CSS transitionend/animationend callbacks and frame/layout timing helpers.","repository":{"type":"git","url":"git+https://github.com/rcaferati/wac.git"},"homepage":"https://github.com/rcaferati/wac#readme","bugs":{"url":"https://github.com/rcaferati/wac/issues"},"license":"MIT","publishConfig":{"access":"public"},"main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs","default":"./dist/index.mjs"},"./package.json":"./package.json"},"sideEffects":false,"engines":{"node":">=18.18"},"keywords":["css","animation","transition","transitionend","animationend","requestanimationframe","raf","timing","layout"],"scripts":{"clean":"node -e \"try{require('fs').rmSync('dist',{recursive:true,force:true})}catch(e){}\"","build":"npm run clean && tsup","build-watch":"tsup --watch","lint":"eslint .","lint:fix":"eslint . --fix","typecheck":"tsc --noEmit","check":"npm run lint && npm run typecheck && npm run build","test":"echo \"No tests yet\"","prepack":"npm run build"},"devDependencies":{"@eslint/js":"^9.0.0","eslint":"^9.0.0","globals":"^17.3.0","terser":"^5.46.0","tsup":"^8.5.1","typescript":"^4.8.4","typescript-eslint":"^8.56.1"},"_id":"@rcaferati/wac@1.2.0","gitHead":"f55ba344ef51defa4fa9fb77ba54cc0dd98cb891","_nodeVersion":"20.20.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-JHhiFVmWQQl9/x+kPGbIFuLM8KzERlWTSF9pDRwlodabTGlGmvb1dJnHpj/EczeQmK04KWBot0LdMG1fc+lAGg==","shasum":"737d85946a75b78893730c2f98bc84b351bd83e8","tarball":"https://registry.npmjs.org/@rcaferati/wac/-/wac-1.2.0.tgz","fileCount":8,"unpackedSize":53016,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFPA+Yqhc5fN1V8EPtDh4/0fzB/rtP/t9uWIUL9mGQq5AiEAlLxLqqXc8SdRTCYnyDb4UDmMvpn/GTJzObgf1NiKa0o="}]},"_npmUser":{"name":"rcaferati","email":"rafael@genodex.org"},"directories":{},"maintainers":[{"name":"rcaferati","email":"rafael@genodex.org"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/wac_1.2.0_1772505573224_0.9627734547046569"},"_hasShrinkwrap":false}},"time":{"created":"2022-10-22T10:51:39.304Z","modified":"2026-03-03T02:39:33.527Z","1.0.0":"2022-10-22T10:51:39.541Z","1.2.0":"2026-03-03T02:39:33.392Z"},"bugs":{"url":"https://github.com/rcaferati/wac/issues"},"license":"MIT","homepage":"https://github.com/rcaferati/wac#readme","keywords":["css","animation","transition","transitionend","animationend","requestanimationframe","raf","timing","layout"],"repository":{"type":"git","url":"git+https://github.com/rcaferati/wac.git"},"description":"Tiny JavaScript utility for CSS transitionend/animationend callbacks and frame/layout timing helpers.","maintainers":[{"name":"rcaferati","email":"rafael@genodex.org"}],"readme":"# WAC — CSS transition and animation controls\n\n![NPM](https://img.shields.io/npm/v/@rcaferati/wac.svg)\n\nTiny JavaScript utility for CSS `transitionend` / `animationend` callbacks and frame/layout timing helpers.\n\nWAC provides small Promise-based helpers to:\n\n- wait for a transition or animation to finish\n- ignore bubbled events from child elements\n- schedule code on the next layout / future frames\n- optionally use refresh-rate independent timing (`fixedFrameThrower`)\n\n---\n\n## Installation\n\n### npm\n\nnpm install @rcaferati/wac\n\n### yarn\n\nyarn add @rcaferati/wac\n\n---\n\n## Quick usage\n\n### Wait for a transition to finish\n\n```js\nimport { onceTransitionEnd } from \"@rcaferati/wac\";\n\nconst box = document.querySelector(\".box\");\n\nbox.classList.add(\"animated\");\nbox.classList.add(\"move\");\n\nonceTransitionEnd(box).then((event) => {\n  // transition finished on `box`\n});\n```\n\n### Wait for a specific transitioned property\n\n```js\nimport { onceTransitionEnd } from \"@rcaferati/wac\";\n\nonceTransitionEnd(box, { propertyName: \"transform\" }).then(() => {\n  // resolves only when the transform transition ends\n});\n```\n\n### Wait for an animation to finish\n\n```js\nimport { onceAnimationEnd } from \"@rcaferati/wac\";\n\nonceAnimationEnd(box).then((event) => {\n  // animation finished on `box`\n});\n```\n\n### Run code after the next CSS layout\n\n```js\nimport { beforeCssLayout } from \"@rcaferati/wac\";\n\nbeforeCssLayout(() => {\n  // runs on next animation frame (or immediately if rAF is unavailable)\n});\n```\n\n### Wait N real display frames (rAF hops)\n\n```js\nimport { frameThrower } from \"@rcaferati/wac\";\n\nawait frameThrower(2); // waits using real display frames\n```\n\n### Wait N virtual frames (refresh-rate independent)\n\n```js\nimport { fixedFrameThrower } from \"@rcaferati/wac\";\n\n// Interprets \"10 frames\" at 60fps (~166.7ms),\n// even on 120Hz / 144Hz displays.\nawait fixedFrameThrower(10);\n```\n\n---\n\n## Basic HTML/CSS example\n\n```html\n<style>\n  .box {\n    width: 40px;\n    height: 40px;\n    background: #222;\n  }\n\n  .animated {\n    transition: transform 0.4s linear;\n  }\n\n  .move {\n    transform: translateX(100px);\n  }\n</style>\n\n<div class=\"box\"></div>\n\n<script type=\"module\">\n  import { onceTransitionEnd } from \"@rcaferati/wac\";\n\n  const box = document.querySelector(\".box\");\n\n  box.classList.add(\"animated\");\n  box.classList.add(\"move\");\n\n  onceTransitionEnd(box, { propertyName: \"transform\" }).then(() => {\n    console.log(\"Done\");\n  });\n</script>\n```\n\n---\n\n## API\n\n### `onceTransitionEnd(element, options?)`\n\nWaits for the next matching `transitionend` event on `element`.\n\nReturns:\n\n- `Promise<Event | false>`\n\n#### Options\n\n- `tolerance?: number`\n  - Number of matching events to skip before resolving.\n  - `0` resolves on the first matching event.\n- `propertyName?: string`\n  - Preferred transition property filter (example: `\"transform\"`).\n- `property?: string`\n  - Backward-compatible alias for `propertyName`.\n\nExample:\n\n```js\nawait onceTransitionEnd(el, { propertyName: \"opacity\" });\n```\n\n---\n\n### `onceAnimationEnd(element, options?)`\n\nWaits for the next matching `animationend` event on `element`.\n\nReturns:\n\n- `Promise<Event | false>`\n\n#### Options\n\n- `tolerance?: number`\n  - Number of matching events to skip before resolving.\n- `animationName?: string`\n  - Optional animation name filter.\n\nExample:\n\n```js\nawait onceAnimationEnd(el, { animationName: \"fade-in\" });\n```\n\n---\n\n### `setCssEndEvent(element, type, options?)`\n\nLow-level helper used internally by `onceTransitionEnd` / `onceAnimationEnd`.\n\n- `type`: `\"transition\"` or `\"animation\"`\n- Returns `Promise<Event | false>`\n\nNotes:\n\n- Ignores bubbled child events (only resolves for the exact target element).\n- Preserves historical \"latest call wins\" behavior per element (a new pending listener replaces a previous one created by WAC on the same element).\n\n---\n\n### `beforeCssLayout(callback)`\n\nRuns `callback` on the next animation frame.\n\nFallback behavior:\n\n- If `requestAnimationFrame` is unavailable, runs immediately.\n\n---\n\n### `beforeFutureCssLayout(frames, callback)`\n\nRuns `callback` a number of frames into the future, plus one extra frame.\n\nSemantics:\n\n- `frames = 0` -> next animation frame\n- `frames = 1` -> roughly two frame hops from now\n\nFallback behavior:\n\n- If `requestAnimationFrame` is unavailable, runs immediately.\n\n---\n\n### `recursiveAnimationFrame(frames, callback)`\n\nLow-level helper that recursively waits for `frames` animation frames and then runs `callback`.\n\nThis powers the layout/frame helpers and can be used directly if needed.\n\n---\n\n### `frameThrower(frames)`\n\nPromise wrapper around `beforeFutureCssLayout`.\n\nUses real display frames (`requestAnimationFrame` hops), so elapsed time depends on monitor refresh rate (60Hz / 120Hz / etc).\n\nReturns:\n\n- `Promise<void>`\n\n---\n\n### `fixedFrameThrower(frames, fps = 60)`\n\nRefresh-rate independent timing helper.\n\nSame intent as `frameThrower()`, but `frames` are interpreted as \"virtual frames\" at `fps` (60 by default), so timing is stable across 60Hz/120Hz/etc displays.\n\nIt still waits at least 1 `requestAnimationFrame` first, preserving CSS/layout timing semantics similar to `beforeFutureCssLayout`.\n\nExamples (`fps = 60`):\n\n- `fixedFrameThrower(0)` -> ~1 rAF (next layout)\n- `fixedFrameThrower(10)` -> ~1 rAF + ~166.7ms\n\nReturns:\n\n- `Promise<void>`\n\n---\n\n### `onceNextCssLayout()`\n\nResolves after two animation frames (double-rAF).\n\nThis is useful when you want DOM updates and style calculations to progress enough for reliable transition triggering or measurement.\n\nReturns:\n\n- `Promise<void>`\n\n---\n\n### `framesToMs(frames, fps = 60)`\n\nConverts \"virtual frames\" to milliseconds.\n\nExamples:\n\n- `framesToMs(1)` -> ~16.67\n- `framesToMs(10)` -> ~166.67\n- `framesToMs(10, 120)` -> ~83.33\n\nReturns:\n\n- `number`\n\n---\n\n## Notes and caveats\n\n- CSS end events may not fire if:\n  - duration/delay is `0`\n  - the property/animation does not actually run\n  - the animation/transition is canceled or interrupted\n  - the element is removed before completion\n- If no supported CSS end-event name can be resolved for the element, WAC resolves with `false`.\n- In non-browser environments (or when `requestAnimationFrame` is unavailable), frame/layout helpers fall back to immediate execution.\n\n---\n\n## Browser support\n\nWAC targets modern browsers and includes small legacy event-name fallbacks (WebKit / old Opera) for compatibility.\n\n---\n\n## Author\n\n**Rafael Caferati**  \nWebsite: [caferati.dev](https://caferati.dev)  \nLinkedIn: [linkedin.com/rcaferati](https://linkedin.com/in/rcaferati)  \nInstagram: [instagram.com/rcaferati](https://instagram.com/rcaferati)\n\n---\n\n## License\n\nMIT License\n\nCopyright (c) 2018 @rcaferati","readmeFilename":"README.md"}