All files / src/projection/shared stack.ts

17.46% Statements 11/63
0% Branches 0/50
9.09% Functions 1/11
17.24% Lines 10/58

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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