MeasureLayout.mjs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. "use client";
  2. import { jsx } from 'react/jsx-runtime';
  3. import { frame, microtask } from 'motion-dom';
  4. import { useContext, Component } from 'react';
  5. import { usePresence } from '../../../components/AnimatePresence/use-presence.mjs';
  6. import { LayoutGroupContext } from '../../../context/LayoutGroupContext.mjs';
  7. import { SwitchLayoutGroupContext } from '../../../context/SwitchLayoutGroupContext.mjs';
  8. import { globalProjectionState } from '../../../projection/node/state.mjs';
  9. import { correctBorderRadius } from '../../../projection/styles/scale-border-radius.mjs';
  10. import { correctBoxShadow } from '../../../projection/styles/scale-box-shadow.mjs';
  11. import { addScaleCorrector } from '../../../projection/styles/scale-correction.mjs';
  12. class MeasureLayoutWithContext extends Component {
  13. /**
  14. * This only mounts projection nodes for components that
  15. * need measuring, we might want to do it for all components
  16. * in order to incorporate transforms
  17. */
  18. componentDidMount() {
  19. const { visualElement, layoutGroup, switchLayoutGroup, layoutId } = this.props;
  20. const { projection } = visualElement;
  21. addScaleCorrector(defaultScaleCorrectors);
  22. if (projection) {
  23. if (layoutGroup.group)
  24. layoutGroup.group.add(projection);
  25. if (switchLayoutGroup && switchLayoutGroup.register && layoutId) {
  26. switchLayoutGroup.register(projection);
  27. }
  28. projection.root.didUpdate();
  29. projection.addEventListener("animationComplete", () => {
  30. this.safeToRemove();
  31. });
  32. projection.setOptions({
  33. ...projection.options,
  34. onExitComplete: () => this.safeToRemove(),
  35. });
  36. }
  37. globalProjectionState.hasEverUpdated = true;
  38. }
  39. getSnapshotBeforeUpdate(prevProps) {
  40. const { layoutDependency, visualElement, drag, isPresent } = this.props;
  41. const projection = visualElement.projection;
  42. if (!projection)
  43. return null;
  44. /**
  45. * TODO: We use this data in relegate to determine whether to
  46. * promote a previous element. There's no guarantee its presence data
  47. * will have updated by this point - if a bug like this arises it will
  48. * have to be that we markForRelegation and then find a new lead some other way,
  49. * perhaps in didUpdate
  50. */
  51. projection.isPresent = isPresent;
  52. if (drag ||
  53. prevProps.layoutDependency !== layoutDependency ||
  54. layoutDependency === undefined ||
  55. prevProps.isPresent !== isPresent) {
  56. projection.willUpdate();
  57. }
  58. else {
  59. this.safeToRemove();
  60. }
  61. if (prevProps.isPresent !== isPresent) {
  62. if (isPresent) {
  63. projection.promote();
  64. }
  65. else if (!projection.relegate()) {
  66. /**
  67. * If there's another stack member taking over from this one,
  68. * it's in charge of the exit animation and therefore should
  69. * be in charge of the safe to remove. Otherwise we call it here.
  70. */
  71. frame.postRender(() => {
  72. const stack = projection.getStack();
  73. if (!stack || !stack.members.length) {
  74. this.safeToRemove();
  75. }
  76. });
  77. }
  78. }
  79. return null;
  80. }
  81. componentDidUpdate() {
  82. const { projection } = this.props.visualElement;
  83. if (projection) {
  84. projection.root.didUpdate();
  85. microtask.postRender(() => {
  86. if (!projection.currentAnimation && projection.isLead()) {
  87. this.safeToRemove();
  88. }
  89. });
  90. }
  91. }
  92. componentWillUnmount() {
  93. const { visualElement, layoutGroup, switchLayoutGroup: promoteContext, } = this.props;
  94. const { projection } = visualElement;
  95. if (projection) {
  96. projection.scheduleCheckAfterUnmount();
  97. if (layoutGroup && layoutGroup.group)
  98. layoutGroup.group.remove(projection);
  99. if (promoteContext && promoteContext.deregister)
  100. promoteContext.deregister(projection);
  101. }
  102. }
  103. safeToRemove() {
  104. const { safeToRemove } = this.props;
  105. safeToRemove && safeToRemove();
  106. }
  107. render() {
  108. return null;
  109. }
  110. }
  111. function MeasureLayout(props) {
  112. const [isPresent, safeToRemove] = usePresence();
  113. const layoutGroup = useContext(LayoutGroupContext);
  114. return (jsx(MeasureLayoutWithContext, { ...props, layoutGroup: layoutGroup, switchLayoutGroup: useContext(SwitchLayoutGroupContext), isPresent: isPresent, safeToRemove: safeToRemove }));
  115. }
  116. const defaultScaleCorrectors = {
  117. borderRadius: {
  118. ...correctBorderRadius,
  119. applyTo: [
  120. "borderTopLeftRadius",
  121. "borderTopRightRadius",
  122. "borderBottomLeftRadius",
  123. "borderBottomRightRadius",
  124. ],
  125. },
  126. borderTopLeftRadius: correctBorderRadius,
  127. borderTopRightRadius: correctBorderRadius,
  128. borderBottomLeftRadius: correctBorderRadius,
  129. borderBottomRightRadius: correctBorderRadius,
  130. boxShadow: correctBoxShadow,
  131. };
  132. export { MeasureLayout };