UNPKG

lit-html

Version:

HTML template literals in JavaScript

108 lines (97 loc) 3.7 kB
/** * @license * Copyright (c) 2017 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 */ import {directive, Directive, NodePart, removeNodes, reparentNodes} from '../lit-html.js'; export type KeyFn<T> = (item: T) => any; export type ItemTemplate<T> = (item: T, index: number) => any; const keyMapCache = new WeakMap<NodePart, Map<any, NodePart>>(); function cleanMap(part: NodePart, key: any, map: Map<any, NodePart>) { if (!part.startNode.parentNode) { map.delete(key); } } export function repeat<T>( items: T[], keyFn: KeyFn<T>, template: ItemTemplate<T>): Directive<NodePart>; export function repeat<T>( items: T[], template: ItemTemplate<T>): Directive<NodePart>; export function repeat<T>( items: Iterable<T>, keyFnOrTemplate: KeyFn<T>|ItemTemplate<T>, template?: ItemTemplate<T>): Directive<NodePart> { let keyFn: KeyFn<T>; if (arguments.length === 2) { template = keyFnOrTemplate; } else if (arguments.length === 3) { keyFn = keyFnOrTemplate as KeyFn<T>; } return directive((part: NodePart): void => { let keyMap = keyMapCache.get(part); if (keyMap === undefined) { keyMap = new Map(); keyMapCache.set(part, keyMap); } const container = part.startNode.parentNode as HTMLElement | ShadowRoot | DocumentFragment; let index = -1; let currentMarker = part.startNode.nextSibling!; for (const item of items) { let result; let key; try { ++index; result = template !(item, index); key = keyFn ? keyFn(item) : index; } catch (e) { console.error(e); continue; } // Try to reuse a part let itemPart = keyMap.get(key); if (itemPart === undefined) { // TODO(justinfagnani): We really want to avoid manual marker creation // here and instead use something like part.insertBeforePart(). This // requires a little refactoring, like iterating through values and // existing parts like NodePart#_setIterable does. We can also remove // keyMapCache and use part._value instead. // But... repeat() is badly in need of rewriting, so we'll do this for // now and revisit soon. const marker = document.createComment(''); const endNode = document.createComment(''); container.insertBefore(marker, currentMarker); container.insertBefore(endNode, currentMarker); itemPart = new NodePart(part.templateFactory); itemPart.insertAfterNode(marker); if (key !== undefined) { keyMap.set(key, itemPart); } } else if (currentMarker !== itemPart.startNode) { // Existing part in the wrong position const end = itemPart.endNode.nextSibling!; if (currentMarker !== end) { reparentNodes(container, itemPart.startNode, end, currentMarker); } } else { // else part is in the correct position already currentMarker = itemPart.endNode.nextSibling!; } itemPart.setValue(result); itemPart.commit(); } // Cleanup if (currentMarker !== part.endNode) { removeNodes(container, currentMarker, part.endNode); keyMap.forEach(cleanMap); } }); }