stack.mjs 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import { addUniqueItem, removeItem } from 'motion-utils';
  2. class NodeStack {
  3. constructor() {
  4. this.members = [];
  5. }
  6. add(node) {
  7. addUniqueItem(this.members, node);
  8. node.scheduleRender();
  9. }
  10. remove(node) {
  11. removeItem(this.members, node);
  12. if (node === this.prevLead) {
  13. this.prevLead = undefined;
  14. }
  15. if (node === this.lead) {
  16. const prevLead = this.members[this.members.length - 1];
  17. if (prevLead) {
  18. this.promote(prevLead);
  19. }
  20. }
  21. }
  22. relegate(node) {
  23. const indexOfNode = this.members.findIndex((member) => node === member);
  24. if (indexOfNode === 0)
  25. return false;
  26. /**
  27. * Find the next projection node that is present
  28. */
  29. let prevLead;
  30. for (let i = indexOfNode; i >= 0; i--) {
  31. const member = this.members[i];
  32. if (member.isPresent !== false) {
  33. prevLead = member;
  34. break;
  35. }
  36. }
  37. if (prevLead) {
  38. this.promote(prevLead);
  39. return true;
  40. }
  41. else {
  42. return false;
  43. }
  44. }
  45. promote(node, preserveFollowOpacity) {
  46. const prevLead = this.lead;
  47. if (node === prevLead)
  48. return;
  49. this.prevLead = prevLead;
  50. this.lead = node;
  51. node.show();
  52. if (prevLead) {
  53. prevLead.instance && prevLead.scheduleRender();
  54. node.scheduleRender();
  55. node.resumeFrom = prevLead;
  56. if (preserveFollowOpacity) {
  57. node.resumeFrom.preserveOpacity = true;
  58. }
  59. if (prevLead.snapshot) {
  60. node.snapshot = prevLead.snapshot;
  61. node.snapshot.latestValues =
  62. prevLead.animationValues || prevLead.latestValues;
  63. }
  64. if (node.root && node.root.isUpdating) {
  65. node.isLayoutDirty = true;
  66. }
  67. const { crossfade } = node.options;
  68. if (crossfade === false) {
  69. prevLead.hide();
  70. }
  71. /**
  72. * TODO:
  73. * - Test border radius when previous node was deleted
  74. * - boxShadow mixing
  75. * - Shared between element A in scrolled container and element B (scroll stays the same or changes)
  76. * - Shared between element A in transformed container and element B (transform stays the same or changes)
  77. * - Shared between element A in scrolled page and element B (scroll stays the same or changes)
  78. * ---
  79. * - Crossfade opacity of root nodes
  80. * - layoutId changes after animation
  81. * - layoutId changes mid animation
  82. */
  83. }
  84. }
  85. exitAnimationComplete() {
  86. this.members.forEach((node) => {
  87. const { options, resumingFrom } = node;
  88. options.onExitComplete && options.onExitComplete();
  89. if (resumingFrom) {
  90. resumingFrom.options.onExitComplete &&
  91. resumingFrom.options.onExitComplete();
  92. }
  93. });
  94. }
  95. scheduleRender() {
  96. this.members.forEach((node) => {
  97. node.instance && node.scheduleRender(false);
  98. });
  99. }
  100. /**
  101. * Clear any leads that have been removed this render to prevent them from being
  102. * used in future animations and to prevent memory leaks
  103. */
  104. removeLeadSnapshot() {
  105. if (this.lead && this.lead.snapshot) {
  106. this.lead.snapshot = undefined;
  107. }
  108. }
  109. }
  110. export { NodeStack };