Socket
Socket
Sign inDemoInstall

lit-html

Package Overview
Dependencies
Maintainers
1
Versions
102
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

lit-html - npm Package Compare versions

Comparing version 0.6.0-pre.3 to 0.6.0

.clang-format

5

lib/lit-extended.d.ts

@@ -14,3 +14,3 @@ /**

*/
import { TemplateResult, AttributePart, TemplateInstance, TemplatePart, Part } from '../lit-html.js';
import { AttributePart, Part, TemplateInstance, TemplatePart, TemplateResult } from '../lit-html.js';
export { html } from '../lit-html.js';

@@ -49,3 +49,4 @@ /**

}
export declare class EventPart extends Part {
export declare class EventPart implements Part {
instance: TemplateInstance;
element: Element;

@@ -52,0 +53,0 @@ eventName: string;

27

lib/lit-extended.js

@@ -14,3 +14,3 @@ /**

*/
import { render as baseRender, defaultPartCallback, AttributePart, Part } from '../lit-html.js';
import { AttributePart, defaultPartCallback, getValue, render as baseRender } from '../lit-html.js';
export { html } from '../lit-html.js';

@@ -68,3 +68,3 @@ /**

// leading and trailing empty strings.
value = this._getValue(values[startIndex]);
value = getValue(this, values[startIndex]);
}

@@ -77,3 +77,3 @@ else {

if (i < s.length - 1) {
value += this._getValue(values[startIndex + i]);
value += getValue(this, values[startIndex + i]);
}

@@ -85,5 +85,5 @@ }

}
export class EventPart extends Part {
export class EventPart {
constructor(instance, element, eventName) {
super(instance);
this.instance = instance;
this.element = element;

@@ -93,14 +93,10 @@ this.eventName = eventName;

setValue(value) {
const listener = this._getValue(value);
if (typeof listener !== 'function') {
console.error('event handlers must be functions', listener);
return;
}
const listener = getValue(this, value);
if (listener === this._listener) {
return;
}
if (listener === undefined) {
if (listener == null) {
this.element.removeEventListener(this.eventName, this);
}
if (this._listener === undefined) {
else if (this._listener == null) {
this.element.addEventListener(this.eventName, this);

@@ -111,5 +107,10 @@ }

handleEvent(event) {
this._listener.call(this.element, event);
if (typeof this._listener === 'function') {
this._listener.call(this.element, event);
}
else if (typeof this._listener.handleEvent === 'function') {
this._listener.handleEvent(event);
}
}
}
//# sourceMappingURL=lit-extended.js.map

@@ -54,3 +54,3 @@ /**

// if there's no keyMap
let itemPart = keyMap == undefined ? oldParts[oldPartsIndex++] : keyMap.get(key);
let itemPart = keyMap === undefined ? oldParts[oldPartsIndex++] : keyMap.get(key);
if (itemPart === undefined) {

@@ -57,0 +57,0 @@ // New part, attach it

@@ -6,3 +6,2 @@ /**

export declare function html(strings: TemplateStringsArray, ...values: any[]): TemplateResult;
export declare function svg(strings: TemplateStringsArray, ...values: any[]): TemplateResult;
/**

@@ -42,7 +41,7 @@ * The return type of `html`, which holds a Template and the values from

type: string;
path: number[];
index: number;
name: string | undefined;
rawName: string | undefined;
strings: string[] | undefined;
constructor(type: string, path: number[], name?: string | undefined, rawName?: string | undefined, strings?: string[] | undefined);
constructor(type: string, index: number, name?: string | undefined, rawName?: string | undefined, strings?: string[] | undefined);
}

@@ -52,16 +51,10 @@ export declare class Template {

element: HTMLTemplateElement;
svg: boolean;
constructor(strings: TemplateStringsArray, svg?: boolean);
/**
* Returns a string of HTML used to create a <template> element.
*/
private _getHtml(strings, svg?);
constructor(strings: TemplateStringsArray);
}
export declare const getValue: (part: Part, value: any) => any;
export declare type DirectiveFn = (part: Part) => any;
export declare const directive: <F extends DirectiveFn>(f: F) => F;
export declare abstract class Part {
export interface Part {
instance: TemplateInstance;
size?: number;
constructor(instance: TemplateInstance);
protected _getValue(value: any): any;
}

@@ -74,3 +67,4 @@ export interface SinglePart extends Part {

}
export declare class AttributePart extends Part implements MultiPart {
export declare class AttributePart implements MultiPart {
instance: TemplateInstance;
element: Element;

@@ -83,3 +77,4 @@ name: string;

}
export declare class NodePart extends Part implements SinglePart {
export declare class NodePart implements SinglePart {
instance: TemplateInstance;
startNode: Node;

@@ -86,0 +81,0 @@ endNode: Node;

@@ -18,3 +18,2 @@ /**

const templates = new Map();
const svgTemplates = new Map();
/**

@@ -32,10 +31,2 @@ * Interprets a template literal as an HTML template that can efficiently

}
export function svg(strings, ...values) {
let template = svgTemplates.get(strings);
if (template === undefined) {
template = new Template(strings, true);
svgTemplates.set(strings, template);
}
return new TemplateResult(template, values);
}
/**

@@ -60,4 +51,3 @@ * The return type of `html`, which holds a Template and the values from

// Repeat render, just call update()
if (instance !== undefined &&
instance.template === result.template &&
if (instance !== undefined && instance.template === result.template &&
instance._partCallback === partCallback) {

@@ -72,9 +62,14 @@ instance.update(result.values);

instance.update(result.values);
while (container.firstChild) {
container.removeChild(container.firstChild);
let child;
while ((child = container.lastChild)) {
container.removeChild(child);
}
container.appendChild(fragment);
}
const exprMarker = '{{}}';
/**
* An expression marker with embedded unique key to avoid
* https://github.com/PolymerLabs/lit-html/issues/62
*/
const exprMarker = `{{lit-${Math.random()}}}`;
/**
* A placeholder for a dynamic expression in an HTML template.

@@ -96,5 +91,5 @@ *

export class TemplatePart {
constructor(type, path, name, rawName, strings) {
constructor(type, index, name, rawName, strings) {
this.type = type;
this.path = path;
this.index = index;
this.name = name;

@@ -106,30 +101,42 @@ this.rawName = rawName;

export class Template {
constructor(strings, svg = false) {
constructor(strings) {
this.parts = [];
this.svg = svg;
this.element = document.createElement('template');
this.element.innerHTML = this._getHtml(strings, this.svg);
this.element.innerHTML = strings.join(exprMarker);
const walker = document.createTreeWalker(this.element.content, 5 /* elements & text */);
let index = -1;
let partIndex = 0;
const nodesToRemove = [];
const attributesToRemove = [];
// The current location in the DOM tree, as an array of child indices
const currentPath = [];
// What expression we're currently handling
let expressionIndex = 0;
/*
* This populates the parts array by traversing the template with a
* recursive DFS, and giving each part a path of indices from the root of
* the template to the target node.
*/
const findParts = (node, index) => {
currentPath.push(index);
let size = 1;
if (node.nodeType === 3 /* TEXT_NODE */) {
const value = node.nodeValue;
const strings = value.split(exprMarker);
while (walker.nextNode()) {
index++;
const node = walker.currentNode;
if (node.nodeType === 1 /* ELEMENT_NODE */) {
if (!node.hasAttributes())
continue;
const attributes = node.attributes;
for (let i = 0; i < attributes.length; i++) {
const attribute = attributes.item(i);
const attributeStrings = attribute.value.split(exprMarker);
if (attributeStrings.length > 1) {
// Get the template literal section leading up to the first
// expression in this attribute attribute
const attributeString = strings[partIndex];
// Trim the trailing literal value if this is an interpolation
const rawNameString = attributeString.substring(0, attributeString.length - attributeStrings[0].length);
// Find the attribute name
const rawName = rawNameString.match(/((?:\w|[.\-_$])+)=["']?$/)[1];
this.parts.push(new TemplatePart('attribute', index, attribute.name, rawName, attributeStrings));
node.removeAttribute(attribute.name);
partIndex += attributeStrings.length - 1;
i--;
}
}
}
else if (node.nodeType === 3 /* TEXT_NODE */) {
const strings = node.nodeValue.split(exprMarker);
if (strings.length > 1) {
const parent = node.parentNode;
size = strings.length;
const lastIndex = size - 1;
const lastIndex = strings.length - 1;
// We have a part for each match found
expressionIndex += lastIndex;
partIndex += lastIndex;
// We keep this current node, but reset its content to the last

@@ -143,78 +150,25 @@ // literal part. We insert new literal nodes before this so that the

parent.insertBefore(new Text(strings[i]), node);
this.parts.push(new TemplatePart('node', currentPath.slice(1)));
// Increment the last index on the stack because we just created a
// new text node
currentPath[currentPath.length - 1] += 1;
this.parts.push(new TemplatePart('node', index++));
}
}
else if (value.trim() === '') {
else if (!node.nodeValue.trim()) {
nodesToRemove.push(node);
size = 0;
index--;
}
}
else {
if (node.nodeType === 1 && node.hasAttributes()) {
const attributes = node.attributes;
for (let i = 0; i < attributes.length; i++) {
const attribute = attributes.item(i);
const value = attribute.value;
// Look for expression markers
const attributeStrings = value.split(exprMarker);
if (attributeStrings.length > 1) {
// Get the template string that preced this attribute expression
const attributeString = strings[expressionIndex];
// Trim the trailing literal part of the attribute value if this
// is an interpolation
const rawNameString = attributeString.substring(0, attributeString.length - attributeStrings[0].length);
// Extract the attribute name
const match = rawNameString.match(/((?:\w|[.\-_$])+)=["']?$/);
const rawName = match[1];
this.parts.push(new TemplatePart('attribute', currentPath.slice(1), attribute.name, rawName, attributeStrings));
attributesToRemove.push(attribute);
expressionIndex += attributeStrings.length - 1;
}
}
}
if (node.hasChildNodes()) {
let child = node.firstChild;
let i = 0;
while (child !== null) {
i += findParts(child, i);
child = child.nextSibling;
}
}
}
currentPath.pop();
return size;
};
findParts(this.element.content, -1);
// Remove empty text nodes and attributes after the walk so as to not
// disturb the traversal
}
// Remove text binding nodes after the walk to not disturb the TreeWalker
for (const n of nodesToRemove) {
n.parentNode.removeChild(n);
}
for (const a of attributesToRemove) {
a.ownerElement.removeAttribute(a.name);
}
}
/**
* Returns a string of HTML used to create a <template> element.
*/
_getHtml(strings, svg) {
const parts = [];
if (svg) {
parts.push('<svg>');
}
for (let i = 0; i < strings.length; i++) {
parts.push(strings[i]);
if (i < strings.length - 1) {
parts.push(exprMarker);
}
}
if (svg) {
parts.push('</svg>');
}
return parts.join('');
}
export const getValue = (part, value) => {
// `null` as the value of a Text node will render the string 'null'
// so we convert it to undefined
if (value != null && value.__litDirective === true) {
value = value(part);
}
}
return value === null ? undefined : value;
};
export const directive = (f) => {

@@ -224,18 +178,5 @@ f.__litDirective = true;

};
export class Part {
constructor(instance) {
export class AttributePart {
constructor(instance, element, name, strings) {
this.instance = instance;
}
_getValue(value) {
// `null` as the value of a Text node will render the string 'null'
// so we convert it to undefined
if (typeof value === 'function' && value.__litDirective === true) {
value = value(this);
}
return value === null ? undefined : value;
}
}
export class AttributePart extends Part {
constructor(instance, element, name, strings) {
super(instance);
this.element = element;

@@ -252,4 +193,5 @@ this.name = name;

if (i < strings.length - 1) {
const v = this._getValue(values[startIndex + i]);
if (v && (Array.isArray(v) || typeof v !== 'string' && v[Symbol.iterator])) {
const v = getValue(this, values[startIndex + i]);
if (v &&
(Array.isArray(v) || typeof v !== 'string' && v[Symbol.iterator])) {
for (const t of v) {

@@ -268,5 +210,5 @@ // TODO: we need to recursively call getValue into iterables...

}
export class NodePart extends Part {
export class NodePart {
constructor(instance, startNode, endNode) {
super(instance);
this.instance = instance;
this.startNode = startNode;

@@ -276,3 +218,3 @@ this.endNode = endNode;

setValue(value) {
value = this._getValue(value);
value = getValue(this, value);
if (value === null ||

@@ -313,4 +255,5 @@ !(typeof value === 'object' || typeof value === 'function')) {

_setText(value) {
if (this.startNode.nextSibling === this.endNode.previousSibling &&
this.startNode.nextSibling.nodeType === Node.TEXT_NODE) {
const node = this.startNode.nextSibling;
if (node === this.endNode.previousSibling &&
node.nodeType === Node.TEXT_NODE) {
// If we only have a single text node between the markers, we can just

@@ -320,3 +263,3 @@ // set its value, rather than replacing it.

// primitive?
this.startNode.nextSibling.textContent = value;
node.textContent = value;
}

@@ -330,7 +273,9 @@ else {

let instance;
if (this._previousValue && this._previousValue.template === value.template) {
if (this._previousValue &&
this._previousValue.template === value.template) {
instance = this._previousValue;
}
else {
instance = new TemplateInstance(value.template, this.instance._partCallback);
instance =
new TemplateInstance(value.template, this.instance._partCallback);
this._setNode(instance._clone());

@@ -347,6 +292,6 @@ this._previousValue = instance;

// array.map((i) => html`${i}`), by reusing existing TemplateInstances.
// If _previousValue is an array, then the previous render was of an iterable
// and _previousValue will contain the NodeParts from the previous render.
// If _previousValue is not an array, clear this part and make a new array
// for NodeParts.
// If _previousValue is an array, then the previous render was of an
// iterable and _previousValue will contain the NodeParts from the previous
// render. If _previousValue is not an array, clear this part and make a new
// array for NodeParts.
if (!Array.isArray(this._previousValue)) {

@@ -383,2 +328,3 @@ this.clear();

this.clear();
this._previousValue = undefined;
}

@@ -400,8 +346,5 @@ else if (partIndex < itemParts.length) {

clear(startNode = this.startNode) {
this._previousValue = undefined;
let node = startNode.nextSibling;
while (node !== null && node !== this.endNode) {
let next = node.nextSibling;
let node;
while ((node = startNode.nextSibling) !== this.endNode) {
node.parentNode.removeChild(node);
node = next;
}

@@ -444,78 +387,20 @@ }

const fragment = document.importNode(this.template.element.content, true);
/*
* This implements a search that traverses the minimum number of Nodes in a
* DOM tree while only using Node.firstChild and Node.nextSibling, which
* have been measured to be faster than accessing Node.childNodes.
*
* For any given path of childNode indices starting from the root of the
* template, we recursively find the parent, then call nextSibling until
* we get to the target index.
*
* Once found, we cache the index and node, so that the next lookup can be
* faster by:
*
* 1. Finding the common ancestor of the previously searched path
* 2. Starting a traversal of children from that common ancestor from the
* last node found, rather than firstChild
*
* This means that any node is only ever visited once.
*
* In order for this to work, paths much be searched for in depth-first
* order.
*
* The overhead of these optimizations probably only matters for larger,
* more complex templates, with enough bindings to speard search costs
* across them, and the benefit will not show up on micro-bencharks with
* small templates. However, it doesn't seem like this slows down
* micro-benchmarks.
*/
let nodeStack = [];
const findNodeAtPath = (path, depth) => {
// Recurse up the tree to find the parent
const parent = (depth === 0) ? fragment : findNodeAtPath(path, depth - 1);
// The target index we're searching for at this depth
const targetIndex = path[depth];
let currentIndex;
let node;
if (nodeStack.length > depth) {
// If we've cached up to this depth, and the index in the stack at this
// depth equals the targetIndex, then just return from the stack.
if (nodeStack[depth][0] === targetIndex) {
return nodeStack[depth][1];
if (this.template.parts.length > 0) {
const walker = document.createTreeWalker(fragment, 5 /* elements & text */);
const parts = this.template.parts;
let index = 0;
let partIndex = 0;
let templatePart = parts[0];
let node = walker.nextNode();
while (node != null && partIndex < parts.length) {
if (index === templatePart.index) {
this._parts.push(this._partCallback(this, templatePart, node));
templatePart = parts[++partIndex];
}
// Otherwise, start the search at the last index we used at this level
[currentIndex, node] = nodeStack[depth];
nodeStack = nodeStack.slice(0, depth);
}
else {
// If the stack didn't have anything at this depth, initialize the search
// to the first child
currentIndex = 0;
node = parent.firstChild;
}
// Perform the traversal
while (node !== null) {
if (currentIndex === targetIndex) {
// When we have a hit, cache it
nodeStack.push([currentIndex, node]);
return node;
else {
index++;
node = walker.nextNode();
}
node = node.nextSibling;
currentIndex++;
}
// This should never happen
return;
};
for (const p of this.template.parts) {
const node = findNodeAtPath(p.path, p.path.length - 1);
this._parts.push(this._partCallback(this, p, node));
}
if (this.template.svg) {
const svgElement = fragment.firstChild;
fragment.removeChild(svgElement);
const nodes = svgElement.childNodes;
for (let i = 0; i < nodes.length; i++) {
fragment.appendChild(nodes.item(i));
}
}
return fragment;

@@ -522,0 +407,0 @@ }

{
"name": "lit-html",
"version": "0.6.0-pre.3",
"version": "0.6.0",
"description": "HTML template literals in JavaScript",

@@ -16,5 +16,7 @@ "license": "BSD-3-Clause",

"pretest": "npm run posttest; ln -s node_modules bower_components",
"test": "wct -l chrome",
"test": "npm run build && wct -l chrome && npm run lint",
"posttest": "rm -f bower_components",
"checksize": "uglifyjs lit-html.js -mc --toplevel | gzip -9 | wc -c"
"checksize": "uglifyjs lit-html.js -mc --toplevel | gzip -9 | wc -c",
"format": "find src test | grep '\\.js$\\|\\.ts$' | xargs clang-format --style=file -i",
"lint": "tslint --project ./"
},

@@ -27,2 +29,3 @@ "author": "The Polymer Authors",

"mocha": "^3.4.2",
"tslint": "^5.7.0",
"typedoc": "^0.8.0",

@@ -29,0 +32,0 @@ "typescript": "^2.4.1",

@@ -15,4 +15,6 @@ /**

import { render as baseRender, defaultPartCallback, TemplateResult, AttributePart, TemplateInstance, TemplatePart, Part } from '../lit-html.js';
export { html } from '../lit-html.js';
import {AttributePart, defaultPartCallback, getValue, Part, render as baseRender, TemplateInstance, TemplatePart, TemplateResult} from '../lit-html.js';
export {html} from '../lit-html.js';
/**

@@ -45,24 +47,31 @@ *

*/
export function render(result: TemplateResult, container: Element|DocumentFragment) {
export function render(
result: TemplateResult, container: Element|DocumentFragment) {
baseRender(result, container, extendedPartCallback);
}
export const extendedPartCallback = (instance: TemplateInstance, templatePart: TemplatePart, node: Node): Part => {
if (templatePart.type === 'attribute') {
if (templatePart.rawName!.startsWith('on-')) {
const eventName = templatePart.rawName!.substring(3);
return new EventPart(instance, node as Element, eventName);
}
if (templatePart.name!.endsWith('$')) {
const name = templatePart.name!.substring(0, templatePart.name!.length - 1);
return new AttributePart(instance, node as Element, name, templatePart.strings!);
}
return new PropertyPart(instance, node as Element, templatePart.rawName!, templatePart.strings!);
}
return defaultPartCallback(instance, templatePart, node);
};
export const extendedPartCallback =
(instance: TemplateInstance, templatePart: TemplatePart, node: Node):
Part => {
if (templatePart.type === 'attribute') {
if (templatePart.rawName!.startsWith('on-')) {
const eventName = templatePart.rawName!.substring(3);
return new EventPart(instance, node as Element, eventName);
}
if (templatePart.name!.endsWith('$')) {
const name = templatePart.name!.substring(
0, templatePart.name!.length - 1);
return new AttributePart(
instance, node as Element, name, templatePart.strings!);
}
return new PropertyPart(
instance,
node as Element,
templatePart.rawName!,
templatePart.strings!);
}
return defaultPartCallback(instance, templatePart, node);
};
export class PropertyPart extends AttributePart {
setValue(values: any[], startIndex: number): void {

@@ -74,3 +83,3 @@ const s = this.strings;

// leading and trailing empty strings.
value = this._getValue(values[startIndex]);
value = getValue(this, values[startIndex]);
} else {

@@ -82,3 +91,3 @@ // Interpolation, so interpolate

if (i < s.length - 1) {
value += this._getValue(values[startIndex + i]);
value += getValue(this, values[startIndex + i]);
}

@@ -91,4 +100,4 @@ }

export class EventPart extends Part {
export class EventPart implements Part {
instance: TemplateInstance;
element: Element;

@@ -99,3 +108,3 @@ eventName: string;

constructor(instance: TemplateInstance, element: Element, eventName: string) {
super(instance);
this.instance = instance;
this.element = element;

@@ -106,14 +115,9 @@ this.eventName = eventName;

setValue(value: any): void {
const listener = this._getValue(value);
if (typeof listener !== 'function') {
console.error('event handlers must be functions', listener);
return;
}
const listener = getValue(this, value);
if (listener === this._listener) {
return;
}
if (listener === undefined) {
if (listener == null) {
this.element.removeEventListener(this.eventName, this);
}
if (this._listener === undefined) {
} else if (this._listener == null) {
this.element.addEventListener(this.eventName, this);

@@ -125,4 +129,8 @@ }

handleEvent(event: Event) {
this._listener.call(this.element, event);
if (typeof this._listener === 'function') {
this._listener.call(this.element, event);
} else if (typeof this._listener.handleEvent === 'function') {
this._listener.handleEvent(event);
}
}
}

@@ -27,6 +27,9 @@ /**

export function repeat<T>(items: T[], keyFn: KeyFn<T>, template: ItemTemplate<T>): DirectiveFn;
export function repeat<T>(
items: T[], keyFn: KeyFn<T>, template: ItemTemplate<T>): DirectiveFn;
export function repeat<T>(items: T[], template: ItemTemplate<T>): DirectiveFn;
export function repeat<T>(items: Iterable<T>, keyFnOrTemplate: KeyFn<T>|ItemTemplate<T>, template?: ItemTemplate<T>): DirectiveFn {
export function repeat<T>(
items: Iterable<T>,
keyFnOrTemplate: KeyFn<T>| ItemTemplate<T>,
template?: ItemTemplate<T>): DirectiveFn {
let keyFn: KeyFn<T>;

@@ -49,5 +52,7 @@ if (arguments.length === 2) {

}
const container = part.startNode.parentNode as HTMLElement|ShadowRoot|DocumentFragment;
const container = part.startNode.parentNode as HTMLElement | ShadowRoot |
DocumentFragment;
const oldParts = state.parts;
const endParts = new Map<Node, NodePart>(oldParts.map((p) => [p.endNode, p] as [Node, NodePart]));
const endParts = new Map<Node, NodePart>(
oldParts.map((p) => [p.endNode, p] as [Node, NodePart]));
const keyMap = state.keyMap;

@@ -64,5 +69,5 @@

try {
result = template!(item, index++);
result = template !(item, index++);
key = keyFn && keyFn(item);
} catch(e) {
} catch (e) {
console.error(e);

@@ -74,3 +79,4 @@ continue;

// if there's no keyMap
let itemPart = keyMap == undefined ? oldParts[oldPartsIndex++] : keyMap.get(key);
let itemPart =
keyMap === undefined ? oldParts[oldPartsIndex++] : keyMap.get(key);

@@ -109,3 +115,4 @@ if (itemPart === undefined) {

itemPart.endNode = new Text();
container.insertBefore(itemPart.endNode, part.startNode.nextSibling);
container.insertBefore(
itemPart.endNode, part.startNode.nextSibling);
} else {

@@ -126,3 +133,3 @@ // endNode should equal the startNode of the currently first part

const contents = range.extractContents();
container.insertBefore(contents, currentMarker);
container.insertBefore(contents, currentMarker);
}

@@ -146,3 +153,3 @@

if (itemParts.length === 0) {
clearRange.setStartBefore(clearStart);
clearRange.setStartBefore(clearStart);
} else {

@@ -153,3 +160,3 @@ clearRange.setStartAfter(clearStart);

clearRange.deleteContents();
clearRange.detach(); // is this neccessary?
clearRange.detach(); // is this neccessary?
}

@@ -156,0 +163,0 @@

@@ -15,7 +15,7 @@ /**

import { directive, NodePart } from '../lit-html.js';
import {directive, NodePart} from '../lit-html.js';
/**
* Renders the result as HTML, rather than text.
*
*
* Note, this is unsafe to use with any user-provided input that hasn't been

@@ -25,7 +25,6 @@ * sanitized or escaped, as it may lead to cross-site-scripting

*/
export const unsafeHTML = (value: any) =>
directive((_part: NodePart) => {
const tmp = document.createElement('template');
tmp.innerHTML = value;
return document.importNode(tmp.content, true);
});
export const unsafeHTML = (value: any) => directive((_part: NodePart) => {
const tmp = document.createElement('template');
tmp.innerHTML = value;
return document.importNode(tmp.content, true);
});

@@ -15,3 +15,3 @@ /**

import { directive, NodePart } from '../lit-html.js';
import {directive, NodePart} from '../lit-html.js';

@@ -22,5 +22,5 @@ /**

export const until = (promise: Promise<any>, defaultContent: any) =>
directive((part: NodePart) => {
part.setValue(defaultContent);
return promise;
});
directive((part: NodePart) => {
part.setValue(defaultContent);
return promise;
});

@@ -19,3 +19,2 @@ /**

const templates = new Map<TemplateStringsArray, Template>();
const svgTemplates = new Map<TemplateStringsArray, Template>();

@@ -26,3 +25,4 @@ /**

*/
export function html(strings: TemplateStringsArray, ...values: any[]): TemplateResult {
export function html(
strings: TemplateStringsArray, ...values: any[]): TemplateResult {
let template = templates.get(strings);

@@ -36,11 +36,2 @@ if (template === undefined) {

export function svg(strings: TemplateStringsArray, ...values: any[]): TemplateResult {
let template = svgTemplates.get(strings);
if (template === undefined) {
template = new Template(strings, true);
svgTemplates.set(strings, template);
}
return new TemplateResult(template, values);
}
/**

@@ -66,3 +57,5 @@ * The return type of `html`, which holds a Template and the values from

*/
export function render(result: TemplateResult, container: Element|DocumentFragment,
export function render(
result: TemplateResult,
container: Element|DocumentFragment,
partCallback: PartCallback = defaultPartCallback) {

@@ -72,4 +65,3 @@ let instance = (container as any).__templateInstance as any;

// Repeat render, just call update()
if (instance !== undefined &&
instance.template === result.template &&
if (instance !== undefined && instance.template === result.template &&
instance._partCallback === partCallback) {

@@ -87,4 +79,5 @@ instance.update(result.values);

while (container.firstChild) {
container.removeChild(container.firstChild);
let child;
while ((child = container.lastChild)) {
container.removeChild(child);
}

@@ -94,3 +87,7 @@ container.appendChild(fragment);

const exprMarker = '{{}}';
/**
* An expression marker with embedded unique key to avoid
* https://github.com/PolymerLabs/lit-html/issues/62
*/
const exprMarker = `{{lit-${Math.random()}}}`;

@@ -115,7 +112,4 @@ /**

constructor(
public type: string,
public path: number[],
public name?: string,
public rawName?: string,
public strings?: string[]) {
public type: string, public index: number, public name?: string,
public rawName?: string, public strings?: string[]) {
}

@@ -127,36 +121,46 @@ }

element: HTMLTemplateElement;
svg: boolean
constructor(strings: TemplateStringsArray, svg: boolean = false) {
this.svg = svg;
constructor(strings: TemplateStringsArray) {
this.element = document.createElement('template');
this.element.innerHTML = this._getHtml(strings, this.svg);
this.element.innerHTML = strings.join(exprMarker);
const walker = document.createTreeWalker(
this.element.content, 5 /* elements & text */);
let index = -1;
let partIndex = 0;
const nodesToRemove = [];
const nodesToRemove: Node[] = [];
const attributesToRemove: Attr[] = [];
// The current location in the DOM tree, as an array of child indices
const currentPath: number[] = [];
// What expression we're currently handling
let expressionIndex = 0;
/*
* This populates the parts array by traversing the template with a
* recursive DFS, and giving each part a path of indices from the root of
* the template to the target node.
*/
const findParts = (node: Node, index: number) => {
currentPath.push(index);
let size = 1;
if (node.nodeType === 3 /* TEXT_NODE */) {
const value = node.nodeValue!;
const strings = value!.split(exprMarker);
while (walker.nextNode()) {
index++;
const node = walker.currentNode as Element;
if (node.nodeType === 1 /* ELEMENT_NODE */) {
if (!node.hasAttributes())
continue;
const attributes = node.attributes;
for (let i = 0; i < attributes.length; i++) {
const attribute = attributes.item(i);
const attributeStrings = attribute.value.split(exprMarker);
if (attributeStrings.length > 1) {
// Get the template literal section leading up to the first
// expression in this attribute attribute
const attributeString = strings[partIndex];
// Trim the trailing literal value if this is an interpolation
const rawNameString = attributeString.substring(
0, attributeString.length - attributeStrings[0].length);
// Find the attribute name
const rawName = rawNameString.match(/((?:\w|[.\-_$])+)=["']?$/)![1];
this.parts.push(new TemplatePart(
'attribute', index, attribute.name, rawName, attributeStrings));
node.removeAttribute(attribute.name);
partIndex += attributeStrings.length - 1;
i--;
}
}
} else if (node.nodeType === 3 /* TEXT_NODE */) {
const strings = node.nodeValue!.split(exprMarker);
if (strings.length > 1) {
const parent = node.parentNode!;
size = strings.length;
const lastIndex = size - 1;
const lastIndex = strings.length - 1;
// We have a part for each match found
expressionIndex += lastIndex;
partIndex += lastIndex;

@@ -172,77 +176,27 @@ // We keep this current node, but reset its content to the last

parent.insertBefore(new Text(strings[i]), node);
this.parts.push(new TemplatePart('node', currentPath.slice(1)));
// Increment the last index on the stack because we just created a
// new text node
currentPath[currentPath.length - 1] += 1;
this.parts.push(new TemplatePart('node', index++));
}
} else if (value.trim() === '') {
} else if (!node.nodeValue!.trim()) {
nodesToRemove.push(node);
size = 0;
index--;
}
} else { /* ELEMENT_NODE or DOCUMENT_FRAGMENT */
if (node.nodeType === 1 && node.hasAttributes()) {
const attributes = node.attributes;
for (let i = 0; i < attributes.length; i++) {
const attribute = attributes.item(i);
const value = attribute.value;
// Look for expression markers
const attributeStrings = value.split(exprMarker);
if (attributeStrings.length > 1) {
// Get the template string that preced this attribute expression
const attributeString = strings[expressionIndex];
// Trim the trailing literal part of the attribute value if this
// is an interpolation
const rawNameString = attributeString.substring(0, attributeString.length - attributeStrings[0].length);
// Extract the attribute name
const match = rawNameString.match(/((?:\w|[.\-_$])+)=["']?$/);
const rawName = match![1];
this.parts.push(new TemplatePart('attribute', currentPath.slice(1), attribute.name, rawName, attributeStrings));
attributesToRemove.push(attribute);
expressionIndex += attributeStrings.length - 1;
}
}
}
if (node.hasChildNodes()) {
let child = node.firstChild;
let i = 0;
while (child !== null) {
i += findParts(child, i);
child = child.nextSibling;
}
}
}
currentPath.pop();
return size;
}
findParts(this.element.content, -1);
// Remove empty text nodes and attributes after the walk so as to not
// disturb the traversal
// Remove text binding nodes after the walk to not disturb the TreeWalker
for (const n of nodesToRemove) {
n.parentNode!.removeChild(n);
}
for (const a of attributesToRemove) {
a.ownerElement.removeAttribute(a.name);
}
}
}
/**
* Returns a string of HTML used to create a <template> element.
*/
private _getHtml(strings: TemplateStringsArray, svg?: boolean): string {
const parts = [];
if (svg) { parts.push('<svg>')}
for (let i = 0; i < strings.length; i++) {
parts.push(strings[i]);
if (i < strings.length - 1) {
parts.push(exprMarker);
}
}
if (svg) { parts.push('</svg>')}
return parts.join('');
export const getValue = (part: Part, value: any) => {
// `null` as the value of a Text node will render the string 'null'
// so we convert it to undefined
if (value != null && value.__litDirective === true) {
value = value(part);
}
return value === null ? undefined : value;
};
}
export type DirectiveFn = (part: Part) => any;

@@ -255,23 +209,12 @@

export abstract class Part {
instance: TemplateInstance
export interface Part {
instance: TemplateInstance;
size?: number;
constructor(instance: TemplateInstance) {
this.instance = instance;
}
protected _getValue(value: any) {
// `null` as the value of a Text node will render the string 'null'
// so we convert it to undefined
if (typeof value === 'function' && value.__litDirective === true) {
value = value(this);
}
return value === null ? undefined : value;
}
// constructor(instance: TemplateInstance) {
// this.instance = instance;
// }
}
export interface SinglePart extends Part {
setValue(value: any): void;
}
export interface SinglePart extends Part { setValue(value: any): void; }

@@ -282,3 +225,4 @@ export interface MultiPart extends Part {

export class AttributePart extends Part implements MultiPart {
export class AttributePart implements MultiPart {
instance: TemplateInstance;
element: Element;

@@ -289,4 +233,6 @@ name: string;

constructor(instance: TemplateInstance, element: Element, name: string, strings: string[]) {
super(instance);
constructor(
instance: TemplateInstance, element: Element, name: string,
strings: string[]) {
this.instance = instance;
this.element = element;

@@ -305,4 +251,5 @@ this.name = name;

if (i < strings.length - 1) {
const v = this._getValue(values[startIndex + i]);
if (v && (Array.isArray(v) || typeof v !== 'string' && v[Symbol.iterator])) {
const v = getValue(this, values[startIndex + i]);
if (v &&
(Array.isArray(v) || typeof v !== 'string' && v[Symbol.iterator])) {
for (const t of v) {

@@ -319,6 +266,6 @@ // TODO: we need to recursively call getValue into iterables...

}
}
export class NodePart extends Part implements SinglePart {
export class NodePart implements SinglePart {
instance: TemplateInstance;
startNode: Node;

@@ -329,3 +276,3 @@ endNode: Node;

constructor(instance: TemplateInstance, startNode: Node, endNode: Node) {
super(instance);
this.instance = instance;
this.startNode = startNode;

@@ -336,3 +283,3 @@ this.endNode = endNode;

setValue(value: any): void {
value = this._getValue(value);
value = getValue(this, value);

@@ -372,4 +319,5 @@ if (value === null ||

private _setText(value: string): void {
if (this.startNode.nextSibling! === this.endNode.previousSibling! &&
this.startNode.nextSibling!.nodeType === Node.TEXT_NODE) {
const node = this.startNode.nextSibling!;
if (node === this.endNode.previousSibling &&
node.nodeType === Node.TEXT_NODE) {
// If we only have a single text node between the markers, we can just

@@ -379,3 +327,3 @@ // set its value, rather than replacing it.

// primitive?
this.startNode.nextSibling!.textContent = value;
node.textContent = value;
} else {

@@ -389,6 +337,8 @@ this._setNode(new Text(value));

let instance: TemplateInstance;
if (this._previousValue && this._previousValue.template === value.template) {
if (this._previousValue &&
this._previousValue.template === value.template) {
instance = this._previousValue;
} else {
instance = new TemplateInstance(value.template, this.instance._partCallback);
instance =
new TemplateInstance(value.template, this.instance._partCallback);
this._setNode(instance._clone());

@@ -407,6 +357,6 @@ this._previousValue = instance;

// If _previousValue is an array, then the previous render was of an iterable
// and _previousValue will contain the NodeParts from the previous render.
// If _previousValue is not an array, clear this part and make a new array
// for NodeParts.
// If _previousValue is an array, then the previous render was of an
// iterable and _previousValue will contain the NodeParts from the previous
// render. If _previousValue is not an array, clear this part and make a new
// array for NodeParts.
if (!Array.isArray(this._previousValue)) {

@@ -445,5 +395,6 @@ this.clear();

}
if (partIndex === 0) {
this.clear();
this._previousValue = undefined;
} else if (partIndex < itemParts.length) {

@@ -466,10 +417,5 @@ const lastPart = itemParts[partIndex - 1];

clear(startNode: Node = this.startNode) {
this._previousValue = undefined;
let node = startNode.nextSibling!;
while (node !== null && node !== this.endNode) {
let next = node.nextSibling!;
let node;
while ((node = startNode.nextSibling!) !== this.endNode) {
node.parentNode!.removeChild(node);
node = next;
}

@@ -479,14 +425,20 @@ }

export type PartCallback =
(instance: TemplateInstance, templatePart: TemplatePart, node: Node) =>
Part;
export type PartCallback = (instance: TemplateInstance, templatePart: TemplatePart, node: Node) => Part;
export const defaultPartCallback =
(instance: TemplateInstance,
templatePart: TemplatePart,
node: Node): Part => {
if (templatePart.type === 'attribute') {
return new AttributePart(
instance, node as Element, templatePart.name!, templatePart.strings!
);
} else if (templatePart.type === 'node') {
return new NodePart(instance, node, node.nextSibling!);
}
throw new Error(`Unknown part type ${templatePart.type}`);
};
export const defaultPartCallback = (instance: TemplateInstance, templatePart: TemplatePart, node: Node): Part => {
if (templatePart.type === 'attribute') {
return new AttributePart(instance, node as Element, templatePart.name!, templatePart.strings!);
} else if (templatePart.type === 'node') {
return new NodePart(instance, node, node.nextSibling!);
}
throw new Error(`Unknown part type ${templatePart.type}`);
}
/**

@@ -501,3 +453,4 @@ * An instance of a `Template` that can be attached to the DOM and updated

constructor(template: Template, partCallback: PartCallback = defaultPartCallback) {
constructor(
template: Template, partCallback: PartCallback = defaultPartCallback) {
this.template = template;

@@ -523,86 +476,23 @@ this._partCallback = partCallback;

/*
* This implements a search that traverses the minimum number of Nodes in a
* DOM tree while only using Node.firstChild and Node.nextSibling, which
* have been measured to be faster than accessing Node.childNodes.
*
* For any given path of childNode indices starting from the root of the
* template, we recursively find the parent, then call nextSibling until
* we get to the target index.
*
* Once found, we cache the index and node, so that the next lookup can be
* faster by:
*
* 1. Finding the common ancestor of the previously searched path
* 2. Starting a traversal of children from that common ancestor from the
* last node found, rather than firstChild
*
* This means that any node is only ever visited once.
*
* In order for this to work, paths much be searched for in depth-first
* order.
*
* The overhead of these optimizations probably only matters for larger,
* more complex templates, with enough bindings to speard search costs
* across them, and the benefit will not show up on micro-bencharks with
* small templates. However, it doesn't seem like this slows down
* micro-benchmarks.
*/
let nodeStack: [number, Node][] = [];
const findNodeAtPath = (path: number[], depth: number): Node|undefined => {
// Recurse up the tree to find the parent
const parent = (depth === 0) ? fragment : findNodeAtPath(path, depth - 1)!;
if (this.template.parts.length > 0) {
const walker =
document.createTreeWalker(fragment, 5 /* elements & text */);
// The target index we're searching for at this depth
const targetIndex = path[depth];
let currentIndex;
let node;
if (nodeStack.length > depth) {
// If we've cached up to this depth, and the index in the stack at this
// depth equals the targetIndex, then just return from the stack.
if (nodeStack[depth][0] === targetIndex) {
return nodeStack[depth][1];
const parts = this.template.parts;
let index = 0;
let partIndex = 0;
let templatePart = parts[0];
let node = walker.nextNode();
while (node != null && partIndex < parts.length) {
if (index === templatePart.index) {
this._parts.push(this._partCallback(this, templatePart, node));
templatePart = parts[++partIndex];
} else {
index++;
node = walker.nextNode();
}
// Otherwise, start the search at the last index we used at this level
[currentIndex, node] = nodeStack[depth]
nodeStack = nodeStack.slice(0, depth);
} else {
// If the stack didn't have anything at this depth, initialize the search
// to the first child
currentIndex = 0;
node = parent.firstChild;
}
// Perform the traversal
while (node !== null) {
if (currentIndex === targetIndex) {
// When we have a hit, cache it
nodeStack.push([currentIndex, node]);
return node;
}
node = node.nextSibling;
currentIndex++;
}
// This should never happen
return;
}
for (const p of this.template.parts) {
const node = findNodeAtPath(p.path, p.path.length - 1)!;
this._parts.push(this._partCallback(this, p, node));
}
if (this.template.svg) {
const svgElement = fragment.firstChild!;
fragment.removeChild(svgElement);
const nodes = svgElement.childNodes;
for (let i = 0; i < nodes.length; i++) {
fragment.appendChild(nodes.item(i));
}
}
return fragment;
}
}

@@ -18,4 +18,4 @@ /**

import {render} from '../../lib/lit-extended.js';
import {html, render as renderPlain} from '../../lit-html.js';
import {render} from '../../lib/lit-extended.js';

@@ -55,42 +55,112 @@ const assert = chai.assert;

test('overwrites an existing (plain) TemplateInstance if one exists, ' +
'even if it has a matching Template', () => {
const container = document.createElement('div');
test(
'overwrites an existing (plain) TemplateInstance if one exists, ' +
'even if it has a matching Template',
() => {
const container = document.createElement('div');
const t = () => html`<div>foo</div>`;
const t = () => html`<div>foo</div>`;
renderPlain(t(), container);
renderPlain(t(), container);
assert.equal(container.children.length, 1);
const firstDiv = container.children[0];
assert.equal(firstDiv.textContent, 'foo');
assert.equal(container.children.length, 1);
const firstDiv = container.children[0];
assert.equal(firstDiv.textContent, 'foo');
render(t(), container);
render(t(), container);
assert.equal(container.children.length, 1);
const secondDiv = container.children[0];
assert.equal(secondDiv.textContent, 'foo');
assert.equal(container.children.length, 1);
const secondDiv = container.children[0];
assert.equal(secondDiv.textContent, 'foo');
assert.notEqual(firstDiv, secondDiv);
});
assert.notEqual(firstDiv, secondDiv);
});
test('overwrites an existing ExtendedTemplateInstance if one exists and ' +
'does not have a matching Template', () => {
const container = document.createElement('div');
test(
'overwrites an existing ExtendedTemplateInstance if one exists and ' +
'does not have a matching Template',
() => {
const container = document.createElement('div');
render(html`<div>foo</div>`, container);
render(html`<div>foo</div>`, container);
assert.equal(container.children.length, 1);
const fooDiv = container.children[0];
assert.equal(fooDiv.textContent, 'foo');
assert.equal(container.children.length, 1);
const fooDiv = container.children[0];
assert.equal(fooDiv.textContent, 'foo');
render(html`<div>bar</div>`, container);
render(html`<div>bar</div>`, container);
assert.equal(container.children.length, 1);
const barDiv = container.children[0];
assert.equal(barDiv.textContent, 'bar');
assert.equal(container.children.length, 1);
const barDiv = container.children[0];
assert.equal(barDiv.textContent, 'bar');
assert.notEqual(fooDiv, barDiv);
});
assert.notEqual(fooDiv, barDiv);
});
test('adds event listener functions, calls with right this value', () => {
const container = document.createElement('div');
let thisValue;
let event;
const listener = function(this: any, e: any) {
event = e;
thisValue = this;
};
render(html`<div on-click=${listener}></div>`, container);
const div = container.firstChild as HTMLElement;
div.click();
assert.equal(thisValue, div);
assert.instanceOf(event, MouseEvent);
});
test('adds event listener objects, calls with right this value', () => {
const container = document.createElement('div');
let thisValue;
let event;
const listener = {
handleEvent(e: Event) {
event = e;
thisValue = this;
}
};
render(html`<div on-click=${listener}></div>`, container);
const div = container.firstChild as HTMLElement;
div.click();
assert.equal(thisValue, listener);
});
test('only adds event listeners once', () => {
const container = document.createElement('div');
let count = 0;
const listener = () => {
count++;
};
const go = () =>
render(html`<div on-click=${listener}></div>`, container);
go();
go();
go();
const div = container.firstChild as HTMLElement;
div.click();
assert.equal(count, 1);
});
test('removes event listeners', () => {
const container = document.createElement('div');
let target;
let listener: any = (e: any) => target = e.target;
const t = () => html`<div on-click=${listener}></div>`;
render(t(), container);
const div = container.firstChild as HTMLElement;
div.click();
assert.equal(target, div);
listener = null;
target = undefined;
render(t(), container);
div.click();
assert.equal(target, undefined);
});
});
});

@@ -18,4 +18,4 @@ /**

import {repeat} from '../../lib/repeat.js';
import {html, render} from '../../lit-html.js';
import {repeat} from '../../lib/repeat.js';

@@ -35,6 +35,9 @@ const assert = chai.assert;

test('renders a list', () => {
const r = html`${repeat([1, 2, 3], (i) => i, (i: number) => html`
const r =
html`${repeat([1, 2, 3], (i) => i, (i: number) => html`
<li>item: ${i}</li>`)}`;
render(r, container);
assert.equal(container.innerHTML, `<li>item: 1</li><li>item: 2</li><li>item: 3</li>`);
assert.equal(
container.innerHTML,
`<li>item: 1</li><li>item: 2</li><li>item: 3</li>`);
});

@@ -44,6 +47,9 @@

let items = [1, 2, 3];
const t = () => html`${repeat(items, (i) => i, (i: number) => html`
const t = () =>
html`${repeat(items, (i) => i, (i: number) => html`
<li>item: ${i}</li>`)}`;
render(t(), container);
assert.equal(container.innerHTML, `<li>item: 1</li><li>item: 2</li><li>item: 3</li>`);
assert.equal(
container.innerHTML,
`<li>item: 1</li><li>item: 2</li><li>item: 3</li>`);
const children1 = Array.from(container.querySelectorAll('li'));

@@ -53,3 +59,5 @@

render(t(), container);
assert.equal(container.innerHTML, `<li>item: 3</li><li>item: 2</li><li>item: 1</li>`);
assert.equal(
container.innerHTML,
`<li>item: 3</li><li>item: 2</li><li>item: 1</li>`);
const children2 = Array.from(container.querySelectorAll('li'));

@@ -63,3 +71,4 @@ assert.strictEqual(children1[0], children2[2]);

let items = [1, 2, 3];
const t = () => html`${repeat(items, (i) => i, (i: number) => html`
const t = () =>
html`${repeat(items, (i) => i, (i: number) => html`
<li>item: ${i}</li>`)}`;

@@ -70,3 +79,5 @@

render(t(), container);
assert.equal(container.innerHTML, `<li>item: 0</li><li>item: 1</li><li>item: 2</li><li>item: 3</li>`);
assert.equal(
container.innerHTML,
`<li>item: 0</li><li>item: 1</li><li>item: 2</li><li>item: 3</li>`);
});

@@ -76,3 +87,4 @@

let items = [1, 2, 3];
const t = () => html`${repeat(items, (i) => i, (i: number) => html`
const t = () =>
html`${repeat(items, (i) => i, (i: number) => html`
<li>item: ${i}</li>`)}`;

@@ -83,3 +95,5 @@

render(t(), container);
assert.equal(container.innerHTML, `<li>item: 1</li><li>item: 2</li><li>item: 3</li><li>item: 4</li>`);
assert.equal(
container.innerHTML,
`<li>item: 1</li><li>item: 2</li><li>item: 3</li><li>item: 4</li>`);
});

@@ -89,3 +103,4 @@

let items = [1, 2, 3];
const t = () => html`${repeat(items, (i) => i, (i: number) => html`
const t = () =>
html`${repeat(items, (i) => i, (i: number) => html`
<li>item: ${i}</li>`)}`;

@@ -101,3 +116,4 @@

let items = [1, 2, 3];
const t = () => html`${repeat(items, (i) => i, (i: number) => html`
const t = () =>
html`${repeat(items, (i) => i, (i: number) => html`
<li>item: ${i}</li>`)}`;

@@ -118,3 +134,4 @@

let items = [1, 2, 3];
const t = () => html`${repeat(items, (i) => i, (i: number) => html`
const t = () =>
html`${repeat(items, (i) => i, (i: number) => html`
<li>item: ${i}</li>`)}`;

@@ -135,6 +152,7 @@

let items = [1, 2, 3];
const t = () => html`${repeat(items, (i) => i, (i: number) => html`
const t = () =>
html`${repeat(items, (i) => i, (i: number) => html`
<li>item: ${i}</li>`)}`;
render(t(), container);
render(t(), container);
const children1 = Array.from(container.querySelectorAll('li'));

@@ -158,3 +176,5 @@

render(r, container);
assert.equal(container.innerHTML, `<li>item: 1</li><li>item: 2</li><li>item: 3</li>`);
assert.equal(
container.innerHTML,
`<li>item: 1</li><li>item: 2</li><li>item: 3</li>`);
});

@@ -167,7 +187,11 @@

render(t(), container);
assert.equal(container.innerHTML, `<li>item: 1</li><li>item: 2</li><li>item: 3</li>`);
assert.equal(
container.innerHTML,
`<li>item: 1</li><li>item: 2</li><li>item: 3</li>`);
items = [3, 2, 1];
render(t(), container);
assert.equal(container.innerHTML, `<li>item: 3</li><li>item: 2</li><li>item: 1</li>`);
assert.equal(
container.innerHTML,
`<li>item: 3</li><li>item: 2</li><li>item: 1</li>`);
});

@@ -174,0 +198,0 @@

@@ -27,6 +27,9 @@ /**

const container = document.createElement('div');
render(html`<div>before${unsafeHTML('<span>inner</span>after</div>')}`, container);
assert.equal(container.innerHTML, '<div>before<span>inner</span>after</div>');
render(
html`<div>before${unsafeHTML('<span>inner</span>after</div>')}`,
container);
assert.equal(
container.innerHTML, '<div>before<span>inner</span>after</div>');
});
});

@@ -28,4 +28,8 @@ /**

let resolve: (v: any) => void;
const promise = new Promise((res, _) => {resolve = res;});
render(html`<div>${until(promise, html`<span>loading...</span>`)}</div>`, container);
const promise = new Promise((res, _) => {
resolve = res;
});
render(
html`<div>${until(promise, html`<span>loading...</span>`)}</div>`,
container);
assert.equal(container.innerHTML, '<div><span>loading...</span></div>');

@@ -35,5 +39,5 @@ resolve!('foo');

await new Promise((r) => setTimeout(() => r()));
assert.equal(container.innerHTML, '<div>foo</div>');
assert.equal(container.innerHTML, '<div>foo</div>');
});
});

@@ -18,3 +18,3 @@ /**

import {html, render, defaultPartCallback, TemplateResult, TemplatePart, TemplateInstance, NodePart, Part, AttributePart } from '../lit-html.js';
import {AttributePart, defaultPartCallback, html, NodePart, Part, render, TemplateInstance, TemplatePart, TemplateResult} from '../lit-html.js';

@@ -42,6 +42,10 @@ const assert = chai.assert;

test('does not create extra empty text nodes', () => {
const countNodes = (result: TemplateResult, getNodes: (f: DocumentFragment) => NodeList) =>
getNodes(result.template.element.content).length;
const countNodes =
(result: TemplateResult,
getNodes: (f: DocumentFragment) => NodeList) =>
getNodes(result.template.element.content).length;
assert.equal(countNodes(html`<div>${0}</div>`, (c) => c.childNodes[0].childNodes), 2);
assert.equal(
countNodes(html`<div>${0}</div>`, (c) => c.childNodes[0].childNodes),
2);
assert.equal(countNodes(html`${0}`, (c) => c.childNodes), 2);

@@ -57,2 +61,11 @@ assert.equal(countNodes(html`a${0}`, (c) => c.childNodes), 2);

test('escapes marker sequences in text nodes', () => {
const container = document.createElement('div');
const result = html`{{}}`;
assert.equal(result.template.parts.length, 0);
render(result, container);
console.log(container.innerHTML);
assert.equal(container.innerHTML, '{{}}');
});
test('parses parts for multiple expressions', () => {

@@ -62,7 +75,7 @@ const result = html`

<p>${2}</p>
${3}x${4}
<span a="${5}x${6}">${7}x${8}</span>
${3}
<span a="${4}">${5}</span>
</div>`;
const parts = result.template.parts;
assert.equal(parts.length, 7);
assert.equal(parts.length, 5);
});

@@ -82,4 +95,7 @@

const rawNames = parts.map((p: TemplatePart) => p.rawName);
assert.deepEqual(names, ['someprop', 'a-nother', 'multiparts', undefined, 'athing']);
assert.deepEqual(rawNames, ['someProp', 'a-nother', 'multiParts', undefined, 'aThing']);
assert.deepEqual(
names, ['someprop', 'a-nother', 'multiparts', undefined, 'athing']);
assert.deepEqual(
rawNames,
['someProp', 'a-nother', 'multiParts', undefined, 'aThing']);
});

@@ -99,7 +115,8 @@

const ul = (list: string[]) => {
var items = list.map(item => html`<li>${item}</li>`);
const items = list.map((item) => html`<li>${item}</li>`);
return html`<ul>${items}</ul>`;
};
render(ul(['a', 'b', 'c']), container);
assert.equal(container.innerHTML, '<ul><li>a</li><li>b</li><li>c</li></ul>');
assert.equal(
container.innerHTML, '<ul><li>a</li><li>b</li><li>c</li></ul>');
render(ul(['x', 'y']), container);

@@ -112,8 +129,9 @@ assert.equal(container.innerHTML, '<ul><li>x</li><li>y</li></ul>');

assert(result.template.element.innerHTML, '<div></div>');
})
});
test('resists XSS attempt in attribute values', () => {
const result = html`<div foo="${'"><script>alert("boo");</script><div foo="'}"></div>`;
const result = html
`<div foo="${'"><script>alert("boo");</script><div foo="'}"></div>`;
assert(result.template.element.innerHTML, '<div></div>');
})
});

@@ -153,3 +171,3 @@ });

const container = document.createElement('div');
render(html`<div>${(_:any)=>123}</div>`, container);
render(html`<div>${(_: any) => 123}</div>`, container);
assert.equal(container.innerHTML, '<div>(_) =&gt; 123</div>');

@@ -160,3 +178,3 @@ });

const container = document.createElement('div');
render(html`<div>${[1,2,3]}</div>`, container);
render(html`<div>${[1, 2, 3]}</div>`, container);
assert.equal(container.innerHTML, '<div>123</div>');

@@ -183,3 +201,4 @@ });

// html`${partial}${'bar'}${partial}${'baz'}qux`, container);
// assert.equal(container.innerHTML, '<h1>foo</h1>bar<h1>foo</h1>bazqux');
// assert.equal(container.innerHTML,
// '<h1>foo</h1>bar<h1>foo</h1>bazqux');
// });

@@ -189,3 +208,3 @@

const container = document.createElement('div');
render(html`<div>${[1,2,3].map((i)=>html`${i}`)}</div>`, container);
render(html`<div>${[1, 2, 3].map((i) => html`${i}`)}</div>`, container);
assert.equal(container.innerHTML, '<div>123</div>');

@@ -209,3 +228,4 @@ });

render(html`<div>${children}</div>`, container);
assert.equal(container.innerHTML, '<div><p></p><a></a><span></span></div>');
assert.equal(
container.innerHTML, '<div><p></p><a></a><span></span></div>');
});

@@ -234,3 +254,3 @@

const container = document.createElement('div');
render(html`<div foo=${(_:any)=>123}></div>`, container);
render(html`<div foo=${(_: any) => 123}></div>`, container);
assert.equal(container.innerHTML, '<div foo="(_) => 123"></div>');

@@ -241,3 +261,3 @@ });

const container = document.createElement('div');
render(html`<div foo=${[1,2,3]}></div>`, container);
render(html`<div foo=${[1, 2, 3]}></div>`, container);
assert.equal(container.innerHTML, '<div foo="123"></div>');

@@ -255,3 +275,4 @@ });

render(html`<div>${'baz'}</div><div foo="${'bar'}"></div>`, container);
assert.equal(container.innerHTML, '<div>baz</div><div foo="bar"></div>');
assert.equal(
container.innerHTML, '<div>baz</div><div foo="bar"></div>');
});

@@ -262,3 +283,5 @@

let resolve: (v: any) => void;
const promise = new Promise((res, _) => {resolve = res;});
const promise = new Promise((res, _) => {
resolve = res;
});
render(html`<div>${promise}</div>`, container);

@@ -268,3 +291,3 @@ assert.equal(container.innerHTML, '<div></div>');

await promise;
assert.equal(container.innerHTML, '<div>foo</div>');
assert.equal(container.innerHTML, '<div>foo</div>');
});

@@ -275,5 +298,9 @@

let resolve1: (v: any) => void;
const promise1 = new Promise((res, _) => {resolve1 = res;});
const promise1 = new Promise((res, _) => {
resolve1 = res;
});
let resolve2: (v: any) => void;
const promise2 = new Promise((res, _) => {resolve2 = res;});
const promise2 = new Promise((res, _) => {
resolve2 = res;
});

@@ -322,3 +349,3 @@ let promise = promise1;

const container = document.createElement('div');
let foo = 'aaa';
const foo = 'aaa';

@@ -370,3 +397,3 @@ const t = () => html`<div>${foo}</div>`;

let foo = 'foo';
let bar = 'bar';
const bar = 'bar';

@@ -386,3 +413,3 @@ const t = () => html`<div>${foo}${bar}</div>`;

let foo = 'foo';
let bar = 'bar';
const bar = 'bar';

@@ -402,4 +429,4 @@ const t = () => html`<div a="${foo}:${bar}"></div>`;

let foo = 'foo';
let bar = 'bar';
let baz = 'baz';
const bar = 'bar';
const baz = 'baz';

@@ -415,3 +442,3 @@ const t = (x: boolean) => {

return html`${partial}${baz}`;
}
};

@@ -464,5 +491,6 @@ render(t(true), container);

];
const t = () => html`<div>${children}</div>`
const t = () => html`<div>${children}</div>`;
render(t(), container);
assert.equal(container.innerHTML, '<div><p></p><a></a><span></span></div>');
assert.equal(
container.innerHTML, '<div><p></p><a></a><span></span></div>');

@@ -478,20 +506,22 @@ children = null;

test('overwrites an existing TemplateInstance if one exists and does ' +
'not have a matching Template', () => {
const container = document.createElement('div');
test(
'overwrites an existing TemplateInstance if one exists and does ' +
'not have a matching Template',
() => {
const container = document.createElement('div');
render(html`<div>foo</div>`, container);
render(html`<div>foo</div>`, container);
assert.equal(container.children.length, 1);
const fooDiv = container.children[0];
assert.equal(fooDiv.textContent, 'foo');
assert.equal(container.children.length, 1);
const fooDiv = container.children[0];
assert.equal(fooDiv.textContent, 'foo');
render(html`<div>bar</div>`, container);
render(html`<div>bar</div>`, container);
assert.equal(container.children.length, 1);
const barDiv = container.children[0];
assert.equal(barDiv.textContent, 'bar');
assert.equal(container.children.length, 1);
const barDiv = container.children[0];
assert.equal(barDiv.textContent, 'bar');
assert.notEqual(fooDiv, barDiv);
});
assert.notEqual(fooDiv, barDiv);
});

@@ -502,6 +532,5 @@ });

// These tests demonstrate how a flavored layer on top of lit-html could
// modify the parsed Template to implement different behavior, like setting
// properties instead of attributes.
// modify the parsed Template to implement different behavior, like
// setting properties instead of attributes.

@@ -512,11 +541,16 @@ // Note that because the template parse phase captures the pre-parsed

const partCallback = (instance: TemplateInstance, templatePart: TemplatePart, node: Node): Part => {
if (templatePart.type === 'attribute') {
return new PropertyPart(instance, node as Element, templatePart.rawName!, templatePart.strings!);
}
return defaultPartCallback(instance, templatePart, node);
};
const partCallback =
(instance: TemplateInstance, templatePart: TemplatePart, node: Node):
Part => {
if (templatePart.type === 'attribute') {
return new PropertyPart(
instance,
node as Element,
templatePart.rawName!,
templatePart.strings!);
}
return defaultPartCallback(instance, templatePart, node);
};
class PropertyPart extends AttributePart {
setValue(values: any[]): void {

@@ -602,3 +636,3 @@ const s = this.strings;

test('accepts a function', () => {
part.setValue((_:any)=>123);
part.setValue((_: any) => 123);
assert.equal(container.innerHTML, '(_) =&gt; 123');

@@ -613,3 +647,3 @@ });

test('accepts arrays', () => {
part.setValue([1,2,3]);
part.setValue([1, 2, 3]);
assert.equal(container.innerHTML, '123');

@@ -628,5 +662,7 @@ assert.strictEqual(container.firstChild, startNode);

test('accepts nested arrays', () => {
part.setValue([1,[2],3]);
part.setValue([1, [2], 3]);
assert.equal(container.innerHTML, '123');
assert.deepEqual(['', '1', '', '2', '', '3', ''], Array.from(container.childNodes).map((n) => n.nodeValue));
assert.deepEqual(
['', '1', '', '2', '', '3', ''],
Array.from(container.childNodes).map((n) => n.nodeValue));
assert.strictEqual(container.firstChild, startNode);

@@ -642,3 +678,3 @@ assert.strictEqual(container.lastChild, endNode);

test('accepts arrays of nested templates', () => {
part.setValue([1,2,3].map((i)=>html`${i}`));
part.setValue([1, 2, 3].map((i) => html`${i}`));
assert.equal(container.innerHTML, '123');

@@ -689,3 +725,5 @@ });

assert.equal(container.innerHTML, '123');
assert.deepEqual(['', '1', '', '2', '', '3', ''], Array.from(container.childNodes).map((n) => n.nodeValue));
assert.deepEqual(
['', '1', '', '2', '', '3', ''],
Array.from(container.childNodes).map((n) => n.nodeValue));
assert.strictEqual(container.firstChild, startNode);

@@ -696,3 +734,4 @@ assert.strictEqual(container.lastChild, endNode);

assert.equal(container.innerHTML, '');
assert.deepEqual(['', ''], Array.from(container.childNodes).map((n) => n.nodeValue));
assert.deepEqual(
['', ''], Array.from(container.childNodes).map((n) => n.nodeValue));
assert.strictEqual(container.firstChild, startNode);

@@ -705,3 +744,5 @@ assert.strictEqual(container.lastChild, endNode);

assert.equal(container.innerHTML, '123');
assert.deepEqual(['', '1', '', '2', '', '3', ''], Array.from(container.childNodes).map((n) => n.nodeValue));
assert.deepEqual(
['', '1', '', '2', '', '3', ''],
Array.from(container.childNodes).map((n) => n.nodeValue));
assert.strictEqual(container.firstChild, startNode);

@@ -712,3 +753,5 @@ assert.strictEqual(container.lastChild, endNode);

assert.equal(container.innerHTML, '45');
assert.deepEqual(['', '4', '', '5', ''], Array.from(container.childNodes).map((n) => n.nodeValue));
assert.deepEqual(
['', '4', '', '5', ''],
Array.from(container.childNodes).map((n) => n.nodeValue));
assert.strictEqual(container.firstChild, startNode);

@@ -719,3 +762,4 @@ assert.strictEqual(container.lastChild, endNode);

assert.equal(container.innerHTML, '');
assert.deepEqual(['', ''], Array.from(container.childNodes).map((n) => n.nodeValue));
assert.deepEqual(
['', ''], Array.from(container.childNodes).map((n) => n.nodeValue));
assert.strictEqual(container.firstChild, startNode);

@@ -726,3 +770,5 @@ assert.strictEqual(container.lastChild, endNode);

assert.equal(container.innerHTML, '45');
assert.deepEqual(['', '4', '', '5', ''], Array.from(container.childNodes).map((n) => n.nodeValue));
assert.deepEqual(
['', '4', '', '5', ''],
Array.from(container.childNodes).map((n) => n.nodeValue));
assert.strictEqual(container.firstChild, startNode);

@@ -733,11 +779,15 @@ assert.strictEqual(container.lastChild, endNode);

test('updates nested arrays', () => {
part.setValue([1,[2],3]);
part.setValue([1, [2], 3]);
assert.equal(container.innerHTML, '123');
assert.deepEqual(['', '1', '', '2', '', '3', ''], Array.from(container.childNodes).map((n) => n.nodeValue));
assert.deepEqual(
['', '1', '', '2', '', '3', ''],
Array.from(container.childNodes).map((n) => n.nodeValue));
assert.strictEqual(container.firstChild, startNode);
assert.strictEqual(container.lastChild, endNode);
part.setValue([[1],2,3]);
part.setValue([[1], 2, 3]);
assert.equal(container.innerHTML, '123');
assert.deepEqual(['', '1', '', '2', '', '3', ''], Array.from(container.childNodes).map((n) => n.nodeValue));
assert.deepEqual(
['', '1', '', '2', '', '3', ''],
Array.from(container.childNodes).map((n) => n.nodeValue));
assert.strictEqual(container.firstChild, startNode);

@@ -759,29 +809,33 @@ assert.strictEqual(container.lastChild, endNode);

test('updates are stable when called multiple times with templates', () => {
let value = 'foo';
const r = () => html`<h1>${value}</h1>`;
part.setValue(r());
assert.equal(container.innerHTML, '<h1>foo</h1>');
const originalH1 = container.querySelector('h1');
test(
'updates are stable when called multiple times with templates',
() => {
let value = 'foo';
const r = () => html`<h1>${value}</h1>`;
part.setValue(r());
assert.equal(container.innerHTML, '<h1>foo</h1>');
const originalH1 = container.querySelector('h1');
value = 'bar';
part.setValue(r());
assert.equal(container.innerHTML, '<h1>bar</h1>');
const newH1 = container.querySelector('h1');
assert.strictEqual(newH1, originalH1);
});
value = 'bar';
part.setValue(r());
assert.equal(container.innerHTML, '<h1>bar</h1>');
const newH1 = container.querySelector('h1');
assert.strictEqual(newH1, originalH1);
});
test('updates are stable when called multiple times with arrays of templates', () => {
let items = [1, 2, 3];
const r = () => items.map((i)=>html`<li>${i}</li>`);
part.setValue(r());
assert.equal(container.innerHTML, '<li>1</li><li>2</li><li>3</li>');
const originalLIs = Array.from(container.querySelectorAll('li'));
test(
'updates are stable when called multiple times with arrays of templates',
() => {
let items = [1, 2, 3];
const r = () => items.map((i) => html`<li>${i}</li>`);
part.setValue(r());
assert.equal(container.innerHTML, '<li>1</li><li>2</li><li>3</li>');
const originalLIs = Array.from(container.querySelectorAll('li'));
items = [3, 2, 1];
part.setValue(r());
assert.equal(container.innerHTML, '<li>3</li><li>2</li><li>1</li>');
const newLIs = Array.from(container.querySelectorAll('li'));
assert.deepEqual(newLIs, originalLIs);
});
items = [3, 2, 1];
part.setValue(r());
assert.equal(container.innerHTML, '<li>3</li><li>2</li><li>1</li>');
const newLIs = Array.from(container.querySelectorAll('li'));
assert.deepEqual(newLIs, originalLIs);
});

@@ -794,3 +848,4 @@ });

part.clear();
assert.deepEqual(Array.from(container.childNodes), [startNode, endNode]);
assert.deepEqual(
Array.from(container.childNodes), [startNode, endNode]);
});

@@ -801,3 +856,4 @@

part.clear();
assert.deepEqual(Array.from(container.childNodes), [startNode, endNode]);
assert.deepEqual(
Array.from(container.childNodes), [startNode, endNode]);
});

@@ -804,0 +860,0 @@

@@ -16,4 +16,4 @@ /**

/// <reference path="../../../node_modules/@types/chai/index.d.ts" />
import { render } from '../../lib/lit-extended.js';
import { html, render as renderPlain } from '../../lit-html.js';
import { render } from '../../lib/lit-extended.js';
const assert = chai.assert;

@@ -69,4 +69,62 @@ suite('lit-extended', () => {

});
test('adds event listener functions, calls with right this value', () => {
const container = document.createElement('div');
let thisValue;
let event;
const listener = function (e) {
event = e;
thisValue = this;
};
render(html `<div on-click=${listener}></div>`, container);
const div = container.firstChild;
div.click();
assert.equal(thisValue, div);
assert.instanceOf(event, MouseEvent);
});
test('adds event listener objects, calls with right this value', () => {
const container = document.createElement('div');
let thisValue;
let event;
const listener = {
handleEvent(e) {
event = e;
thisValue = this;
}
};
render(html `<div on-click=${listener}></div>`, container);
const div = container.firstChild;
div.click();
assert.equal(thisValue, listener);
});
test('only adds event listeners once', () => {
const container = document.createElement('div');
let count = 0;
const listener = () => {
count++;
};
const go = () => render(html `<div on-click=${listener}></div>`, container);
go();
go();
go();
const div = container.firstChild;
div.click();
assert.equal(count, 1);
});
test('removes event listeners', () => {
const container = document.createElement('div');
let target;
let listener = (e) => target = e.target;
const t = () => html `<div on-click=${listener}></div>`;
render(t(), container);
const div = container.firstChild;
div.click();
assert.equal(target, div);
listener = null;
target = undefined;
render(t(), container);
div.click();
assert.equal(target, undefined);
});
});
});
//# sourceMappingURL=lit-extended_test.js.map

@@ -16,4 +16,4 @@ /**

/// <reference path="../../../node_modules/@types/chai/index.d.ts" />
import { repeat } from '../../lib/repeat.js';
import { html, render } from '../../lit-html.js';
import { repeat } from '../../lib/repeat.js';
const assert = chai.assert;

@@ -20,0 +20,0 @@ suite('repeat', () => {

@@ -23,3 +23,5 @@ /**

let resolve;
const promise = new Promise((res, _) => { resolve = res; });
const promise = new Promise((res, _) => {
resolve = res;
});
render(html `<div>${until(promise, html `<span>loading...</span>`)}</div>`, container);

@@ -26,0 +28,0 @@ assert.equal(container.innerHTML, '<div><span>loading...</span></div>');

@@ -16,3 +16,3 @@ /**

/// <reference path="../../node_modules/@types/chai/index.d.ts" />
import { html, render, defaultPartCallback, TemplateResult, TemplateInstance, NodePart, AttributePart } from '../lit-html.js';
import { AttributePart, defaultPartCallback, html, NodePart, render, TemplateInstance, TemplateResult } from '../lit-html.js';
const assert = chai.assert;

@@ -44,2 +44,10 @@ suite('lit-html', () => {

});
test('escapes marker sequences in text nodes', () => {
const container = document.createElement('div');
const result = html `{{}}`;
assert.equal(result.template.parts.length, 0);
render(result, container);
console.log(container.innerHTML);
assert.equal(container.innerHTML, '{{}}');
});
test('parses parts for multiple expressions', () => {

@@ -49,7 +57,7 @@ const result = html `

<p>${2}</p>
${3}x${4}
<span a="${5}x${6}">${7}x${8}</span>
${3}
<span a="${4}">${5}</span>
</div>`;
const parts = result.template.parts;
assert.equal(parts.length, 7);
assert.equal(parts.length, 5);
});

@@ -82,3 +90,3 @@ test('stores raw names of attributes', () => {

const ul = (list) => {
var items = list.map(item => html `<li>${item}</li>`);
const items = list.map((item) => html `<li>${item}</li>`);
return html `<ul>${items}</ul>`;

@@ -149,3 +157,4 @@ };

// html`${partial}${'bar'}${partial}${'baz'}qux`, container);
// assert.equal(container.innerHTML, '<h1>foo</h1>bar<h1>foo</h1>bazqux');
// assert.equal(container.innerHTML,
// '<h1>foo</h1>bar<h1>foo</h1>bazqux');
// });

@@ -212,3 +221,5 @@ test('renders arrays of nested templates', () => {

let resolve;
const promise = new Promise((res, _) => { resolve = res; });
const promise = new Promise((res, _) => {
resolve = res;
});
render(html `<div>${promise}</div>`, container);

@@ -223,5 +234,9 @@ assert.equal(container.innerHTML, '<div></div>');

let resolve1;
const promise1 = new Promise((res, _) => { resolve1 = res; });
const promise1 = new Promise((res, _) => {
resolve1 = res;
});
let resolve2;
const promise2 = new Promise((res, _) => { resolve2 = res; });
const promise2 = new Promise((res, _) => {
resolve2 = res;
});
let promise = promise1;

@@ -260,3 +275,3 @@ const t = () => html `<div>${promise}</div>`;

const container = document.createElement('div');
let foo = 'aaa';
const foo = 'aaa';
const t = () => html `<div>${foo}</div>`;

@@ -298,3 +313,3 @@ render(t(), container);

let foo = 'foo';
let bar = 'bar';
const bar = 'bar';
const t = () => html `<div>${foo}${bar}</div>`;

@@ -310,3 +325,3 @@ render(t(), container);

let foo = 'foo';
let bar = 'bar';
const bar = 'bar';
const t = () => html `<div a="${foo}:${bar}"></div>`;

@@ -322,4 +337,4 @@ render(t(), container);

let foo = 'foo';
let bar = 'bar';
let baz = 'baz';
const bar = 'bar';
const baz = 'baz';
const t = (x) => {

@@ -399,4 +414,4 @@ let partial;

// These tests demonstrate how a flavored layer on top of lit-html could
// modify the parsed Template to implement different behavior, like setting
// properties instead of attributes.
// modify the parsed Template to implement different behavior, like
// setting properties instead of attributes.
// Note that because the template parse phase captures the pre-parsed

@@ -403,0 +418,0 @@ // attribute names from the template strings, we can retreive the original

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc