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.7.1 to 0.8.0-pre.1

lib/async-append.d.ts

5

CHANGELOG.md

@@ -13,4 +13,7 @@ # Change Log

<!-- ## Unreleased -->
## Unreleased
* Added the `asyncAppend` and `asyncReplace` directives to handle async iterable values
in expressions.
## [0.7.0] - 2017-10-06

@@ -17,0 +20,0 @@

1

lib/lit-extended.d.ts

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

setValue(value: any): void;
handleEvent(event: Event): void;
}

@@ -49,7 +49,7 @@ /**

if (templatePart.rawName.startsWith('on-')) {
const eventName = templatePart.rawName.substring(3);
const eventName = templatePart.rawName.slice(3);
return new EventPart(instance, node, eventName);
}
if (templatePart.name.endsWith('$')) {
const name = templatePart.name.substring(0, templatePart.name.length - 1);
const name = templatePart.name.slice(0, -1);
return new AttributePart(instance, node, name, templatePart.strings);

@@ -65,3 +65,3 @@ }

let value;
if (s.length === 2 && s[0] === '' && s[s.length - 1] === '') {
if (s.length === 2 && s[0] === '' && s[1] === '') {
// An expression that occupies the whole attribute value will leave

@@ -73,9 +73,3 @@ // leading and trailing empty strings.

// Interpolation, so interpolate
value = '';
for (let i = 0; i < s.length; i++) {
value += s[i];
if (i < s.length - 1) {
value += getValue(this, values[startIndex + i]);
}
}
value = this._interpolate(values, startIndex);
}

@@ -93,19 +87,12 @@ this.element[this.name] = value;

const listener = getValue(this, value);
if (listener === this._listener) {
const previous = this._listener;
if (listener === previous) {
return;
}
if (listener == null) {
this.element.removeEventListener(this.eventName, this);
}
else if (this._listener == null) {
this.element.addEventListener(this.eventName, this);
}
this._listener = listener;
}
handleEvent(event) {
if (typeof this._listener === 'function') {
this._listener.call(this.element, event);
if (previous != null) {
this.element.removeEventListener(this.eventName, previous);
}
else if (typeof this._listener.handleEvent === 'function') {
this._listener.handleEvent(event);
if (listener != null) {
this.element.addEventListener(this.eventName, listener);
}

@@ -112,0 +99,0 @@ }

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

import { directive, NodePart } from '../lit-html.js';
const stateCache = new WeakMap();
const keyMapCache = new WeakMap();
function cleanMap(part, key, map) {
if (!part.startNode.parentNode) {
map.delete(key);
}
}
export function repeat(items, keyFnOrTemplate, template) {

@@ -26,17 +31,10 @@ let keyFn;

return directive((part) => {
let state = stateCache.get(part);
if (state === undefined) {
state = {
keyMap: keyFn && new Map(),
parts: [],
};
stateCache.set(part, state);
let keyMap = keyMapCache.get(part);
if (keyMap === undefined) {
keyMap = new Map();
keyMapCache.set(part, keyMap);
}
const container = part.startNode.parentNode;
const oldParts = state.parts;
const endParts = new Map(oldParts.map((p) => [p.endNode, p]));
const keyMap = state.keyMap;
const itemParts = [];
let index = 0;
let currentMarker;
let index = -1;
let currentMarker = part.startNode.nextSibling;
for (const item of items) {

@@ -46,4 +44,5 @@ let result;

try {
result = template(item, index++);
key = keyFn && keyFn(item);
++index;
result = template(item, index);
key = keyFn ? keyFn(item) : index;
}

@@ -54,83 +53,41 @@ catch (e) {

}
// Try to reuse a part, either keyed or from the list of previous parts
// if there's no keyMap
let itemPart = keyMap === undefined ? oldParts[0] : keyMap.get(key);
// Try to reuse a part
let itemPart = keyMap.get(key);
if (itemPart === undefined) {
// New part, attach it
if (currentMarker === undefined) {
currentMarker = new Text();
container.insertBefore(currentMarker, part.startNode.nextSibling);
}
const endNode = new Text();
container.insertBefore(endNode, currentMarker.nextSibling);
itemPart = new NodePart(part.instance, currentMarker, endNode);
if (key !== undefined && keyMap !== undefined) {
const marker = document.createTextNode('');
const endNode = document.createTextNode('');
container.insertBefore(marker, currentMarker);
container.insertBefore(endNode, currentMarker);
itemPart = new NodePart(part.instance, marker, endNode);
if (key !== undefined) {
keyMap.set(key, itemPart);
}
}
else {
// Existing part, maybe move it
const range = document.createRange();
range.setStartBefore(itemPart.startNode);
range.setEndBefore(itemPart.endNode);
if (currentMarker === undefined) {
// this should be the first part, make sure it's first
if (part.startNode.nextSibling !== itemPart.startNode) {
// move the whole part
// get previous and next parts
const previousPart = endParts.get(itemPart.startNode);
if (previousPart) {
previousPart.endNode = itemPart.endNode;
endParts.set(previousPart.endNode, previousPart);
}
const contents = range.extractContents();
if (part.startNode.nextSibling === part.endNode) {
// The container part was empty, so we need a new endPart
itemPart.endNode = new Text();
container.insertBefore(itemPart.endNode, part.startNode.nextSibling);
}
else {
// endNode should equal the startNode of the currently first part
itemPart.endNode = part.startNode.nextSibling;
}
container.insertBefore(contents, part.startNode.nextSibling);
}
// else part is in the correct position already
else if (currentMarker !== itemPart.startNode) {
// Existing part in the wrong position
const end = itemPart.endNode.nextSibling;
for (let node = itemPart.startNode; node !== end;) {
const n = node.nextSibling;
container.insertBefore(node, currentMarker);
node = n;
}
else if (currentMarker !== itemPart.startNode) {
// move to correct position
const previousPart = endParts.get(itemPart.startNode);
if (previousPart) {
previousPart.endNode = itemPart.endNode;
endParts.set(previousPart.endNode, previousPart);
}
const contents = range.extractContents();
container.insertBefore(contents, currentMarker);
}
// remove part from oldParts list so it's not cleaned up
oldParts.splice(oldParts.indexOf(itemPart), 1);
}
// else part is in the correct position already
else {
// else part is in the correct position already
currentMarker = itemPart.endNode.nextSibling;
}
itemPart.setValue(result);
itemParts.push(itemPart);
currentMarker = itemPart.endNode;
}
// Cleanup
if (oldParts.length > 0) {
const clearStart = oldParts[0].startNode;
const clearEnd = oldParts[oldParts.length - 1].endNode;
const clearRange = document.createRange();
if (itemParts.length === 0) {
clearRange.setStartBefore(clearStart);
if (currentMarker !== part.endNode) {
const end = part.endNode;
for (let node = currentMarker; node !== end;) {
const n = node.nextSibling;
container.removeChild(node);
node = n;
}
else {
clearRange.setStartAfter(clearStart);
}
clearRange.setEndAfter(clearEnd);
clearRange.deleteContents();
clearRange.detach(); // is this neccessary?
keyMap.forEach(cleanMap);
}
state.parts = itemParts;
});
}
//# sourceMappingURL=repeat.js.map

@@ -22,2 +22,2 @@ /**

*/
export declare const unsafeHTML: (value: any) => (_part: NodePart) => DocumentFragment;
export declare const unsafeHTML: (value: any) => (part: NodePart) => void;

@@ -22,7 +22,7 @@ /**

*/
export const unsafeHTML = (value) => directive((_part) => {
export const unsafeHTML = (value) => directive((part) => {
const tmp = document.createElement('template');
tmp.innerHTML = value;
return document.importNode(tmp.content, true);
part.setValue(document.importNode(tmp.content, true));
});
//# sourceMappingURL=unsafe-html.js.map

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

*/
export declare const until: (promise: Promise<any>, defaultContent: any) => (part: NodePart) => Promise<any>;
export declare const until: (promise: Promise<any>, defaultContent: any) => (part: NodePart) => void;

@@ -20,4 +20,4 @@ /**

part.setValue(defaultContent);
return promise;
part.setValue(promise);
});
//# sourceMappingURL=until.js.map

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

element: HTMLTemplateElement;
svg: boolean;
constructor(strings: TemplateStringsArray, svg?: boolean);

@@ -62,2 +61,9 @@ /**

}
/**
* Returns a value ready to be inserted into a Part from a user-provided value.
*
* If the user value is a directive, this invokes the directive with the given
* part. If the value is null, it's converted to undefined to work better
* with certain DOM APIs, like textContent.
*/
export declare const getValue: (part: Part, value: any) => any;

@@ -83,2 +89,3 @@ export declare type DirectiveFn = (part: Part) => any;

constructor(instance: TemplateInstance, element: Element, name: string, strings: string[]);
protected _interpolate(values: any[], startIndex: number): string;
setValue(values: any[], startIndex: number): void;

@@ -90,3 +97,3 @@ }

endNode: Node;
private _previousValue;
_previousValue: any;
constructor(instance: TemplateInstance, startNode: Node, endNode: Node);

@@ -99,3 +106,3 @@ setValue(value: any): void;

private _setIterable(value);
protected _setPromise(value: Promise<any>): void;
private _setPromise(value);
clear(startNode?: Node): void;

@@ -117,1 +124,13 @@ }

}
/**
* Reparents nodes, starting from `startNode` (inclusive) to `endNode`
* (exclusive), into another container (could be the same container), before
* `beforeNode`. If `beforeNode` is null, it appends the nodes to the
* container.
*/
export declare const reparentNodes: (container: Node, start: Node | null, end?: Node | null, before?: Node | null) => void;
/**
* Removes nodes, starting from `startNode` (inclusive) to `endNode`
* (exclusive), from `container`.
*/
export declare const removeNodes: (container: Node, startNode: Node | null, endNode?: Node | null) => void;

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

instance.update(result.values);
let child;
while ((child = container.lastChild)) {
container.removeChild(child);
}
removeNodes(container, container.firstChild);
container.appendChild(fragment);

@@ -88,15 +85,19 @@ }

*/
const attributeMarker = `{{lit-${Math.random()}}}`;
const marker = `{{lit-${String(Math.random()).slice(2)}}}`;
const nodeMarker = `<!--${marker}-->`;
const markerRegex = new RegExp(`${marker}|${nodeMarker}`);
const nonWhitespace = /[^\s]/;
const lastAttributeNameRegex = /([\w\-.$]+)=(?:[^"']*|"[^"]*|'[^']*)$/;
/**
* Regex to scan the string preceding an expression to see if we're in a text
* context, and not an attribute context.
*
* This works by seeing if we have a `>` not followed by a `<`. If there is a
* `<` closer to the end of the strings, then we're inside a tag.
* Finds the closing index of the last closed HTML tag.
* This has 3 possible return values:
* - `-1`, meaning there is no tag in str.
* - `string.length`, meaning the last opened tag is unclosed.
* - Some positive number < str.length, meaning the index of the closing '>'.
*/
const textRegex = />[^<]*$/;
const hasTagsRegex = /[^<]*/;
const textMarkerContent = '_-lit-html-_';
const textMarker = `<!--${textMarkerContent}-->`;
const attrOrTextRegex = new RegExp(`${attributeMarker}|${textMarker}`);
function findTagClose(str) {
const close = str.lastIndexOf('>');
const open = str.indexOf('<', close + 1);
return open > -1 ? str.length : close;
}
/**

@@ -130,7 +131,12 @@ * A placeholder for a dynamic expression in an HTML template.

this.parts = [];
this.svg = svg;
this.element = document.createElement('template');
this.element.innerHTML = this._getHtml(strings, svg);
const element = this.element = document.createElement('template');
element.innerHTML = this._getHtml(strings, svg);
const content = element.content;
if (svg) {
const svgElement = content.firstChild;
content.removeChild(svgElement);
reparentNodes(content, svgElement.firstChild);
}
// Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null
const walker = document.createTreeWalker(this.element.content, 133 /* NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT |
const walker = document.createTreeWalker(content, 133 /* NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT |
NodeFilter.SHOW_TEXT */, null, false);

@@ -154,25 +160,31 @@ let index = -1;

const attributes = node.attributes;
// Per https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap,
// attributes are not guaranteed to be returned in document order. In
// particular, Edge/IE can return them out of order, so we cannot assume
// a correspondance between part index and attribute index.
let count = 0;
for (let i = 0; i < attributes.length; i++) {
const attribute = attributes.item(i);
const attributeStrings = attribute.value.split(attrOrTextRegex);
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--;
if (attributes[i].value.indexOf(marker) >= 0) {
count++;
}
}
while (count-- > 0) {
// Get the template literal section leading up to the first
// expression in this attribute attribute
const stringForPart = strings[partIndex];
// Find the attribute name
const attributeNameInPart = lastAttributeNameRegex.exec(stringForPart)[1];
// Find the corresponding attribute
const attribute = attributes.getNamedItem(attributeNameInPart);
const stringsForAttributeValue = attribute.value.split(markerRegex);
this.parts.push(new TemplatePart('attribute', index, attribute.name, attributeNameInPart, stringsForAttributeValue));
node.removeAttribute(attribute.name);
partIndex += stringsForAttributeValue.length - 1;
}
}
else if (node.nodeType === 3 /* Node.TEXT_NODE */) {
const nodeValue = node.nodeValue;
const strings = nodeValue.split(attributeMarker);
if (strings.length > 1) {
if (nodeValue.indexOf(marker) > -1) {
const parent = node.parentNode;
const strings = nodeValue.split(markerRegex);
const lastIndex = strings.length - 1;

@@ -201,3 +213,3 @@ // We have a part for each match found

nextSibling.nodeType === 1 /* Node.ELEMENT_NODE */) &&
nodeValue.trim() === '') {
!nonWhitespace.test(nodeValue)) {
nodesToRemove.push(node);

@@ -210,10 +222,18 @@ currentNode = previousNode;

else if (node.nodeType === 8 /* Node.COMMENT_NODE */ &&
node.nodeValue === textMarkerContent) {
node.nodeValue === marker) {
const parent = node.parentNode;
// If we don't have a previous node add a marker node.
// If the previousSibling is removed, because it's another part
// placholder, or empty text, add a marker node.
if (node.previousSibling === null ||
node.previousSibling !== previousNode) {
parent.insertBefore(new Text(), node);
// Add a new marker node to be the startNode of the Part if any of the
// following are true:
// * We don't have a previousSibling
// * previousSibling is being removed (thus it's not the
// `previousNode`)
// * previousSibling is not a Text node
//
// TODO(justinfagnani): We should be able to use the previousNode here
// as the marker node and reduce the number of extra nodes we add to a
// template. See https://github.com/PolymerLabs/lit-html/issues/147
const previousSibling = node.previousSibling;
if (previousSibling === null || previousSibling !== previousNode ||
previousSibling.nodeType !== Node.TEXT_NODE) {
parent.insertBefore(document.createTextNode(''), node);
}

@@ -225,7 +245,7 @@ else {

nodesToRemove.push(node);
// If we don't have a next node add a marker node.
// If we don't have a nextSibling add a marker node.
// We don't have to check if the next node is going to be removed,
// because that node will induce a marker if so.
// because that node will induce a new marker if so.
if (node.nextSibling === null) {
parent.insertBefore(new Text(), node);
parent.insertBefore(document.createTextNode(''), node);
}

@@ -248,25 +268,32 @@ else {

_getHtml(strings, svg) {
const l = strings.length;
const a = [];
let isTextBinding = false;
for (let i = 0; i < l - 1; i++) {
const l = strings.length - 1;
let html = '';
let isTextBinding = true;
for (let i = 0; i < l; i++) {
const s = strings[i];
a.push(s);
// We're in a text position if the previous string matches the
// textRegex. If it doesn't and the previous string has no tags, then
// we use the previous text position state.
isTextBinding = s.match(textRegex) !== null ||
(s.match(hasTagsRegex) !== null && isTextBinding);
a.push(isTextBinding ? textMarker : attributeMarker);
html += s;
// We're in a text position if the previous string closed its tags.
// If it doesn't have any tags, then we use the previous text position
// state.
const closing = findTagClose(s);
isTextBinding = closing > -1 ? closing < s.length : isTextBinding;
html += isTextBinding ? nodeMarker : marker;
}
a.push(strings[l - 1]);
const html = a.join('');
html += strings[l];
return svg ? `<svg>${html}</svg>` : html;
}
}
/**
* Returns a value ready to be inserted into a Part from a user-provided value.
*
* If the user value is a directive, this invokes the directive with the given
* part. If the value is null, it's converted to undefined to work better
* with certain DOM APIs, like textContent.
*/
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) {
if (isDirective(value)) {
value = value(part);
return directiveValue;
}

@@ -279,2 +306,4 @@ return value === null ? undefined : value;

};
const isDirective = (o) => typeof o === 'function' && o.__litDirective === true;
const directiveValue = {};
export class AttributePart {

@@ -288,21 +317,24 @@ constructor(instance, element, name, strings) {

}
setValue(values, startIndex) {
_interpolate(values, startIndex) {
const strings = this.strings;
const l = strings.length - 1;
let text = '';
for (let i = 0; i < strings.length; i++) {
for (let i = 0; i < l; i++) {
text += strings[i];
if (i < strings.length - 1) {
const v = getValue(this, values[startIndex + i]);
if (v &&
(Array.isArray(v) || typeof v !== 'string' && v[Symbol.iterator])) {
for (const t of v) {
// TODO: we need to recursively call getValue into iterables...
text += t;
}
const v = getValue(this, values[startIndex + i]);
if (v && v !== directiveValue &&
(Array.isArray(v) || typeof v !== 'string' && v[Symbol.iterator])) {
for (const t of v) {
// TODO: we need to recursively call getValue into iterables...
text += t;
}
else {
text += v;
}
}
else {
text += v;
}
}
return text + strings[l];
}
setValue(values, startIndex) {
const text = this._interpolate(values, startIndex);
this.element.setAttribute(this.name, text);

@@ -320,2 +352,5 @@ }

value = getValue(this, value);
if (value === directiveValue) {
return;
}
if (value === null ||

@@ -351,2 +386,5 @@ !(typeof value === 'object' || typeof value === 'function')) {

_setNode(value) {
if (this._previousValue === value) {
return;
}
this.clear();

@@ -358,2 +396,3 @@ this._insert(value);

const node = this.startNode.nextSibling;
value = value === undefined ? '' : value;
if (node === this.endNode.previousSibling &&

@@ -368,3 +407,3 @@ node.nodeType === Node.TEXT_NODE) {

else {
this._setNode(document.createTextNode(value === undefined ? '' : value));
this._setNode(document.createTextNode(value));
}

@@ -439,2 +478,3 @@ this._previousValue = value;

_setPromise(value) {
this._previousValue = value;
value.then((v) => {

@@ -445,9 +485,5 @@ if (this._previousValue === value) {

});
this._previousValue = value;
}
clear(startNode = this.startNode) {
let node;
while ((node = startNode.nextSibling) !== this.endNode) {
node.parentNode.removeChild(node);
}
removeNodes(this.startNode.parentNode, startNode.nextSibling, this.endNode);
}

@@ -489,33 +525,47 @@ }

const fragment = document.importNode(this.template.element.content, true);
if (this.template.parts.length > 0) {
const parts = this.template.parts;
if (parts.length > 0) {
// Edge needs all 4 parameters present; IE11 needs 3rd parameter to be
// null
const walker = document.createTreeWalker(fragment, 133 /* NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT */, null, false);
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 {
const walker = document.createTreeWalker(fragment, 133 /* NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT |
NodeFilter.SHOW_TEXT */, null, false);
let index = -1;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
while (index < part.index) {
index++;
node = walker.nextNode();
walker.nextNode();
}
this._parts.push(this._partCallback(this, part, walker.currentNode));
}
}
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;
}
}
/**
* Reparents nodes, starting from `startNode` (inclusive) to `endNode`
* (exclusive), into another container (could be the same container), before
* `beforeNode`. If `beforeNode` is null, it appends the nodes to the
* container.
*/
export const reparentNodes = (container, start, end = null, before = null) => {
let node = start;
while (node !== end) {
const n = node.nextSibling;
container.insertBefore(node, before);
node = n;
}
};
/**
* Removes nodes, starting from `startNode` (inclusive) to `endNode`
* (exclusive), from `container`.
*/
export const removeNodes = (container, startNode, endNode = null) => {
let node = startNode;
while (node !== endNode) {
const n = node.nextSibling;
container.removeChild(node);
node = n;
}
};
//# sourceMappingURL=lit-html.js.map
{
"name": "lit-html",
"version": "0.7.1",
"version": "0.8.0-pre.1",
"description": "HTML template literals in JavaScript",

@@ -23,3 +23,3 @@ "license": "BSD-3-Clause",

"gen-docs": "typedoc --readme none --mode modules --excludeNotExported --excludePrivate --exclude **/*_test.ts --out ./docs/api .",
"test": "npm run build && wct -l chrome --npm && npm run lint",
"test": "npm run build && wct --npm && npm run lint",
"quicktest": "wct -l chrome -p --npm",

@@ -41,3 +41,5 @@ "checksize": "uglifyjs lit-html.js -mc --toplevel | gzip -9 | wc -c",

"wct-browser-legacy": "^0.0.1-pre.10",
"web-component-tester": "^6.3.0"
"web-component-tester": "^6.3.0",
"@webcomponents/template": "^1.1.0",
"get-own-property-symbols": "^0.9.2"
},

@@ -44,0 +46,0 @@ "typings": "lit-html.d.ts",

@@ -243,2 +243,44 @@ # lit-html

#### `asyncAppend(asyncIterable)` and `asyncReplace(asyncIterable)`
`asyncAppend` renders the values of an [async iterable](https://github.com/tc39/proposal-async-iteration),
appending each new value after the previous.
`asyncReplace` renders the values of an [async iterable](https://github.com/tc39/proposal-async-iteration),
replacing the previous value with the new value.
Example:
```javascript
/**
* Returns an async iterable that yields the number of seconds since `date`,
* approximately every second.
*/
async function* secondsAgo(date) {
const seconds = (Date.now() - date.getTime()) / 1000;
const wholeSeconds = Math.floor(seconds);
yield wholeSeconds;
yield* await new Promise((res) => {
setTimeout(() => {
res(secondsAgo(date));
}, 1000 - (seconds - wholeSeconds) * 1000);
});
}
render(html`
This example started ${asyncReplace(secondsAgo(new Date()))} seconds ago.
`, document.body);
```
In the near future, `ReadableStream`s will be async iterables, enabling streaming `fetch()`
directly into a template:
```javascript
const streamingResponse = (async () => {
const response = await fetch('https://cors-anywhere.herokuapp.com/http://stuff.mit.edu/afs/sipb/contrib/pi/pi-billion.txt');
return response.body.getReader();
})();
render(html`π is: ${asyncAppend(streamingResponse)}`, document.body);
```
### Promises

@@ -245,0 +287,0 @@

@@ -56,8 +56,7 @@ /**

if (templatePart.rawName!.startsWith('on-')) {
const eventName = templatePart.rawName!.substring(3);
const eventName = templatePart.rawName!.slice(3);
return new EventPart(instance, node as Element, eventName);
}
if (templatePart.name!.endsWith('$')) {
const name = templatePart.name!.substring(
0, templatePart.name!.length - 1);
const name = templatePart.name!.slice(0, -1);
return new AttributePart(

@@ -79,3 +78,3 @@ instance, node as Element, name, templatePart.strings!);

let value: any;
if (s.length === 2 && s[0] === '' && s[s.length - 1] === '') {
if (s.length === 2 && s[0] === '' && s[1] === '') {
// An expression that occupies the whole attribute value will leave

@@ -86,9 +85,3 @@ // leading and trailing empty strings.

// Interpolation, so interpolate
value = '';
for (let i = 0; i < s.length; i++) {
value += s[i];
if (i < s.length - 1) {
value += getValue(this, values[startIndex + i]);
}
}
value = this._interpolate(values, startIndex);
}

@@ -113,20 +106,15 @@ (this.element as any)[this.name] = value;

const listener = getValue(this, value);
if (listener === this._listener) {
const previous = this._listener;
if (listener === previous) {
return;
}
if (listener == null) {
this.element.removeEventListener(this.eventName, this);
} else if (this._listener == null) {
this.element.addEventListener(this.eventName, this);
}
this._listener = listener;
}
handleEvent(event: Event) {
if (typeof this._listener === 'function') {
this._listener.call(this.element, event);
} else if (typeof this._listener.handleEvent === 'function') {
this._listener.handleEvent(event);
if (previous != null) {
this.element.removeEventListener(this.eventName, previous);
}
if (listener != null) {
this.element.addEventListener(this.eventName, listener);
}
}
}

@@ -20,9 +20,10 @@ /**

interface State {
keyMap: Map<any, NodePart>;
parts: NodePart[];
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);
}
}
const stateCache = new WeakMap<NodePart, State>();
export function repeat<T>(

@@ -44,21 +45,12 @@ items: T[], keyFn: KeyFn<T>, template: ItemTemplate<T>): DirectiveFn;

let state = stateCache.get(part);
if (state === undefined) {
state = {
keyMap: keyFn && new Map(),
parts: [],
};
stateCache.set(part, state);
let keyMap = keyMapCache.get(part);
if (keyMap === undefined) {
keyMap = new Map();
keyMapCache.set(part, keyMap);
}
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 keyMap = state.keyMap;
let index = -1;
let currentMarker = part.startNode.nextSibling!;
const itemParts = [];
let index = 0;
let currentMarker: Node|undefined;
for (const item of items) {

@@ -68,4 +60,5 @@ let result;

try {
result = template !(item, index++);
key = keyFn && keyFn(item);
++index;
result = template !(item, index);
key = keyFn ? keyFn(item) : index;
} catch (e) {

@@ -76,85 +69,40 @@ console.error(e);

// Try to reuse a part, either keyed or from the list of previous parts
// if there's no keyMap
let itemPart = keyMap === undefined ? oldParts[0] : keyMap.get(key);
// Try to reuse a part
let itemPart = keyMap.get(key);
if (itemPart === undefined) {
// New part, attach it
if (currentMarker === undefined) {
currentMarker = new Text();
container.insertBefore(currentMarker, part.startNode.nextSibling);
const marker = document.createTextNode('');
const endNode = document.createTextNode('');
container.insertBefore(marker, currentMarker);
container.insertBefore(endNode, currentMarker);
itemPart = new NodePart(part.instance, marker, endNode);
if (key !== undefined) {
keyMap.set(key, itemPart);
}
const endNode = new Text();
container.insertBefore(endNode, currentMarker.nextSibling);
itemPart = new NodePart(part.instance, currentMarker, endNode);
if (key !== undefined && keyMap !== undefined) {
keyMap.set(key, itemPart!);
} else if (currentMarker !== itemPart.startNode) {
// Existing part in the wrong position
const end = itemPart.endNode.nextSibling;
for (let node = itemPart.startNode; node !== end;) {
const n = node.nextSibling!;
container.insertBefore(node, currentMarker);
node = n;
}
} else {
// Existing part, maybe move it
const range = document.createRange();
range.setStartBefore(itemPart.startNode);
range.setEndBefore(itemPart.endNode);
if (currentMarker === undefined) {
// this should be the first part, make sure it's first
if (part.startNode.nextSibling !== itemPart.startNode) {
// move the whole part
// get previous and next parts
const previousPart = endParts.get(itemPart.startNode);
if (previousPart) {
previousPart.endNode = itemPart.endNode;
endParts.set(previousPart.endNode, previousPart);
}
const contents = range.extractContents();
if (part.startNode.nextSibling === part.endNode) {
// The container part was empty, so we need a new endPart
itemPart.endNode = new Text();
container.insertBefore(
itemPart.endNode, part.startNode.nextSibling);
} else {
// endNode should equal the startNode of the currently first part
itemPart.endNode = part.startNode.nextSibling!;
}
container.insertBefore(contents, part.startNode.nextSibling);
}
// else part is in the correct position already
} else if (currentMarker !== itemPart.startNode) {
// move to correct position
const previousPart = endParts.get(itemPart.startNode);
if (previousPart) {
previousPart.endNode = itemPart.endNode;
endParts.set(previousPart.endNode, previousPart);
}
const contents = range.extractContents();
container.insertBefore(contents, currentMarker);
}
// remove part from oldParts list so it's not cleaned up
oldParts.splice(oldParts.indexOf(itemPart), 1);
// else part is in the correct position already
currentMarker = itemPart.endNode.nextSibling!;
}
// else part is in the correct position already
itemPart.setValue(result);
itemParts.push(itemPart);
currentMarker = itemPart.endNode;
}
// Cleanup
if (oldParts.length > 0) {
const clearStart = oldParts[0].startNode;
const clearEnd = oldParts[oldParts.length - 1].endNode;
const clearRange = document.createRange();
if (itemParts.length === 0) {
clearRange.setStartBefore(clearStart);
} else {
clearRange.setStartAfter(clearStart);
if (currentMarker !== part.endNode) {
const end = part.endNode;
for (let node = currentMarker; node !== end;) {
const n = node.nextSibling!;
container.removeChild(node);
node = n;
}
clearRange.setEndAfter(clearEnd);
clearRange.deleteContents();
clearRange.detach(); // is this neccessary?
keyMap.forEach(cleanMap);
}
state.parts = itemParts;
});
}

@@ -24,6 +24,6 @@ /**

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

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

part.setValue(defaultContent);
return promise;
part.setValue(promise);
});

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

let child;
while ((child = container.lastChild)) {
container.removeChild(child);
}
removeNodes(container, container.firstChild);
container.appendChild(fragment);

@@ -113,16 +110,20 @@ }

*/
const attributeMarker = `{{lit-${Math.random()}}}`;
const marker = `{{lit-${String(Math.random()).slice(2)}}}`;
const nodeMarker = `<!--${marker}-->`;
const markerRegex = new RegExp(`${marker}|${nodeMarker}`);
const nonWhitespace = /[^\s]/;
const lastAttributeNameRegex = /([\w\-.$]+)=(?:[^"']*|"[^"]*|'[^']*)$/;
/**
* Regex to scan the string preceding an expression to see if we're in a text
* context, and not an attribute context.
*
* This works by seeing if we have a `>` not followed by a `<`. If there is a
* `<` closer to the end of the strings, then we're inside a tag.
* Finds the closing index of the last closed HTML tag.
* This has 3 possible return values:
* - `-1`, meaning there is no tag in str.
* - `string.length`, meaning the last opened tag is unclosed.
* - Some positive number < str.length, meaning the index of the closing '>'.
*/
const textRegex = />[^<]*$/;
const hasTagsRegex = /[^<]*/;
const textMarkerContent = '_-lit-html-_';
const textMarker = `<!--${textMarkerContent}-->`;
const attrOrTextRegex = new RegExp(`${attributeMarker}|${textMarker}`);
function findTagClose(str: string): number {
const close = str.lastIndexOf('>');
const open = str.indexOf('<', close + 1);
return open > -1 ? str.length : close;
}

@@ -156,11 +157,17 @@ /**

element: HTMLTemplateElement;
svg: boolean;
constructor(strings: TemplateStringsArray, svg: boolean = false) {
this.svg = svg;
this.element = document.createElement('template');
this.element.innerHTML = this._getHtml(strings, svg);
const element = this.element = document.createElement('template');
element.innerHTML = this._getHtml(strings, svg);
const content = element.content;
if (svg) {
const svgElement = content.firstChild!;
content.removeChild(svgElement);
reparentNodes(content, svgElement.firstChild);
}
// Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null
const walker = document.createTreeWalker(
this.element.content,
content,
133 /* NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT |

@@ -190,26 +197,36 @@ NodeFilter.SHOW_TEXT */

const attributes = node.attributes;
// Per https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap,
// attributes are not guaranteed to be returned in document order. In
// particular, Edge/IE can return them out of order, so we cannot assume
// a correspondance between part index and attribute index.
let count = 0;
for (let i = 0; i < attributes.length; i++) {
const attribute = attributes.item(i);
const attributeStrings = attribute.value.split(attrOrTextRegex);
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--;
if (attributes[i].value.indexOf(marker) >= 0) {
count++;
}
}
while (count-- > 0) {
// Get the template literal section leading up to the first
// expression in this attribute attribute
const stringForPart = strings[partIndex];
// Find the attribute name
const attributeNameInPart =
lastAttributeNameRegex.exec(stringForPart)![1];
// Find the corresponding attribute
const attribute = attributes.getNamedItem(attributeNameInPart);
const stringsForAttributeValue = attribute.value.split(markerRegex);
this.parts.push(new TemplatePart(
'attribute',
index,
attribute.name,
attributeNameInPart,
stringsForAttributeValue));
node.removeAttribute(attribute.name);
partIndex += stringsForAttributeValue.length - 1;
}
} else if (node.nodeType === 3 /* Node.TEXT_NODE */) {
const nodeValue = node.nodeValue!;
const strings = nodeValue.split(attributeMarker);
if (strings.length > 1) {
if (nodeValue.indexOf(marker) > -1) {
const parent = node.parentNode!;
const strings = nodeValue.split(markerRegex);
const lastIndex = strings.length - 1;

@@ -240,3 +257,3 @@

nextSibling.nodeType === 1 /* Node.ELEMENT_NODE */) &&
nodeValue.trim() === '') {
!nonWhitespace.test(nodeValue)) {
nodesToRemove.push(node);

@@ -249,10 +266,18 @@ currentNode = previousNode;

node.nodeType === 8 /* Node.COMMENT_NODE */ &&
node.nodeValue === textMarkerContent) {
node.nodeValue === marker) {
const parent = node.parentNode!;
// If we don't have a previous node add a marker node.
// If the previousSibling is removed, because it's another part
// placholder, or empty text, add a marker node.
if (node.previousSibling === null ||
node.previousSibling !== previousNode) {
parent.insertBefore(new Text(), node);
// Add a new marker node to be the startNode of the Part if any of the
// following are true:
// * We don't have a previousSibling
// * previousSibling is being removed (thus it's not the
// `previousNode`)
// * previousSibling is not a Text node
//
// TODO(justinfagnani): We should be able to use the previousNode here
// as the marker node and reduce the number of extra nodes we add to a
// template. See https://github.com/PolymerLabs/lit-html/issues/147
const previousSibling = node.previousSibling;
if (previousSibling === null || previousSibling !== previousNode ||
previousSibling.nodeType !== Node.TEXT_NODE) {
parent.insertBefore(document.createTextNode(''), node);
} else {

@@ -263,7 +288,7 @@ index--;

nodesToRemove.push(node);
// If we don't have a next node add a marker node.
// If we don't have a nextSibling add a marker node.
// We don't have to check if the next node is going to be removed,
// because that node will induce a marker if so.
// because that node will induce a new marker if so.
if (node.nextSibling === null) {
parent.insertBefore(new Text(), node);
parent.insertBefore(document.createTextNode(''), node);
} else {

@@ -287,17 +312,16 @@ index--;

private _getHtml(strings: TemplateStringsArray, svg?: boolean): string {
const l = strings.length;
const a = [];
let isTextBinding = false;
for (let i = 0; i < l - 1; i++) {
const l = strings.length - 1;
let html = '';
let isTextBinding = true;
for (let i = 0; i < l; i++) {
const s = strings[i];
a.push(s);
// We're in a text position if the previous string matches the
// textRegex. If it doesn't and the previous string has no tags, then
// we use the previous text position state.
isTextBinding = s.match(textRegex) !== null ||
(s.match(hasTagsRegex) !== null && isTextBinding);
a.push(isTextBinding ? textMarker : attributeMarker);
html += s;
// We're in a text position if the previous string closed its tags.
// If it doesn't have any tags, then we use the previous text position
// state.
const closing = findTagClose(s);
isTextBinding = closing > -1 ? closing < s.length : isTextBinding;
html += isTextBinding ? nodeMarker : marker;
}
a.push(strings[l - 1]);
const html = a.join('');
html += strings[l];
return svg ? `<svg>${html}</svg>` : html;

@@ -307,7 +331,15 @@ }

/**
* Returns a value ready to be inserted into a Part from a user-provided value.
*
* If the user value is a directive, this invokes the directive with the given
* part. If the value is null, it's converted to undefined to work better
* with certain DOM APIs, like textContent.
*/
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) {
if (isDirective(value)) {
value = value(part);
return directiveValue;
}

@@ -324,9 +356,10 @@ return value === null ? undefined : value;

const isDirective = (o: any) =>
typeof o === 'function' && o.__litDirective === true;
const directiveValue = {};
export interface Part {
instance: TemplateInstance;
size?: number;
// constructor(instance: TemplateInstance) {
// this.instance = instance;
// }
}

@@ -357,21 +390,25 @@

setValue(values: any[], startIndex: number): void {
protected _interpolate(values: any[], startIndex: number) {
const strings = this.strings;
const l = strings.length - 1;
let text = '';
for (let i = 0; i < strings.length; i++) {
for (let i = 0; i < l; i++) {
text += strings[i];
if (i < strings.length - 1) {
const v = getValue(this, values[startIndex + i]);
if (v &&
(Array.isArray(v) || typeof v !== 'string' && v[Symbol.iterator])) {
for (const t of v) {
// TODO: we need to recursively call getValue into iterables...
text += t;
}
} else {
text += v;
const v = getValue(this, values[startIndex + i]);
if (v && v !== directiveValue &&
(Array.isArray(v) || typeof v !== 'string' && v[Symbol.iterator])) {
for (const t of v) {
// TODO: we need to recursively call getValue into iterables...
text += t;
}
} else {
text += v;
}
}
return text + strings[l];
}
setValue(values: any[], startIndex: number): void {
const text = this._interpolate(values, startIndex);
this.element.setAttribute(this.name, text);

@@ -385,3 +422,3 @@ }

endNode: Node;
private _previousValue: any;
_previousValue: any;

@@ -397,3 +434,5 @@ constructor(instance: TemplateInstance, startNode: Node, endNode: Node) {

value = getValue(this, value);
if (value === directiveValue) {
return;
}
if (value === null ||

@@ -426,2 +465,5 @@ !(typeof value === 'object' || typeof value === 'function')) {

private _setNode(value: Node): void {
if (this._previousValue === value) {
return;
}
this.clear();

@@ -434,2 +476,3 @@ this._insert(value);

const node = this.startNode.nextSibling!;
value = value === undefined ? '' : value;
if (node === this.endNode.previousSibling &&

@@ -443,3 +486,3 @@ node.nodeType === Node.TEXT_NODE) {

} else {
this._setNode(document.createTextNode(value === undefined ? '' : value));
this._setNode(document.createTextNode(value));
}

@@ -520,3 +563,4 @@ this._previousValue = value;

protected _setPromise(value: Promise<any>): void {
private _setPromise(value: Promise<any>): void {
this._previousValue = value;
value.then((v: any) => {

@@ -527,10 +571,6 @@ if (this._previousValue === value) {

});
this._previousValue = value;
}
clear(startNode: Node = this.startNode) {
let node;
while ((node = startNode.nextSibling!) !== this.endNode) {
node.parentNode!.removeChild(node);
}
removeNodes(this.startNode.parentNode!, startNode.nextSibling!, this.endNode);
}

@@ -587,4 +627,5 @@ }

const fragment = document.importNode(this.template.element.content, true);
const parts = this.template.parts;
if (this.template.parts.length > 0) {
if (parts.length > 0) {
// Edge needs all 4 parameters present; IE11 needs 3rd parameter to be

@@ -594,3 +635,4 @@ // null

fragment,
133 /* NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT */
133 /* NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT |
NodeFilter.SHOW_TEXT */
,

@@ -600,27 +642,49 @@ null as any,

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 {
let index = -1;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
while (index < part.index) {
index++;
node = walker.nextNode();
walker.nextNode();
}
this._parts.push(this._partCallback(this, part, walker.currentNode));
}
}
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;
}
}
/**
* Reparents nodes, starting from `startNode` (inclusive) to `endNode`
* (exclusive), into another container (could be the same container), before
* `beforeNode`. If `beforeNode` is null, it appends the nodes to the
* container.
*/
export const reparentNodes =
(container: Node,
start: Node | null,
end: Node | null = null,
before: Node | null = null): void => {
let node = start;
while (node !== end) {
const n = node!.nextSibling;
container.insertBefore(node!, before as Node);
node = n;
}
};
/**
* Removes nodes, starting from `startNode` (inclusive) to `endNode`
* (exclusive), from `container`.
*/
export const removeNodes =
(container: Node,
startNode: Node | null,
endNode: Node | null = null): void => {
let node = startNode;
while (node !== endNode) {
const n = node!.nextSibling;
container.removeChild(node!);
node = n;
}
};

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