PopChild.mjs 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. "use client";
  2. import { jsx } from 'react/jsx-runtime';
  3. import * as React from 'react';
  4. import { useId, useRef, useContext, useInsertionEffect } from 'react';
  5. import { MotionConfigContext } from '../../context/MotionConfigContext.mjs';
  6. /**
  7. * Measurement functionality has to be within a separate component
  8. * to leverage snapshot lifecycle.
  9. */
  10. class PopChildMeasure extends React.Component {
  11. getSnapshotBeforeUpdate(prevProps) {
  12. const element = this.props.childRef.current;
  13. if (element && prevProps.isPresent && !this.props.isPresent) {
  14. const parent = element.offsetParent;
  15. const parentWidth = parent instanceof HTMLElement ? parent.offsetWidth || 0 : 0;
  16. const size = this.props.sizeRef.current;
  17. size.height = element.offsetHeight || 0;
  18. size.width = element.offsetWidth || 0;
  19. size.top = element.offsetTop;
  20. size.left = element.offsetLeft;
  21. size.right = parentWidth - size.width - size.left;
  22. }
  23. return null;
  24. }
  25. /**
  26. * Required with getSnapshotBeforeUpdate to stop React complaining.
  27. */
  28. componentDidUpdate() { }
  29. render() {
  30. return this.props.children;
  31. }
  32. }
  33. function PopChild({ children, isPresent, anchorX }) {
  34. const id = useId();
  35. const ref = useRef(null);
  36. const size = useRef({
  37. width: 0,
  38. height: 0,
  39. top: 0,
  40. left: 0,
  41. right: 0,
  42. });
  43. const { nonce } = useContext(MotionConfigContext);
  44. /**
  45. * We create and inject a style block so we can apply this explicit
  46. * sizing in a non-destructive manner by just deleting the style block.
  47. *
  48. * We can't apply size via render as the measurement happens
  49. * in getSnapshotBeforeUpdate (post-render), likewise if we apply the
  50. * styles directly on the DOM node, we might be overwriting
  51. * styles set via the style prop.
  52. */
  53. useInsertionEffect(() => {
  54. const { width, height, top, left, right } = size.current;
  55. if (isPresent || !ref.current || !width || !height)
  56. return;
  57. const x = anchorX === "left" ? `left: ${left}` : `right: ${right}`;
  58. ref.current.dataset.motionPopId = id;
  59. const style = document.createElement("style");
  60. if (nonce)
  61. style.nonce = nonce;
  62. document.head.appendChild(style);
  63. if (style.sheet) {
  64. style.sheet.insertRule(`
  65. [data-motion-pop-id="${id}"] {
  66. position: absolute !important;
  67. width: ${width}px !important;
  68. height: ${height}px !important;
  69. ${x}px !important;
  70. top: ${top}px !important;
  71. }
  72. `);
  73. }
  74. return () => {
  75. document.head.removeChild(style);
  76. };
  77. }, [isPresent]);
  78. return (jsx(PopChildMeasure, { isPresent: isPresent, childRef: ref, sizeRef: size, children: React.cloneElement(children, { ref }) }));
  79. }
  80. export { PopChild };