Re: [w3c/webcomponents] Method for detecting finally-distributed nodes. (#611)

@hayato, check this out. Custom Elements in my library observe `slot` elements in order to mark those elements' shadowParent (i.e. the parent of the slot they are distributed to), and adds distributed children to a `shadowChildren` list of the parent the `slot`. On `slotchange` of a slot, this code is ran:

```js
        _handleDistributedChildren(slot) {
            const diff = this._getDistributedChildDifference(slot)

            for (const addedNode of diff.added) {

                // We do this because if the given slot is assigned to another
                // slot, then this logic will run again for the next slot on
                // that next slot's slotchange, so we remove the distributed
                // node from the previous shadowParent and add it to the next
                // one. If we don't do this, then the distributed node will
                // exist in multiple shadowChildren lists when there is an
                // assignedSlot list. For more info, see
                // https://github.com/w3c/webcomponents/issues/611
                if (addedNode._shadowParent)
                    addedNode._shadowParent._shadowChildren.delete(addedNode)

                addedNode._shadowParent = this
                this._shadowChildren.add(addedNode)
            }

            for (const removedNode of diff.removed) {
                removedNode._shadowParent = null
                this._shadowChildren.delete(removedNode)
            }
        }
```

where `_getDistributedChildDifference` returns an object containing `added` and `removed` properties which are lists of nodes that were distributed and undistributed, respectively, and looks like this:

```js
        _getDistributedChildDifference(slot) {
            let previousNodes

            if (this._slotElementsAssignedNodes.has(slot))
                previousNodes = this._slotElementsAssignedNodes.get(slot)
            else
                previousNodes = []

            const newNodes = slot.assignedNodes({flatten: true})

            // save the newNodes to be used as the previousNodes for next time.
            this._slotElementsAssignedNodes.set(slot, newNodes)

            const diff = {
                removed: [],
            }

            for (let i=0, l=previousNodes.length; i<l; i+=1) {
                let oldNode = previousNodes[i]
                let newIndex = newNodes.indexOf(oldNode)

                // if it exists in the previousNodes but not the newNodes, then
                // the node was removed.
                if (!(newIndex >= 0)) {
                    diff.removed.push(oldNode)
                }

                // otherwise the node wasn't added or removed.
                else {
                    newNodes.splice(i, 1)
                }
            }

            // Remaining nodes in newNodes must have been added.
            diff.added = newNodes

            return diff
        }
```

When a chain of assigned slots exists (i.e. slot1 assigned to slot2, slot2 assigned to slot3, etc), then the that code will run multiple times, once per `slotchange` of each slot in the chain.

Because there's not a way to get finally-distributed nodes, I have to guard against the fact that a previous slot in the chain might have added the distributed node to `shadowChildren` of the previous slot's parent.

If there were a `distributedNodes()` method, then that defensive check would not be needed and the code would look like this:

```js
        _handleDistributedChildren(slot) {
            const diff = this._getDistributedChildDifference(slot)

            for (const addedNode of diff.added) {
                addedNode._shadowParent = this
                this._shadowChildren.add(addedNode)
            }

            for (const removedNode of diff.removed) {
                removedNode._shadowParent = null
                this._shadowChildren.delete(removedNode)
            }
        }
```

furthermore, this logic wouldn't have to fire once per slot if there was an event that only fired on finally-distributed children. Right now, if there is a chain of 4 slots across 4 shadow tree, then this logic will fire four times from shallowest to deepest slot.

TLDR: I've gotten it to work, but it's ugly due to the fact that there's no possible way to know if the nodes returned from from `slot.assignedNodes({flatten: true})` are finally-distributed to the current slot or not, so instead I am relying on the fact that the `slotchange` event will fire from shallowest to deepest slot.

Is that order ot `slotchange` events something I can rely on? Or is it just Chrome's behavior and could possibly be different in other browsers?

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/w3c/webcomponents/issues/611#issuecomment-263748417

Received on Wednesday, 30 November 2016 00:47:28 UTC