UNPKG

five-bells-visualization

Version:
377 lines (341 loc) 13.5 kB
<!-- @license Copyright (c) 2014 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt --> <link rel="import" href="../lib/array-splice.html"> <link rel="import" href="../lib/dom-api.html"> <script> (function() { /** Implements a pared down version of ShadowDOM's scoping, which is easy to polyfill across browsers. */ Polymer.Base._addFeature({ _prepShady: function() { // Use this system iff localDom is needed. this._useContent = this._useContent || Boolean(this._template); if (this._useContent) { this._template._hasInsertionPoint = this._template.content.querySelector('content'); } }, // called as part of content initialization, prior to template stamping _poolContent: function() { if (this._useContent) { // capture lightChildren to help reify dom scoping saveLightChildrenIfNeeded(this); } }, // called as part of content initialization, after template stamping _setupRoot: function() { if (this._useContent) { this._createLocalRoot(); } }, _createLocalRoot: function() { this.shadyRoot = this.root; this.shadyRoot._distributionClean = false; this.shadyRoot._isShadyRoot = true; this.shadyRoot._dirtyRoots = []; // capture insertion point list // TODO(sorvell): it's faster to do this via native qSA than annotator. this.shadyRoot._insertionPoints = this._template._hasInsertionPoint ? this.shadyRoot.querySelectorAll('content') : []; // save logical tree info for shadyRoot. saveLightChildrenIfNeeded(this.shadyRoot); this.shadyRoot.host = this; }, /** * Return the element whose local dom within which this element * is contained. This is a shorthand for * `Polymer.dom(this).getOwnerRoot().host`. */ get domHost() { var root = Polymer.dom(this).getOwnerRoot(); return root && root.host; }, /** * Force this element to distribute its children to its local dom. * A user should call `distributeContent` if distribution has been * invalidated due to changes to selectors on child elements that * effect distribution. For example, if an element contains an * insertion point with <content select=".foo"> and a `foo` class is * added to a child, then `distributeContent` must be called to update * local dom distribution. */ distributeContent: function() { if (this._useContent) { this.shadyRoot._distributionClean = false; this._distributeContent(); } }, _distributeContent: function() { if (this._useContent && !this.shadyRoot._distributionClean) { // logically distribute self this._beginDistribute(); this._distributeDirtyRoots(); this._finishDistribute(); } }, _beginDistribute: function() { if (this._useContent && hasInsertionPoint(this.shadyRoot)) { // reset distributions this._resetDistribution(); // compute which nodes should be distributed where // TODO(jmesserly): this is simplified because we assume a single // ShadowRoot per host and no `<shadow>`. this._distributePool(this.shadyRoot, this._collectPool()); } }, _distributeDirtyRoots: function() { var c$ = this.shadyRoot._dirtyRoots; for (var i=0, l= c$.length, c; (i<l) && (c=c$[i]); i++) { c._distributeContent(); } this.shadyRoot._dirtyRoots = []; }, _finishDistribute: function() { // compose self if (this._useContent) { if (hasInsertionPoint(this.shadyRoot)) { this._composeTree(); } else { if (!this.shadyRoot._hasDistributed) { this.textContent = ''; this.appendChild(this.shadyRoot); } else { // simplified non-tree walk composition var children = this._composeNode(this); this._updateChildNodes(this, children); } } this.shadyRoot._hasDistributed = true; this.shadyRoot._distributionClean = true; } }, // This is a polyfill for Element.prototype.matches, which is sometimes // still prefixed. Alternatively we could just polyfill it somewhere. // Note that the arguments are reversed from what you might expect. elementMatches: function(selector, node) { if (node === undefined) { node = this; } return matchesSelector.call(node, selector); }, // Many of the following methods are all conceptually static, but they are // included here as "protected" methods to allow overriding. _resetDistribution: function() { // light children var children = getLightChildren(this); for (var i = 0; i < children.length; i++) { var child = children[i]; if (child._destinationInsertionPoints) { child._destinationInsertionPoints = undefined; } } // insertion points var root = this.shadyRoot; var p$ = root._insertionPoints; for (var j = 0; j < p$.length; j++) { p$[j]._distributedNodes = []; } }, // Gather the pool of nodes that should be distributed. We will combine // these with the "content root" to arrive at the composed tree. _collectPool: function() { var pool = []; var children = getLightChildren(this); for (var i = 0; i < children.length; i++) { var child = children[i]; if (isInsertionPoint(child)) { pool.push.apply(pool, child._distributedNodes); } else { pool.push(child); } } return pool; }, // perform "logical" distribution; note, no actual dom is moved here, // instead elements are distributed into a `content._distributedNodes` // array where applicable. _distributePool: function(node, pool) { var p$ = node._insertionPoints; for (var i=0, l=p$.length, p; (i<l) && (p=p$[i]); i++) { this._distributeInsertionPoint(p, pool); } }, _distributeInsertionPoint: function(content, pool) { // distribute nodes from the pool that this selector matches var anyDistributed = false; for (var i=0, l=pool.length, node; i < l; i++) { node=pool[i]; // skip nodes that were already used if (!node) { continue; } // distribute this node if it matches if (this._matchesContentSelect(node, content)) { distributeNodeInto(node, content); // remove this node from the pool pool[i] = undefined; // since at least one node matched, we won't need fallback content anyDistributed = true; var parent = content.lightParent; // dirty a shadyRoot if a change may trigger reprojection! if (parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot)) { parent.shadyRoot._distributionClean = false; this.shadyRoot._dirtyRoots.push(parent); } } } // Fallback content if nothing was distributed here if (!anyDistributed) { var children = getLightChildren(content); for (var j = 0; j < children.length; j++) { distributeNodeInto(children[j], content); } } }, // Reify dom such that it is at its correct rendering position // based on logical distribution. _composeTree: function() { this._updateChildNodes(this, this._composeNode(this)); var p$ = this.shadyRoot._insertionPoints; for (var i=0, l=p$.length, p, parent; (i<l) && (p=p$[i]); i++) { parent = p.lightParent || p.parentNode; if (!parent._useContent && (parent !== this) && (parent !== this.shadyRoot)) { this._updateChildNodes(parent, this._composeNode(parent)); } } }, // Returns the list of nodes which should be rendered inside `node`. _composeNode: function(node) { var children = []; var c$ = getLightChildren(node.shadyRoot || node); for (var i = 0; i < c$.length; i++) { var child = c$[i]; if (isInsertionPoint(child)) { var distributedNodes = child._distributedNodes; for (var j = 0; j < distributedNodes.length; j++) { var distributedNode = distributedNodes[j]; if (isFinalDestination(child, distributedNode)) { children.push(distributedNode); } } } else { children.push(child); } } return children; }, // Ensures that the rendered node list inside `node` is `children`. _updateChildNodes: function(node, children) { var splices = Polymer.ArraySplice.calculateSplices(children, node.childNodes); for (var i=0; i<splices.length; i++) { var s = splices[i]; // remove for (var j=0, c; j < s.removed.length; j++) { c = s.removed[j]; if (c.previousSibling == children[s.index-1]) { remove(c); } } // insert for (var idx=s.index, ch, o; idx < s.index + s.addedCount; idx++) { ch = children[idx]; o = node.childNodes[idx]; while (o && o === ch) { o = o.nextSibling; } insertBefore(node, ch, o); } } }, _matchesContentSelect: function(node, contentElement) { var select = contentElement.getAttribute('select'); // no selector matches all nodes (including text) if (!select) { return true; } select = select.trim(); // same thing if it had only whitespace if (!select) { return true; } // selectors can only match Elements if (!(node instanceof Element)) { return false; } // only valid selectors can match: // TypeSelector // * // ClassSelector // IDSelector // AttributeSelector // negation var validSelectors = /^(:not\()?[*.#[a-zA-Z_|]/; if (!validSelectors.test(select)) { return false; } return this.elementMatches(select, node); }, // system override point _elementAdd: function() {}, // system override point _elementRemove: function() {} }); var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded; var getLightChildren = Polymer.DomApi.getLightChildren; var matchesSelector = Polymer.DomApi.matchesSelector; var hasInsertionPoint = Polymer.DomApi.hasInsertionPoint; function distributeNodeInto(child, insertionPoint) { insertionPoint._distributedNodes.push(child); var points = child._destinationInsertionPoints; if (!points) { child._destinationInsertionPoints = [insertionPoint]; // TODO(sorvell): _destinationInsertionPoints may not be cleared when // nodes are dynamically added/removed, therefore test before adding // insertion points. } else if (points.indexOf(insertionPoint) < 0) { points.push(insertionPoint); } } function isFinalDestination(insertionPoint, node) { var points = node._destinationInsertionPoints; return points && points[points.length - 1] === insertionPoint; } function isInsertionPoint(node) { // TODO(jmesserly): we could add back 'shadow' support here. return node.localName == 'content'; } var nativeInsertBefore = Element.prototype.insertBefore; var nativeRemoveChild = Element.prototype.removeChild; function insertBefore(parentNode, newChild, refChild) { // remove child from its old parent first remove(newChild); // make sure we never lose logical DOM information: // if the parentNode doesn't have lightChildren, save that information now. saveLightChildrenIfNeeded(parentNode); // insert it into the real DOM nativeInsertBefore.call(parentNode, newChild, refChild || null); } function remove(node) { var parentNode = node.parentNode; if (parentNode) { // make sure we never lose logical DOM information: // if the parentNode doesn't have lightChildren, save that information now. saveLightChildrenIfNeeded(parentNode); // remove it from the real DOM nativeRemoveChild.call(parentNode, node); } } })(); </script>