UNPKG

55.7 kBJavaScriptView Raw
1
2/**
3 * @preserve
4 * Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
5 * Licensed under the Apache License, Version 2.0.
6 */
7
8'use strict';
9
10Object.defineProperty(exports, '__esModule', { value: true });
11
12/**
13 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
14 *
15 * Licensed under the Apache License, Version 2.0 (the "License");
16 * you may not use this file except in compliance with the License.
17 * You may obtain a copy of the License at
18 *
19 * http://www.apache.org/licenses/LICENSE-2.0
20 *
21 * Unless required by applicable law or agreed to in writing, software
22 * distributed under the License is distributed on an "AS-IS" BASIS,
23 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 * See the License for the specific language governing permissions and
25 * limitations under the License.
26 */
27
28/**
29 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
30 *
31 * Licensed under the Apache License, Version 2.0 (the "License");
32 * you may not use this file except in compliance with the License.
33 * You may obtain a copy of the License at
34 *
35 * http://www.apache.org/licenses/LICENSE-2.0
36 *
37 * Unless required by applicable law or agreed to in writing, software
38 * distributed under the License is distributed on an "AS-IS" BASIS,
39 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
40 * See the License for the specific language governing permissions and
41 * limitations under the License.
42 */
43/**
44 * The name of the HTML attribute that holds the element key
45 * (e.g. `<div key="foo">`). The attribute value, if it exists, is then used
46 * as the default key when importing an element.
47 * If null, no attribute value is used as the default key.
48 */
49let keyAttributeName = "key";
50function getKeyAttributeName() {
51 return keyAttributeName;
52}
53function setKeyAttributeName(name) {
54 keyAttributeName = name;
55}
56
57/**
58 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
59 *
60 * Licensed under the Apache License, Version 2.0 (the "License");
61 * you may not use this file except in compliance with the License.
62 * You may obtain a copy of the License at
63 *
64 * http://www.apache.org/licenses/LICENSE-2.0
65 *
66 * Unless required by applicable law or agreed to in writing, software
67 * distributed under the License is distributed on an "AS-IS" BASIS,
68 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
69 * See the License for the specific language governing permissions and
70 * limitations under the License.
71 */
72/**
73 * Keeps track whether or not we are in an attributes declaration (after
74 * elementOpenStart, but before elementOpenEnd).
75 */
76let inAttributes = false;
77/**
78 * Keeps track whether or not we are in an element that should not have its
79 * children cleared.
80 */
81let inSkip = false;
82/**
83 * Keeps track of whether or not we are in a patch.
84 */
85let inPatch = false;
86/**
87 * Asserts that a value exists and is not null or undefined. goog.asserts
88 * is not used in order to avoid dependencies on external code.
89 * @param val The value to assert is truthy.
90 * @returns The value.
91 */
92function assert(val) {
93 if (!val) {
94 throw new Error("Expected value to be defined");
95 }
96 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
97 return val;
98}
99/**
100 * Makes sure that there is a current patch context.
101 * @param functionName The name of the caller, for the error message.
102 */
103function assertInPatch(functionName) {
104 if (!inPatch) {
105 throw new Error("Cannot call " + functionName + "() unless in patch.");
106 }
107}
108/**
109 * Makes sure that a patch closes every node that it opened.
110 * @param openElement
111 * @param root
112 */
113function assertNoUnclosedTags(openElement, root) {
114 if (openElement === root) {
115 return;
116 }
117 let currentElement = openElement;
118 const openTags = [];
119 while (currentElement && currentElement !== root) {
120 openTags.push(currentElement.nodeName.toLowerCase());
121 currentElement = currentElement.parentNode;
122 }
123 throw new Error("One or more tags were not closed:\n" + openTags.join("\n"));
124}
125/**
126 * Makes sure that node being outer patched has a parent node.
127 * @param parent
128 */
129function assertPatchOuterHasParentNode(parent) {
130 if (!parent) {
131 console.warn("patchOuter requires the node have a parent if there is a key.");
132 }
133}
134/**
135 * Makes sure that the caller is not where attributes are expected.
136 * @param functionName The name of the caller, for the error message.
137 */
138function assertNotInAttributes(functionName) {
139 if (inAttributes) {
140 throw new Error(functionName +
141 "() can not be called between " +
142 "elementOpenStart() and elementOpenEnd().");
143 }
144}
145/**
146 * Makes sure that the caller is not inside an element that has declared skip.
147 * @param functionName The name of the caller, for the error message.
148 */
149function assertNotInSkip(functionName) {
150 if (inSkip) {
151 throw new Error(functionName +
152 "() may not be called inside an element " +
153 "that has called skip().");
154 }
155}
156/**
157 * Makes sure that the caller is where attributes are expected.
158 * @param functionName The name of the caller, for the error message.
159 */
160function assertInAttributes(functionName) {
161 if (!inAttributes) {
162 throw new Error(functionName +
163 "() can only be called after calling " +
164 "elementOpenStart().");
165 }
166}
167/**
168 * Makes sure the patch closes virtual attributes call
169 */
170function assertVirtualAttributesClosed() {
171 if (inAttributes) {
172 throw new Error("elementOpenEnd() must be called after calling " + "elementOpenStart().");
173 }
174}
175/**
176 * Makes sure that tags are correctly nested.
177 * @param currentNameOrCtor
178 * @param nameOrCtor
179 */
180function assertCloseMatchesOpenTag(currentNameOrCtor, nameOrCtor) {
181 if (currentNameOrCtor !== nameOrCtor) {
182 throw new Error('Received a call to close "' +
183 nameOrCtor +
184 '" but "' +
185 currentNameOrCtor +
186 '" was open.');
187 }
188}
189/**
190 * Makes sure that no children elements have been declared yet in the current
191 * element.
192 * @param functionName The name of the caller, for the error message.
193 * @param previousNode
194 */
195function assertNoChildrenDeclaredYet(functionName, previousNode) {
196 if (previousNode !== null) {
197 throw new Error(functionName +
198 "() must come before any child " +
199 "declarations inside the current element.");
200 }
201}
202/**
203 * Checks that a call to patchOuter actually patched the element.
204 * @param maybeStartNode The value for the currentNode when the patch
205 * started.
206 * @param maybeCurrentNode The currentNode when the patch finished.
207 * @param expectedNextNode The Node that is expected to follow the
208 * currentNode after the patch;
209 * @param expectedPrevNode The Node that is expected to preceed the
210 * currentNode after the patch.
211 */
212function assertPatchElementNoExtras(maybeStartNode, maybeCurrentNode, expectedNextNode, expectedPrevNode) {
213 const startNode = assert(maybeStartNode);
214 const currentNode = assert(maybeCurrentNode);
215 const wasUpdated = currentNode.nextSibling === expectedNextNode &&
216 currentNode.previousSibling === expectedPrevNode;
217 const wasChanged = currentNode.nextSibling === startNode.nextSibling &&
218 currentNode.previousSibling === expectedPrevNode;
219 const wasRemoved = currentNode === startNode;
220 if (!wasUpdated && !wasChanged && !wasRemoved) {
221 throw new Error("There must be exactly one top level call corresponding " +
222 "to the patched element.");
223 }
224}
225/**
226 * @param newContext The current patch context.
227 */
228function updatePatchContext(newContext) {
229 inPatch = newContext != null;
230}
231/**
232 * Updates the state of being in an attribute declaration.
233 * @param value Whether or not the patch is in an attribute declaration.
234 * @return the previous value.
235 */
236function setInAttributes(value) {
237 const previous = inAttributes;
238 inAttributes = value;
239 return previous;
240}
241/**
242 * Updates the state of being in a skip element.
243 * @param value Whether or not the patch is skipping the children of a
244 * parent node.
245 * @return the previous value.
246 */
247function setInSkip(value) {
248 const previous = inSkip;
249 inSkip = value;
250 return previous;
251}
252
253/**
254 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
255 *
256 * Licensed under the Apache License, Version 2.0 (the "License");
257 * you may not use this file except in compliance with the License.
258 * You may obtain a copy of the License at
259 *
260 * http://www.apache.org/licenses/LICENSE-2.0
261 *
262 * Unless required by applicable law or agreed to in writing, software
263 * distributed under the License is distributed on an "AS-IS" BASIS,
264 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
265 * See the License for the specific language governing permissions and
266 * limitations under the License.
267 */
268/**
269 * A cached reference to the hasOwnProperty function.
270 */
271const hasOwnProperty = Object.prototype.hasOwnProperty;
272/**
273 * A constructor function that will create blank objects.
274 */
275function Blank() { }
276Blank.prototype = Object.create(null);
277/**
278 * Used to prevent property collisions between our "map" and its prototype.
279 * @param map The map to check.
280 * @param property The property to check.
281 * @return Whether map has property.
282 */
283function has(map, property) {
284 return hasOwnProperty.call(map, property);
285}
286/**
287 * Creates an map object without a prototype.
288 * @returns An Object that can be used as a map.
289 */
290function createMap() {
291 return new Blank();
292}
293/**
294 * Truncates an array, removing items up until length.
295 * @param arr The array to truncate.
296 * @param length The new length of the array.
297 */
298function truncateArray(arr, length) {
299 while (arr.length > length) {
300 arr.pop();
301 }
302}
303/**
304 * Creates an array for a desired initial size. Note that the array will still
305 * be empty.
306 * @param initialAllocationSize The initial size to allocate.
307 * @returns An empty array, with an initial allocation for the desired size.
308 */
309function createArray(initialAllocationSize) {
310 const arr = new Array(initialAllocationSize);
311 truncateArray(arr, 0);
312 return arr;
313}
314
315/**
316 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
317 *
318 * Licensed under the Apache License, Version 2.0 (the "License");
319 * you may not use this file except in compliance with the License.
320 * You may obtain a copy of the License at
321 *
322 * http://www.apache.org/licenses/LICENSE-2.0
323 *
324 * Unless required by applicable law or agreed to in writing, software
325 * distributed under the License is distributed on an "AS-IS" BASIS,
326 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
327 * See the License for the specific language governing permissions and
328 * limitations under the License.
329 */
330const symbols = {
331 default: "__default"
332};
333
334/**
335 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
336 *
337 * Licensed under the Apache License, Version 2.0 (the "License");
338 * you may not use this file except in compliance with the License.
339 * You may obtain a copy of the License at
340 *
341 * http://www.apache.org/licenses/LICENSE-2.0
342 *
343 * Unless required by applicable law or agreed to in writing, software
344 * distributed under the License is distributed on an "AS-IS" BASIS,
345 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
346 * See the License for the specific language governing permissions and
347 * limitations under the License.
348 */
349/**
350 * @param name The name of the attribute. For example "tabindex" or
351 * "xlink:href".
352 * @returns The namespace to use for the attribute, or null if there is
353 * no namespace.
354 */
355function getNamespace(name) {
356 if (name.lastIndexOf("xml:", 0) === 0) {
357 return "http://www.w3.org/XML/1998/namespace";
358 }
359 if (name.lastIndexOf("xlink:", 0) === 0) {
360 return "http://www.w3.org/1999/xlink";
361 }
362 return null;
363}
364/**
365 * Applies an attribute or property to a given Element. If the value is null
366 * or undefined, it is removed from the Element. Otherwise, the value is set
367 * as an attribute.
368 * @param el The element to apply the attribute to.
369 * @param name The attribute's name.
370 * @param value The attribute's value.
371 */
372function applyAttr(el, name, value) {
373 if (value == null) {
374 el.removeAttribute(name);
375 }
376 else {
377 const attrNS = getNamespace(name);
378 if (attrNS) {
379 el.setAttributeNS(attrNS, name, String(value));
380 }
381 else {
382 el.setAttribute(name, String(value));
383 }
384 }
385}
386/**
387 * Applies a property to a given Element.
388 * @param el The element to apply the property to.
389 * @param name The property's name.
390 * @param value The property's value.
391 */
392function applyProp(el, name, value) {
393 el[name] = value;
394}
395/**
396 * Applies a value to a style declaration. Supports CSS custom properties by
397 * setting properties containing a dash using CSSStyleDeclaration.setProperty.
398 * @param style A style declaration.
399 * @param prop The property to apply. This can be either camelcase or dash
400 * separated. For example: "backgroundColor" and "background-color" are both
401 * supported.
402 * @param value The value of the property.
403 */
404function setStyleValue(style, prop, value) {
405 if (prop.indexOf("-") >= 0) {
406 style.setProperty(prop, value);
407 }
408 else {
409 style[prop] = value;
410 }
411}
412/**
413 * Applies a style to an Element. No vendor prefix expansion is done for
414 * property names/values.
415 * @param el The Element to apply the style for.
416 * @param name The attribute's name.
417 * @param style The style to set. Either a string of css or an object
418 * containing property-value pairs.
419 */
420function applyStyle(el, name, style) {
421 // MathML elements inherit from Element, which does not have style. We cannot
422 // do `instanceof HTMLElement` / `instanceof SVGElement`, since el can belong
423 // to a different document, so just check that it has a style.
424 assert("style" in el);
425 const elStyle = el.style;
426 if (typeof style === "string") {
427 elStyle.cssText = style;
428 }
429 else {
430 elStyle.cssText = "";
431 for (const prop in style) {
432 if (has(style, prop)) {
433 setStyleValue(elStyle, prop, style[prop]);
434 }
435 }
436 }
437}
438/**
439 * Updates a single attribute on an Element.
440 * @param el The Element to apply the attribute to.
441 * @param name The attribute's name.
442 * @param value The attribute's value. If the value is an object or
443 * function it is set on the Element, otherwise, it is set as an HTML
444 * attribute.
445 */
446function applyAttributeTyped(el, name, value) {
447 const type = typeof value;
448 if (type === "object" || type === "function") {
449 applyProp(el, name, value);
450 }
451 else {
452 applyAttr(el, name, value);
453 }
454}
455/**
456 * A publicly mutable object to provide custom mutators for attributes.
457 * NB: The result of createMap() has to be recast since closure compiler
458 * will just assume attributes is "any" otherwise and throws away
459 * the type annotation set by tsickle.
460 */
461const attributes = createMap();
462// Special generic mutator that's called for any attribute that does not
463// have a specific mutator.
464attributes[symbols.default] = applyAttributeTyped;
465attributes["style"] = applyStyle;
466/**
467 * Calls the appropriate attribute mutator for this attribute.
468 * @param el The Element to apply the attribute to.
469 * @param name The attribute's name.
470 * @param value The attribute's value. If the value is an object or
471 * function it is set on the Element, otherwise, it is set as an HTML
472 * attribute.
473 */
474function updateAttribute(el, name, value) {
475 const mutator = attributes[name] || attributes[symbols.default];
476 mutator(el, name, value);
477}
478
479/**
480 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
481 *
482 * Licensed under the Apache License, Version 2.0 (the "License");
483 * you may not use this file except in compliance with the License.
484 * You may obtain a copy of the License at
485 *
486 * http://www.apache.org/licenses/LICENSE-2.0
487 *
488 * Unless required by applicable law or agreed to in writing, software
489 * distributed under the License is distributed on an "AS-IS" BASIS,
490 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
491 * See the License for the specific language governing permissions and
492 * limitations under the License.
493 */
494const notifications = {
495 nodesCreated: null,
496 nodesDeleted: null
497};
498
499/**
500 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
501 *
502 * Licensed under the Apache License, Version 2.0 (the "License");
503 * you may not use this file except in compliance with the License.
504 * You may obtain a copy of the License at
505 *
506 * http://www.apache.org/licenses/LICENSE-2.0
507 *
508 * Unless required by applicable law or agreed to in writing, software
509 * distributed under the License is distributed on an "AS-IS" BASIS,
510 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
511 * See the License for the specific language governing permissions and
512 * limitations under the License.
513 */
514/**
515 * A context object keeps track of the state of a patch.
516 */
517class Context {
518 constructor() {
519 this.created = [];
520 this.deleted = [];
521 }
522 markCreated(node) {
523 this.created.push(node);
524 }
525 markDeleted(node) {
526 this.deleted.push(node);
527 }
528 /**
529 * Notifies about nodes that were created during the patch operation.
530 */
531 notifyChanges() {
532 if (notifications.nodesCreated && this.created.length > 0) {
533 notifications.nodesCreated(this.created);
534 }
535 if (notifications.nodesDeleted && this.deleted.length > 0) {
536 notifications.nodesDeleted(this.deleted);
537 }
538 }
539}
540
541/**
542 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
543 *
544 * Licensed under the Apache License, Version 2.0 (the "License");
545 * you may not use this file except in compliance with the License.
546 * You may obtain a copy of the License at
547 *
548 * http://www.apache.org/licenses/LICENSE-2.0
549 *
550 * Unless required by applicable law or agreed to in writing, software
551 * distributed under the License is distributed on an "AS-IS" BASIS,
552 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
553 * See the License for the specific language governing permissions and
554 * limitations under the License.
555 */
556/**
557 * Checks if the node is the root of a document. This is either a Document
558 * or ShadowRoot. DocumentFragments are included for simplicity of the
559 * implementation, though we only want to consider Documents or ShadowRoots.
560 * @param node The node to check.
561 * @return True if the node the root of a document, false otherwise.
562 */
563function isDocumentRoot(node) {
564 return node.nodeType === 11 || node.nodeType === 9;
565}
566/**
567 * Checks if the node is an Element. This is faster than an instanceof check.
568 * @param node The node to check.
569 * @return Whether or not the node is an Element.
570 */
571function isElement(node) {
572 return node.nodeType === 1;
573}
574/**
575 * @param node The node to start at, inclusive.
576 * @param root The root ancestor to get until, exclusive.
577 * @return The ancestry of DOM nodes.
578 */
579function getAncestry(node, root) {
580 const ancestry = [];
581 let cur = node;
582 while (cur !== root) {
583 const n = assert(cur);
584 ancestry.push(n);
585 cur = n.parentNode;
586 }
587 return ancestry;
588}
589/**
590 * @param this
591 * @returns The root node of the DOM tree that contains this node.
592 */
593const getRootNode = (typeof Node !== "undefined" && Node.prototype.getRootNode) ||
594 function () {
595 let cur = this;
596 let prev = cur;
597 while (cur) {
598 prev = cur;
599 cur = cur.parentNode;
600 }
601 return prev;
602 };
603/**
604 * @param node The node to get the activeElement for.
605 * @returns The activeElement in the Document or ShadowRoot
606 * corresponding to node, if present.
607 */
608function getActiveElement(node) {
609 const root = getRootNode.call(node);
610 return isDocumentRoot(root) ? root.activeElement : null;
611}
612/**
613 * Gets the path of nodes that contain the focused node in the same document as
614 * a reference node, up until the root.
615 * @param node The reference node to get the activeElement for.
616 * @param root The root to get the focused path until.
617 * @returns The path of focused parents, if any exist.
618 */
619function getFocusedPath(node, root) {
620 const activeElement = getActiveElement(node);
621 if (!activeElement || !node.contains(activeElement)) {
622 return [];
623 }
624 return getAncestry(activeElement, root);
625}
626/**
627 * Like insertBefore, but instead instead of moving the desired node, instead
628 * moves all the other nodes after.
629 * @param parentNode
630 * @param node
631 * @param referenceNode
632 */
633function moveBefore(parentNode, node, referenceNode) {
634 const insertReferenceNode = node.nextSibling;
635 let cur = referenceNode;
636 while (cur !== null && cur !== node) {
637 const next = cur.nextSibling;
638 parentNode.insertBefore(cur, insertReferenceNode);
639 cur = next;
640 }
641}
642
643/**
644 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
645 *
646 * Licensed under the Apache License, Version 2.0 (the "License");
647 * you may not use this file except in compliance with the License.
648 * You may obtain a copy of the License at
649 *
650 * http://www.apache.org/licenses/LICENSE-2.0
651 *
652 * Unless required by applicable law or agreed to in writing, software
653 * distributed under the License is distributed on an "AS-IS" BASIS,
654 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
655 * See the License for the specific language governing permissions and
656 * limitations under the License.
657 */
658/**
659 * Keeps track of information needed to perform diffs for a given DOM node.
660 */
661class NodeData {
662 constructor(nameOrCtor, key, text) {
663 /**
664 * An array of attribute name/value pairs, used for quickly diffing the
665 * incomming attributes to see if the DOM node's attributes need to be
666 * updated.
667 */
668 this._attrsArr = null;
669 /**
670 * Whether or not the statics have been applied for the node yet.
671 */
672 this.staticsApplied = false;
673 this.nameOrCtor = nameOrCtor;
674 this.key = key;
675 this.text = text;
676 }
677 hasEmptyAttrsArr() {
678 const attrs = this._attrsArr;
679 return !attrs || !attrs.length;
680 }
681 getAttrsArr(length) {
682 return this._attrsArr || (this._attrsArr = createArray(length));
683 }
684}
685/**
686 * Initializes a NodeData object for a Node.
687 * @param node The Node to initialized data for.
688 * @param nameOrCtor The NameOrCtorDef to use when diffing.
689 * @param key The Key for the Node.
690 * @param text The data of a Text node, if importing a Text node.
691 * @returns A NodeData object with the existing attributes initialized.
692 */
693function initData(node, nameOrCtor, key, text) {
694 const data = new NodeData(nameOrCtor, key, text);
695 node["__incrementalDOMData"] = data;
696 return data;
697}
698/**
699 * @param node The node to check.
700 * @returns True if the NodeData already exists, false otherwise.
701 */
702function isDataInitialized(node) {
703 return Boolean(node["__incrementalDOMData"]);
704}
705/**
706 * Records the element's attributes.
707 * @param node The Element that may have attributes
708 * @param data The Element's data
709 */
710function recordAttributes(node, data) {
711 const attributes = node.attributes;
712 const length = attributes.length;
713 if (!length) {
714 return;
715 }
716 const attrsArr = data.getAttrsArr(length);
717 // Use a cached length. The attributes array is really a live NamedNodeMap,
718 // which exists as a DOM "Host Object" (probably as C++ code). This makes the
719 // usual constant length iteration very difficult to optimize in JITs.
720 for (let i = 0, j = 0; i < length; i += 1, j += 2) {
721 const attr = attributes[i];
722 const name = attr.name;
723 const value = attr.value;
724 attrsArr[j] = name;
725 attrsArr[j + 1] = value;
726 }
727}
728/**
729 * Imports single node and its subtree, initializing caches, if it has not
730 * already been imported.
731 * @param node The node to import.
732 * @param fallbackKey A key to use if importing and no key was specified.
733 * Useful when not transmitting keys from serverside render and doing an
734 * immediate no-op diff.
735 * @returns The NodeData for the node.
736 */
737function importSingleNode(node, fallbackKey) {
738 if (node["__incrementalDOMData"]) {
739 return node["__incrementalDOMData"];
740 }
741 const nodeName = isElement(node) ? node.localName : node.nodeName;
742 const keyAttrName = getKeyAttributeName();
743 const keyAttr = isElement(node) && keyAttrName != null
744 ? node.getAttribute(keyAttrName)
745 : null;
746 const key = isElement(node) ? keyAttr || fallbackKey : null;
747 const data = initData(node, nodeName, key);
748 if (isElement(node)) {
749 recordAttributes(node, data);
750 }
751 return data;
752}
753/**
754 * Imports node and its subtree, initializing caches.
755 * @param node The Node to import.
756 */
757function importNode(node) {
758 importSingleNode(node);
759 for (let child = node.firstChild; child; child = child.nextSibling) {
760 importNode(child);
761 }
762}
763/**
764 * Retrieves the NodeData object for a Node, creating it if necessary.
765 * @param node The node to get data for.
766 * @param fallbackKey A key to use if importing and no key was specified.
767 * Useful when not transmitting keys from serverside render and doing an
768 * immediate no-op diff.
769 * @returns The NodeData for the node.
770 */
771function getData(node, fallbackKey) {
772 return importSingleNode(node, fallbackKey);
773}
774/**
775 * Gets the key for a Node. note that the Node should have been imported
776 * by now.
777 * @param node The node to check.
778 * @returns The key used to create the node.
779 */
780function getKey(node) {
781 assert(node["__incrementalDOMData"]);
782 return getData(node).key;
783}
784/**
785 * Clears all caches from a node and all of its children.
786 * @param node The Node to clear the cache for.
787 */
788function clearCache(node) {
789 node["__incrementalDOMData"] = null;
790 for (let child = node.firstChild; child; child = child.nextSibling) {
791 clearCache(child);
792 }
793}
794
795/**
796 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
797 *
798 * Licensed under the Apache License, Version 2.0 (the "License");
799 * you may not use this file except in compliance with the License.
800 * You may obtain a copy of the License at
801 *
802 * http://www.apache.org/licenses/LICENSE-2.0
803 *
804 * Unless required by applicable law or agreed to in writing, software
805 * distributed under the License is distributed on an "AS-IS" BASIS,
806 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
807 * See the License for the specific language governing permissions and
808 * limitations under the License.
809 */
810/**
811 * Gets the namespace to create an element (of a given tag) in.
812 * @param tag The tag to get the namespace for.
813 * @param parent The current parent Node, if any.
814 * @returns The namespace to use,
815 */
816function getNamespaceForTag(tag, parent) {
817 if (tag === "svg") {
818 return "http://www.w3.org/2000/svg";
819 }
820 if (tag === "math") {
821 return "http://www.w3.org/1998/Math/MathML";
822 }
823 if (parent == null) {
824 return null;
825 }
826 if (getData(parent).nameOrCtor === "foreignObject") {
827 return null;
828 }
829 return parent.namespaceURI;
830}
831/**
832 * Creates an Element and initializes the NodeData.
833 * @param doc The document with which to create the Element.
834 * @param parent The parent of new Element.
835 * @param nameOrCtor The tag or constructor for the Element.
836 * @param key A key to identify the Element.
837 * @returns The newly created Element.
838 */
839function createElement(doc, parent, nameOrCtor, key) {
840 let el;
841 if (typeof nameOrCtor === "function") {
842 el = new nameOrCtor();
843 }
844 else {
845 const namespace = getNamespaceForTag(nameOrCtor, parent);
846 if (namespace) {
847 el = doc.createElementNS(namespace, nameOrCtor);
848 }
849 else {
850 el = doc.createElement(nameOrCtor);
851 }
852 }
853 initData(el, nameOrCtor, key);
854 return el;
855}
856/**
857 * Creates a Text Node.
858 * @param doc The document with which to create the Element.
859 * @returns The newly created Text.
860 */
861function createText(doc) {
862 const node = doc.createTextNode("");
863 initData(node, "#text", null);
864 return node;
865}
866
867/**
868 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
869 *
870 * Licensed under the Apache License, Version 2.0 (the "License");
871 * you may not use this file except in compliance with the License.
872 * You may obtain a copy of the License at
873 *
874 * http://www.apache.org/licenses/LICENSE-2.0
875 *
876 * Unless required by applicable law or agreed to in writing, software
877 * distributed under the License is distributed on an "AS-IS" BASIS,
878 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
879 * See the License for the specific language governing permissions and
880 * limitations under the License.
881 */
882/**
883 * The default match function to use, if one was not specified when creating
884 * the patcher.
885 * @param matchNode The node to match against, unused.
886 * @param nameOrCtor The name or constructor as declared.
887 * @param expectedNameOrCtor The name or constructor of the existing node.
888 * @param key The key as declared.
889 * @param expectedKey The key of the existing node.
890 * @returns True if the node matches, false otherwise.
891 */
892function defaultMatchFn(matchNode, nameOrCtor, expectedNameOrCtor, key, expectedKey) {
893 // Key check is done using double equals as we want to treat a null key the
894 // same as undefined. This should be okay as the only values allowed are
895 // strings, null and undefined so the == semantics are not too weird.
896 return nameOrCtor == expectedNameOrCtor && key == expectedKey;
897}
898let context = null;
899let currentNode = null;
900let currentParent = null;
901let doc = null;
902let focusPath = [];
903let matchFn = defaultMatchFn;
904/**
905 * Used to build up call arguments. Each patch call gets a separate copy, so
906 * this works with nested calls to patch.
907 */
908let argsBuilder = [];
909/**
910 * Used to build up attrs for the an element.
911 */
912let attrsBuilder = [];
913/**
914 * TODO(sparhami) We should just export argsBuilder directly when Closure
915 * Compiler supports ES6 directly.
916 * @returns The Array used for building arguments.
917 */
918function getArgsBuilder() {
919 return argsBuilder;
920}
921/**
922 * TODO(sparhami) We should just export attrsBuilder directly when Closure
923 * Compiler supports ES6 directly.
924 * @returns The Array used for building arguments.
925 */
926function getAttrsBuilder() {
927 return attrsBuilder;
928}
929/**
930 * Checks whether or not the current node matches the specified nameOrCtor and
931 * key. This uses the specified match function when creating the patcher.
932 * @param matchNode A node to match the data to.
933 * @param nameOrCtor The name or constructor to check for.
934 * @param key The key used to identify the Node.
935 * @return True if the node matches, false otherwise.
936 */
937function matches(matchNode, nameOrCtor, key) {
938 const data = getData(matchNode, key);
939 return matchFn(matchNode, nameOrCtor, data.nameOrCtor, key, data.key);
940}
941/**
942 * Finds the matching node, starting at `node` and looking at the subsequent
943 * siblings if a key is used.
944 * @param matchNode The node to start looking at.
945 * @param nameOrCtor The name or constructor for the Node.
946 * @param key The key used to identify the Node.
947 * @returns The matching Node, if any exists.
948 */
949function getMatchingNode(matchNode, nameOrCtor, key) {
950 if (!matchNode) {
951 return null;
952 }
953 let cur = matchNode;
954 do {
955 if (matches(cur, nameOrCtor, key)) {
956 return cur;
957 }
958 } while (key && (cur = cur.nextSibling));
959 return null;
960}
961/**
962 * Clears out any unvisited Nodes in a given range.
963 * @param maybeParentNode
964 * @param startNode The node to start clearing from, inclusive.
965 * @param endNode The node to clear until, exclusive.
966 */
967function clearUnvisitedDOM(maybeParentNode, startNode, endNode) {
968 const parentNode = maybeParentNode;
969 let child = startNode;
970 while (child !== endNode) {
971 const next = child.nextSibling;
972 parentNode.removeChild(child);
973 context.markDeleted(child);
974 child = next;
975 }
976}
977/**
978 * @return The next Node to be patched.
979 */
980function getNextNode() {
981 if (currentNode) {
982 return currentNode.nextSibling;
983 }
984 else {
985 return currentParent.firstChild;
986 }
987}
988/**
989 * Changes to the first child of the current node.
990 */
991function enterNode() {
992 currentParent = currentNode;
993 currentNode = null;
994}
995/**
996 * Changes to the parent of the current node, removing any unvisited children.
997 */
998function exitNode() {
999 clearUnvisitedDOM(currentParent, getNextNode(), null);
1000 currentNode = currentParent;
1001 currentParent = currentParent.parentNode;
1002}
1003/**
1004 * Changes to the next sibling of the current node.
1005 */
1006function nextNode() {
1007 currentNode = getNextNode();
1008}
1009/**
1010 * Creates a Node and marking it as created.
1011 * @param nameOrCtor The name or constructor for the Node.
1012 * @param key The key used to identify the Node.
1013 * @return The newly created node.
1014 */
1015function createNode(nameOrCtor, key) {
1016 let node;
1017 if (nameOrCtor === "#text") {
1018 node = createText(doc);
1019 }
1020 else {
1021 node = createElement(doc, currentParent, nameOrCtor, key);
1022 }
1023 context.markCreated(node);
1024 return node;
1025}
1026/**
1027 * Aligns the virtual Node definition with the actual DOM, moving the
1028 * corresponding DOM node to the correct location or creating it if necessary.
1029 * @param nameOrCtor The name or constructor for the Node.
1030 * @param key The key used to identify the Node.
1031 */
1032function alignWithDOM(nameOrCtor, key) {
1033 nextNode();
1034 const existingNode = getMatchingNode(currentNode, nameOrCtor, key);
1035 const node = existingNode || createNode(nameOrCtor, key);
1036 // If we are at the matching node, then we are done.
1037 if (node === currentNode) {
1038 return;
1039 }
1040 // Re-order the node into the right position, preserving focus if either
1041 // node or currentNode are focused by making sure that they are not detached
1042 // from the DOM.
1043 if (focusPath.indexOf(node) >= 0) {
1044 // Move everything else before the node.
1045 moveBefore(currentParent, node, currentNode);
1046 }
1047 else {
1048 currentParent.insertBefore(node, currentNode);
1049 }
1050 currentNode = node;
1051}
1052/**
1053 * Makes sure that the current node is an Element with a matching nameOrCtor and
1054 * key.
1055 *
1056 * @param nameOrCtor The tag or constructor for the Element.
1057 * @param key The key used to identify this element. This can be an
1058 * empty string, but performance may be better if a unique value is used
1059 * when iterating over an array of items.
1060 * @return The corresponding Element.
1061 */
1062function open(nameOrCtor, key) {
1063 alignWithDOM(nameOrCtor, key);
1064 enterNode();
1065 return currentParent;
1066}
1067/**
1068 * Closes the currently open Element, removing any unvisited children if
1069 * necessary.
1070 * @returns The Element that was just closed.
1071 */
1072function close() {
1073 {
1074 setInSkip(false);
1075 }
1076 exitNode();
1077 return currentNode;
1078}
1079/**
1080 * Makes sure the current node is a Text node and creates a Text node if it is
1081 * not.
1082 * @returns The Text node that was aligned or created.
1083 */
1084function text() {
1085 alignWithDOM("#text", null);
1086 return currentNode;
1087}
1088/**
1089 * @returns The current Element being patched.
1090 */
1091function currentElement() {
1092 {
1093 assertInPatch("currentElement");
1094 assertNotInAttributes("currentElement");
1095 }
1096 return currentParent;
1097}
1098/**
1099 * @return The Node that will be evaluated for the next instruction.
1100 */
1101function currentPointer() {
1102 {
1103 assertInPatch("currentPointer");
1104 assertNotInAttributes("currentPointer");
1105 }
1106 // TODO(tomnguyen): assert that this is not null
1107 return getNextNode();
1108}
1109/**
1110 * Skips the children in a subtree, allowing an Element to be closed without
1111 * clearing out the children.
1112 */
1113function skip() {
1114 {
1115 assertNoChildrenDeclaredYet("skip", currentNode);
1116 setInSkip(true);
1117 }
1118 currentNode = currentParent.lastChild;
1119}
1120/**
1121 * Returns a patcher function that sets up and restores a patch context,
1122 * running the run function with the provided data.
1123 * @param run The function that will run the patch.
1124 * @param patchConfig The configuration to use for the patch.
1125 * @returns The created patch function.
1126 */
1127function createPatcher(run, patchConfig = {}) {
1128 const { matches = defaultMatchFn } = patchConfig;
1129 const f = (node, fn, data) => {
1130 const prevContext = context;
1131 const prevDoc = doc;
1132 const prevFocusPath = focusPath;
1133 const prevArgsBuilder = argsBuilder;
1134 const prevAttrsBuilder = attrsBuilder;
1135 const prevCurrentNode = currentNode;
1136 const prevCurrentParent = currentParent;
1137 const prevMatchFn = matchFn;
1138 let previousInAttributes = false;
1139 let previousInSkip = false;
1140 doc = node.ownerDocument;
1141 context = new Context();
1142 matchFn = matches;
1143 argsBuilder = [];
1144 attrsBuilder = [];
1145 currentNode = null;
1146 currentParent = node.parentNode;
1147 focusPath = getFocusedPath(node, currentParent);
1148 {
1149 previousInAttributes = setInAttributes(false);
1150 previousInSkip = setInSkip(false);
1151 updatePatchContext(context);
1152 }
1153 try {
1154 const retVal = run(node, fn, data);
1155 {
1156 assertVirtualAttributesClosed();
1157 }
1158 return retVal;
1159 }
1160 finally {
1161 context.notifyChanges();
1162 doc = prevDoc;
1163 context = prevContext;
1164 matchFn = prevMatchFn;
1165 argsBuilder = prevArgsBuilder;
1166 attrsBuilder = prevAttrsBuilder;
1167 currentNode = prevCurrentNode;
1168 currentParent = prevCurrentParent;
1169 focusPath = prevFocusPath;
1170 // Needs to be done after assertions because assertions rely on state
1171 // from these methods.
1172 {
1173 setInAttributes(previousInAttributes);
1174 setInSkip(previousInSkip);
1175 updatePatchContext(context);
1176 }
1177 }
1178 };
1179 return f;
1180}
1181/**
1182 * Creates a patcher that patches the document starting at node with a
1183 * provided function. This function may be called during an existing patch operation.
1184 * @param patchConfig The config to use for the patch.
1185 * @returns The created function for patching an Element's children.
1186 */
1187function createPatchInner(patchConfig) {
1188 return createPatcher((node, fn, data) => {
1189 currentNode = node;
1190 enterNode();
1191 fn(data);
1192 exitNode();
1193 {
1194 assertNoUnclosedTags(currentNode, node);
1195 }
1196 return node;
1197 }, patchConfig);
1198}
1199/**
1200 * Creates a patcher that patches an Element with the the provided function.
1201 * Exactly one top level element call should be made corresponding to `node`.
1202 * @param patchConfig The config to use for the patch.
1203 * @returns The created function for patching an Element.
1204 */
1205function createPatchOuter(patchConfig) {
1206 return createPatcher((node, fn, data) => {
1207 const startNode = { nextSibling: node };
1208 let expectedNextNode = null;
1209 let expectedPrevNode = null;
1210 {
1211 expectedNextNode = node.nextSibling;
1212 expectedPrevNode = node.previousSibling;
1213 }
1214 currentNode = startNode;
1215 fn(data);
1216 {
1217 assertPatchOuterHasParentNode(currentParent);
1218 assertPatchElementNoExtras(startNode, currentNode, expectedNextNode, expectedPrevNode);
1219 }
1220 if (currentParent) {
1221 clearUnvisitedDOM(currentParent, getNextNode(), node.nextSibling);
1222 }
1223 return startNode === currentNode ? null : currentNode;
1224 }, patchConfig);
1225}
1226const patchInner = createPatchInner();
1227const patchOuter = createPatchOuter();
1228
1229/**
1230 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
1231 *
1232 * Licensed under the Apache License, Version 2.0 (the "License");
1233 * you may not use this file except in compliance with the License.
1234 * You may obtain a copy of the License at
1235 *
1236 * http://www.apache.org/licenses/LICENSE-2.0
1237 *
1238 * Unless required by applicable law or agreed to in writing, software
1239 * distributed under the License is distributed on an "AS-IS" BASIS,
1240 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1241 * See the License for the specific language governing permissions and
1242 * limitations under the License.
1243 */
1244const buffer = [];
1245let bufferStart = 0;
1246/**
1247 * TODO(tomnguyen): This is a bit silly and really needs to be better typed.
1248 * @param fn A function to call.
1249 * @param a The first argument to the function.
1250 * @param b The second argument to the function.
1251 * @param c The third argument to the function.
1252 */
1253function queueChange(fn, a, b, c) {
1254 buffer.push(fn);
1255 buffer.push(a);
1256 buffer.push(b);
1257 buffer.push(c);
1258}
1259/**
1260 * Flushes the changes buffer, calling the functions for each change.
1261 */
1262function flush() {
1263 // A change may cause this function to be called re-entrantly. Keep track of
1264 // the portion of the buffer we are consuming. Updates the start pointer so
1265 // that the next call knows where to start from.
1266 const start = bufferStart;
1267 const end = buffer.length;
1268 bufferStart = end;
1269 for (let i = start; i < end; i += 4) {
1270 const fn = buffer[i];
1271 fn(buffer[i + 1], buffer[i + 2], buffer[i + 3]);
1272 }
1273 bufferStart = start;
1274 truncateArray(buffer, start);
1275}
1276
1277/**
1278 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
1279 *
1280 * Licensed under the Apache License, Version 2.0 (the "License");
1281 * you may not use this file except in compliance with the License.
1282 * You may obtain a copy of the License at
1283 *
1284 * http://www.apache.org/licenses/LICENSE-2.0
1285 *
1286 * Unless required by applicable law or agreed to in writing, software
1287 * distributed under the License is distributed on an "AS-IS" BASIS,
1288 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1289 * See the License for the specific language governing permissions and
1290 * limitations under the License.
1291 */
1292/**
1293 * Used to keep track of the previous values when a 2-way diff is necessary.
1294 * This object is cleared out and reused.
1295 */
1296const prevValuesMap = createMap();
1297/**
1298 * Calculates the diff between previous and next values, calling the update
1299 * function when an item has changed value. If an item from the previous values
1300 * is not present in the the next values, the update function is called with a
1301 * value of `undefined`.
1302 * @param prev The previous values, alternating name, value pairs.
1303 * @param next The next values, alternating name, value pairs.
1304 * @param updateCtx The context for the updateFn.
1305 * @param updateFn A function to call when a value has changed.
1306 */
1307function calculateDiff(prev, next, updateCtx, updateFn) {
1308 const isNew = !prev.length;
1309 let i = 0;
1310 for (; i < next.length; i += 2) {
1311 const name = next[i];
1312 if (isNew) {
1313 prev[i] = name;
1314 }
1315 else if (prev[i] !== name) {
1316 break;
1317 }
1318 const value = next[i + 1];
1319 if (isNew || prev[i + 1] !== value) {
1320 prev[i + 1] = value;
1321 queueChange(updateFn, updateCtx, name, value);
1322 }
1323 }
1324 // Items did not line up exactly as before, need to make sure old items are
1325 // removed. This should be a rare case.
1326 if (i < next.length || i < prev.length) {
1327 const startIndex = i;
1328 for (i = startIndex; i < prev.length; i += 2) {
1329 prevValuesMap[prev[i]] = prev[i + 1];
1330 }
1331 for (i = startIndex; i < next.length; i += 2) {
1332 const name = next[i];
1333 const value = next[i + 1];
1334 if (prevValuesMap[name] !== value) {
1335 queueChange(updateFn, updateCtx, name, value);
1336 }
1337 prev[i] = name;
1338 prev[i + 1] = value;
1339 delete prevValuesMap[name];
1340 }
1341 truncateArray(prev, next.length);
1342 for (const name in prevValuesMap) {
1343 queueChange(updateFn, updateCtx, name, undefined);
1344 delete prevValuesMap[name];
1345 }
1346 }
1347 flush();
1348}
1349
1350/**
1351 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
1352 *
1353 * Licensed under the Apache License, Version 2.0 (the "License");
1354 * you may not use this file except in compliance with the License.
1355 * You may obtain a copy of the License at
1356 *
1357 * http://www.apache.org/licenses/LICENSE-2.0
1358 *
1359 * Unless required by applicable law or agreed to in writing, software
1360 * distributed under the License is distributed on an "AS-IS" BASIS,
1361 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1362 * See the License for the specific language governing permissions and
1363 * limitations under the License.
1364 */
1365/**
1366 * The offset in the virtual element declaration where the attributes are
1367 * specified.
1368 */
1369const ATTRIBUTES_OFFSET = 3;
1370/**
1371 * Used to keep track of the previous values when a 2-way diff is necessary.
1372 * This object is reused.
1373 * TODO(sparhamI) Scope this to a patch so you can call patch from an attribute
1374 * update.
1375 */
1376const prevAttrsMap = createMap();
1377/**
1378 * @param element The Element to diff the attrs for.
1379 * @param data The NodeData associated with the Element.
1380 */
1381function diffAttrs(element, data) {
1382 const attrsBuilder = getAttrsBuilder();
1383 const prevAttrsArr = data.getAttrsArr(attrsBuilder.length);
1384 calculateDiff(prevAttrsArr, attrsBuilder, element, updateAttribute);
1385 truncateArray(attrsBuilder, 0);
1386}
1387/**
1388 * Applies the statics. When importing an Element, any existing attributes that
1389 * match a static are converted into a static attribute.
1390 * @param node The Element to apply statics for.
1391 * @param data The NodeData associated with the Element.
1392 * @param statics The statics array.
1393 */
1394function diffStatics(node, data, statics) {
1395 if (data.staticsApplied) {
1396 return;
1397 }
1398 data.staticsApplied = true;
1399 if (!statics || !statics.length) {
1400 return;
1401 }
1402 if (data.hasEmptyAttrsArr()) {
1403 for (let i = 0; i < statics.length; i += 2) {
1404 updateAttribute(node, statics[i], statics[i + 1]);
1405 }
1406 return;
1407 }
1408 for (let i = 0; i < statics.length; i += 2) {
1409 prevAttrsMap[statics[i]] = i + 1;
1410 }
1411 const attrsArr = data.getAttrsArr(0);
1412 let j = 0;
1413 for (let i = 0; i < attrsArr.length; i += 2) {
1414 const name = attrsArr[i];
1415 const value = attrsArr[i + 1];
1416 const staticsIndex = prevAttrsMap[name];
1417 if (staticsIndex) {
1418 // For any attrs that are static and have the same value, make sure we do
1419 // not set them again.
1420 if (statics[staticsIndex] === value) {
1421 delete prevAttrsMap[name];
1422 }
1423 continue;
1424 }
1425 // For any attrs that are dynamic, move them up to the right place.
1426 attrsArr[j] = name;
1427 attrsArr[j + 1] = value;
1428 j += 2;
1429 }
1430 // Anything after `j` was either moved up already or static.
1431 truncateArray(attrsArr, j);
1432 for (const name in prevAttrsMap) {
1433 updateAttribute(node, name, statics[prevAttrsMap[name]]);
1434 delete prevAttrsMap[name];
1435 }
1436}
1437/**
1438 * Declares a virtual Element at the current location in the document. This
1439 * corresponds to an opening tag and a elementClose tag is required. This is
1440 * like elementOpen, but the attributes are defined using the attr function
1441 * rather than being passed as arguments. Must be folllowed by 0 or more calls
1442 * to attr, then a call to elementOpenEnd.
1443 * @param nameOrCtor The Element's tag or constructor.
1444 * @param key The key used to identify this element. This can be an
1445 * empty string, but performance may be better if a unique value is used
1446 * when iterating over an array of items.
1447 * @param statics An array of attribute name/value pairs of the static
1448 * attributes for the Element. Attributes will only be set once when the
1449 * Element is created.
1450 */
1451function elementOpenStart(nameOrCtor, key, statics) {
1452 const argsBuilder = getArgsBuilder();
1453 {
1454 assertNotInAttributes("elementOpenStart");
1455 setInAttributes(true);
1456 }
1457 argsBuilder[0] = nameOrCtor;
1458 argsBuilder[1] = key;
1459 argsBuilder[2] = statics;
1460}
1461/**
1462 * Allows you to define a key after an elementOpenStart. This is useful in
1463 * templates that define key after an element has been opened ie
1464 * `<div key('foo')></div>`.
1465 * @param key The key to use for the next call.
1466 */
1467function key(key) {
1468 const argsBuilder = getArgsBuilder();
1469 {
1470 assertInAttributes("key");
1471 assert(argsBuilder);
1472 }
1473 argsBuilder[1] = key;
1474}
1475/**
1476 * Buffers an attribute, which will get applied during the next call to
1477 * `elementOpen`, `elementOpenEnd` or `applyAttrs`.
1478 * @param name The of the attribute to buffer.
1479 * @param value The value of the attribute to buffer.
1480 */
1481function attr(name, value) {
1482 const attrsBuilder = getAttrsBuilder();
1483 {
1484 assertInPatch("attr");
1485 }
1486 attrsBuilder.push(name);
1487 attrsBuilder.push(value);
1488}
1489/**
1490 * Closes an open tag started with elementOpenStart.
1491 * @return The corresponding Element.
1492 */
1493function elementOpenEnd() {
1494 const argsBuilder = getArgsBuilder();
1495 {
1496 assertInAttributes("elementOpenEnd");
1497 setInAttributes(false);
1498 }
1499 const node = open(argsBuilder[0], argsBuilder[1]);
1500 const data = getData(node);
1501 diffStatics(node, data, argsBuilder[2]);
1502 diffAttrs(node, data);
1503 truncateArray(argsBuilder, 0);
1504 return node;
1505}
1506/**
1507 * @param nameOrCtor The Element's tag or constructor.
1508 * @param key The key used to identify this element. This can be an
1509 * empty string, but performance may be better if a unique value is used
1510 * when iterating over an array of items.
1511 * @param statics An array of attribute name/value pairs of the static
1512 * attributes for the Element. Attributes will only be set once when the
1513 * Element is created.
1514 * @param varArgs, Attribute name/value pairs of the dynamic attributes
1515 * for the Element.
1516 * @return The corresponding Element.
1517 */
1518function elementOpen(nameOrCtor, key,
1519// Ideally we could tag statics and varArgs as an array where every odd
1520// element is a string and every even element is any, but this is hard.
1521statics, ...varArgs) {
1522 {
1523 assertNotInAttributes("elementOpen");
1524 assertNotInSkip("elementOpen");
1525 }
1526 elementOpenStart(nameOrCtor, key, statics);
1527 for (let i = ATTRIBUTES_OFFSET; i < arguments.length; i += 2) {
1528 attr(arguments[i], arguments[i + 1]);
1529 }
1530 return elementOpenEnd();
1531}
1532/**
1533 * Applies the currently buffered attrs to the currently open element. This
1534 * clears the buffered attributes.
1535 */
1536function applyAttrs() {
1537 const node = currentElement();
1538 const data = getData(node);
1539 diffAttrs(node, data);
1540}
1541/**
1542 * Applies the current static attributes to the currently open element. Note:
1543 * statics should be applied before calling `applyAtrs`.
1544 * @param statics The statics to apply to the current element.
1545 */
1546function applyStatics(statics) {
1547 const node = currentElement();
1548 const data = getData(node);
1549 diffStatics(node, data, statics);
1550}
1551/**
1552 * Closes an open virtual Element.
1553 *
1554 * @param nameOrCtor The Element's tag or constructor.
1555 * @return The corresponding Element.
1556 */
1557function elementClose(nameOrCtor) {
1558 {
1559 assertNotInAttributes("elementClose");
1560 }
1561 const node = close();
1562 {
1563 assertCloseMatchesOpenTag(getData(node).nameOrCtor, nameOrCtor);
1564 }
1565 return node;
1566}
1567/**
1568 * Declares a virtual Element at the current location in the document that has
1569 * no children.
1570 * @param nameOrCtor The Element's tag or constructor.
1571 * @param key The key used to identify this element. This can be an
1572 * empty string, but performance may be better if a unique value is used
1573 * when iterating over an array of items.
1574 * @param statics An array of attribute name/value pairs of the static
1575 * attributes for the Element. Attributes will only be set once when the
1576 * Element is created.
1577 * @param varArgs Attribute name/value pairs of the dynamic attributes
1578 * for the Element.
1579 * @return The corresponding Element.
1580 */
1581function elementVoid(nameOrCtor, key,
1582// Ideally we could tag statics and varArgs as an array where every odd
1583// element is a string and every even element is any, but this is hard.
1584statics, ...varArgs) {
1585 elementOpen.apply(null, arguments);
1586 return elementClose(nameOrCtor);
1587}
1588/**
1589 * Declares a virtual Text at this point in the document.
1590 *
1591 * @param value The value of the Text.
1592 * @param varArgs
1593 * Functions to format the value which are called only when the value has
1594 * changed.
1595 * @return The corresponding text node.
1596 */
1597function text$1(value, ...varArgs) {
1598 {
1599 assertNotInAttributes("text");
1600 assertNotInSkip("text");
1601 }
1602 const node = text();
1603 const data = getData(node);
1604 if (data.text !== value) {
1605 data.text = value;
1606 let formatted = value;
1607 for (let i = 1; i < arguments.length; i += 1) {
1608 /*
1609 * Call the formatter function directly to prevent leaking arguments.
1610 * https://github.com/google/incremental-dom/pull/204#issuecomment-178223574
1611 */
1612 const fn = arguments[i];
1613 formatted = fn(formatted);
1614 }
1615 // Setting node.data resets the cursor in IE/Edge.
1616 if (node.data !== formatted) {
1617 node.data = formatted;
1618 }
1619 }
1620 return node;
1621}
1622
1623/**
1624 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
1625 *
1626 * Licensed under the Apache License, Version 2.0 (the "License");
1627 * you may not use this file except in compliance with the License.
1628 * You may obtain a copy of the License at
1629 *
1630 * http://www.apache.org/licenses/LICENSE-2.0
1631 *
1632 * Unless required by applicable law or agreed to in writing, software
1633 * distributed under the License is distributed on an "AS-IS" BASIS,
1634 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1635 * See the License for the specific language governing permissions and
1636 * limitations under the License.
1637 */
1638
1639exports.applyAttr = applyAttr;
1640exports.applyProp = applyProp;
1641exports.attributes = attributes;
1642exports.alignWithDOM = alignWithDOM;
1643exports.close = close;
1644exports.createPatchInner = createPatchInner;
1645exports.createPatchOuter = createPatchOuter;
1646exports.currentElement = currentElement;
1647exports.currentPointer = currentPointer;
1648exports.open = open;
1649exports.patch = patchInner;
1650exports.patchInner = patchInner;
1651exports.patchOuter = patchOuter;
1652exports.skip = skip;
1653exports.skipNode = nextNode;
1654exports.setKeyAttributeName = setKeyAttributeName;
1655exports.clearCache = clearCache;
1656exports.getKey = getKey;
1657exports.importNode = importNode;
1658exports.isDataInitialized = isDataInitialized;
1659exports.notifications = notifications;
1660exports.symbols = symbols;
1661exports.applyAttrs = applyAttrs;
1662exports.applyStatics = applyStatics;
1663exports.attr = attr;
1664exports.elementClose = elementClose;
1665exports.elementOpen = elementOpen;
1666exports.elementOpenEnd = elementOpenEnd;
1667exports.elementOpenStart = elementOpenStart;
1668exports.elementVoid = elementVoid;
1669exports.key = key;
1670exports.text = text$1;
1671//# sourceMappingURL=bundle.cjs.js.map