{"mappings":";AAiCA;IACE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;IACE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,eAAe,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,gBAAgB,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IACpD,KAAK,CAAC,EAAE,CAAC,gBAAgB,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;CACnD;AC4CD,OAAO,MAAM,sBACA,WAAW,mDAOnB,iBAAiB;;;CA+OrB,CAAC","sources":["src/src/types.ts","src/src/index.ts","src/index.ts"],"sourcesContent":[null,null,"/* eslint-disable @typescript-eslint/no-empty-function */\nimport {\n  anticipate,\n  backIn,\n  backInOut,\n  backOut,\n  circIn,\n  circInOut,\n  circOut,\n  easeIn,\n  easeInOut,\n  easeOut,\n  linear,\n} from '@popmotion/easing';\nimport sync from 'framesync';\nimport throttle from 'lodash.throttle';\nimport { tween } from 'popmotion';\nimport {\n  BoundingClientRect,\n  CachedPositionData,\n  ChildBoundingClientRect,\n  Coords,\n  ItemPosition,\n  PopmotionEasing,\n  WrapGridArguments,\n} from './types';\n\nconst popmotionEasing: PopmotionEasing = {\n  anticipate,\n  backIn,\n  backInOut,\n  backOut,\n  circIn,\n  circInOut,\n  circOut,\n  easeIn,\n  easeInOut,\n  easeOut,\n  linear,\n};\n\nconst DATASET_KEY = 'animateGridId';\n\n// in order to account for scroll, (which we're not listening for)\n// always cache the item's position relative\n// to the top and left of the grid container\nconst getGridAwareBoundingClientRect = (\n  gridBoundingClientRect: BoundingClientRect,\n  el: HTMLElement\n): BoundingClientRect => {\n  const { top, left, width, height } = el.getBoundingClientRect();\n  const rect = { top, left, width, height };\n  rect.top -= gridBoundingClientRect.top;\n  rect.left -= gridBoundingClientRect.left;\n  // if an element is display:none it will return top: 0 and left:0\n  // rather than saying it's still in the containing element\n  // so we need to use Math.max to make sure the coordinates stay\n  // within the container\n  rect.top = Math.max(rect.top, 0);\n  rect.left = Math.max(rect.left, 0);\n  return rect;\n};\n\n// the function used during the tweening\nconst applyCoordTransform = (\n  el: HTMLElement,\n  { translateX, translateY, scaleX, scaleY }: Coords,\n  { immediate }: { immediate?: boolean } = {}\n): void => {\n  const isFinished =\n    translateX === 0 && translateY === 0 && scaleX === 1 && scaleY === 1;\n  const styleEl = () => {\n    el.style.transform = isFinished\n      ? ''\n      : `translateX(${translateX}px) translateY(${translateY}px) scaleX(${scaleX}) scaleY(${scaleY})`;\n  };\n  if (immediate) {\n    styleEl();\n  } else {\n    sync.render(styleEl);\n  }\n  const firstChild = el.children[0] as HTMLElement;\n  if (firstChild) {\n    const styleChild = () => {\n      firstChild.style.transform = isFinished\n        ? ''\n        : `scaleX(${1 / scaleX}) scaleY(${1 / scaleY})`;\n    };\n    if (immediate) {\n      styleChild();\n    } else {\n      sync.render(styleChild);\n    }\n  }\n};\n\n// return a function that take a reference to a grid dom node and optional config\nexport const wrapGrid = (\n  container: HTMLElement,\n  {\n    duration = 250,\n    stagger = 0,\n    easing = 'easeInOut',\n    onStart = () => { },\n    onEnd = () => { },\n  }: WrapGridArguments = {}\n) => {\n  if (!popmotionEasing[easing]) {\n    throw new Error(`${easing} is not a valid easing name`);\n  }\n\n  let mutationsDisabled = false;\n\n  const disableMutationsWhileFunctionRuns = (func: () => void) => {\n    mutationsDisabled = true;\n    func();\n    setTimeout(() => {\n      mutationsDisabled = false;\n    }, 0);\n  };\n\n  // all cached position data, and in-progress tween data, is stored here\n  const cachedPositionData: CachedPositionData = {};\n  // initially and after every transition, record element positions\n  const recordPositions = (\n    elements: HTMLCollectionOf<HTMLElement> | HTMLElement[]\n  ) => {\n    const gridBoundingClientRect = container.getBoundingClientRect();\n    Array.from(elements).forEach(el => {\n      if (typeof el.getBoundingClientRect !== 'function') {\n        return;\n      }\n      if (!el.dataset[DATASET_KEY]) {\n        const newId = `${Math.random()}`;\n        el.dataset[DATASET_KEY] = newId;\n      }\n      const animateGridId = el.dataset[DATASET_KEY] as string;\n\n      if (!cachedPositionData[animateGridId]) {\n        cachedPositionData[animateGridId] = {} as ItemPosition;\n      }\n\n      const rect = getGridAwareBoundingClientRect(gridBoundingClientRect, el);\n      cachedPositionData[animateGridId].rect = rect;\n      cachedPositionData[\n        animateGridId\n      ].gridBoundingClientRect = gridBoundingClientRect;\n    });\n  };\n  recordPositions(container.children as HTMLCollectionOf<HTMLElement>);\n\n  const throttledResizeListener = throttle(() => {\n    const bodyElement = document.querySelector('body');\n    const containerIsNoLongerInPage =\n      bodyElement && !bodyElement.contains(container);\n    if (!container || containerIsNoLongerInPage) {\n      window.removeEventListener('resize', throttledResizeListener);\n    }\n    recordPositions(container.children as HTMLCollectionOf<HTMLElement>);\n  }, 250);\n  window.addEventListener('resize', throttledResizeListener);\n\n  const throttledScrollListener = throttle(() => {\n    recordPositions(container.children as HTMLCollectionOf<HTMLElement>);\n  }, 20);\n  container.addEventListener('scroll', throttledScrollListener);\n\n  const mutationCallback = (\n    mutationsList: MutationRecord[] | 'forceGridAnimation'\n  ) => {\n    if (mutationsList !== 'forceGridAnimation') {\n      // check if we care about the mutation\n      const relevantMutationHappened = mutationsList.filter(\n        (m: MutationRecord) =>\n          m.attributeName === 'class' ||\n          m.addedNodes.length ||\n          m.removedNodes.length\n      ).length;\n      if (!relevantMutationHappened) {\n        return;\n      }\n      if (mutationsDisabled) return;\n    }\n    const gridBoundingClientRect = container.getBoundingClientRect();\n    const childrenElements = Array.from(container.children) as HTMLElement[];\n    // stop current transitions and remove transforms on transitioning elements\n    childrenElements\n      .filter(el => {\n        const itemPosition =\n          cachedPositionData[el.dataset[DATASET_KEY] as string];\n        if (itemPosition && itemPosition.stopTween) {\n          itemPosition.stopTween();\n          delete itemPosition.stopTween;\n          return true;\n        }\n      })\n      .forEach(el => {\n        el.style.transform = '';\n        const firstChild = el.children[0] as HTMLElement;\n        if (firstChild) {\n          firstChild.style.transform = '';\n        }\n      });\n    const animatedGridChildren = childrenElements\n      .map(el => ({\n        childCoords: {} as ChildBoundingClientRect,\n        el,\n        boundingClientRect: getGridAwareBoundingClientRect(\n          gridBoundingClientRect,\n          el\n        ),\n      }))\n      .filter(({ el, boundingClientRect }) => {\n        const itemPosition =\n          cachedPositionData[el.dataset[DATASET_KEY] as string];\n        // don't animate the initial appearance of elements,\n        // just cache their position so they can be animated later\n        if (!itemPosition) {\n          recordPositions([el]);\n          return false;\n        } else if (\n          boundingClientRect.top === itemPosition.rect.top &&\n          boundingClientRect.left === itemPosition.rect.left &&\n          boundingClientRect.width === itemPosition.rect.width &&\n          boundingClientRect.height === itemPosition.rect.height\n        ) {\n          // if it hasn't moved, dont animate it\n          return false;\n        }\n        return true;\n      });\n\n    // having more than one child in the animated item is not supported\n    animatedGridChildren.forEach(({ el }) => {\n      if (Array.from(el.children).length > 1) {\n        throw new Error(\n          'Make sure every grid item has a single container element surrounding its children'\n        );\n      }\n    });\n\n    if (!animatedGridChildren.length) {\n      return;\n    }\n\n    const animatedElements = animatedGridChildren.map(({ el }) => el);\n    disableMutationsWhileFunctionRuns(() => onStart(animatedElements));\n\n    const completionPromises: Array<Promise<unknown>> = [];\n\n    animatedGridChildren\n      // do this measurement first so as not to cause layout thrashing\n      .map(data => {\n        const firstChild = data.el.children[0] as HTMLElement;\n        // different transform origins give different effects. \"50% 50%\" is default\n        if (firstChild) {\n          data.childCoords = getGridAwareBoundingClientRect(\n            gridBoundingClientRect,\n            firstChild\n          );\n        }\n        return data;\n      })\n      .forEach(\n        (\n          {\n            el,\n            boundingClientRect: { top, left, width, height },\n            childCoords: { top: childTop, left: childLeft },\n          },\n          i\n        ) => {\n          const firstChild = el.children[0] as HTMLElement;\n          const itemPosition =\n            cachedPositionData[el.dataset[DATASET_KEY] as string];\n          const coords: Coords = {\n            scaleX: itemPosition.rect.width / width,\n            scaleY: itemPosition.rect.height / height,\n            translateX: itemPosition.rect.left - left,\n            translateY: itemPosition.rect.top - top,\n          };\n\n          el.style.transformOrigin = '0 0';\n          if (firstChild && childLeft === left && childTop === top) {\n            firstChild.style.transformOrigin = '0 0';\n          }\n\n          let cachedResolve: () => void;\n\n          const completionPromise = new Promise(resolve => {\n            cachedResolve = resolve as () => void;\n          });\n\n          completionPromises.push(completionPromise);\n\n          applyCoordTransform(el, coords, { immediate: true });\n          // now start the animation\n          const startAnimation = () => {\n            const { stop } = tween({\n              from: coords,\n              to: { translateX: 0, translateY: 0, scaleX: 1, scaleY: 1 },\n              duration,\n              ease: popmotionEasing[easing],\n            }).start({\n              update: (transforms: Coords) => {\n                applyCoordTransform(el, transforms);\n                // this helps prevent layout thrashing\n                sync.postRender(() => recordPositions([el]));\n              },\n              complete: cachedResolve,\n            });\n            itemPosition.stopTween = stop;\n          };\n\n          if (typeof stagger !== 'number') {\n            startAnimation();\n          } else {\n            const timeoutId = setTimeout(() => {\n              sync.update(startAnimation);\n            }, stagger * i);\n            itemPosition.stopTween = () => clearTimeout(timeoutId);\n          }\n        }\n      );\n\n    Promise.all(completionPromises).then(() => {\n      onEnd(animatedElements);\n    });\n  };\n\n  const observer = new MutationObserver(mutationCallback);\n  observer.observe(container, {\n    childList: true,\n    attributes: true,\n    subtree: true,\n    attributeFilter: ['class'],\n  });\n  const unwrapGrid = () => {\n    window.removeEventListener('resize', throttledResizeListener);\n    container.removeEventListener('scroll', throttledScrollListener);\n    observer.disconnect();\n  };\n  const forceGridAnimation = () => mutationCallback('forceGridAnimation');\n  return { unwrapGrid, forceGridAnimation };\n};\n"],"names":[],"version":3,"file":"index.d.ts.map"}