lit-html
Advanced tools
Comparing version 1.0.0-rc.1 to 1.0.0-rc.2
@@ -19,2 +19,17 @@ # Change Log | ||
## [1.0.0-rc.2] - 2019-01-09 | ||
### Changed | ||
* Performance improvements to template processing. ([#690](https://github.com/Polymer/lit-html/pull/690)) | ||
### Added | ||
* Added the `nothing` sentinel value which can be used to clear a part. ([#673](https://github.com/Polymer/lit-html/pull/673)) | ||
### Fixed | ||
* Fixed #702: a bug with the `unsafeHTML` directive when changing between unsafe and other values. ([#703](https://github.com/Polymer/lit-html/pull/703)) | ||
* Fixed #708: a bug with the `until` directive where placeholders could overwrite resolved Promises. ([#721](https://github.com/Polymer/lit-html/pull/721)) | ||
## [1.0.0-rc.1] - 2018-12-13 | ||
### Fixed | ||
* Documentation updates. | ||
* Fixed typing for template_polyfill `createElement` call. | ||
## [0.14.0] - 2018-11-30 | ||
@@ -21,0 +36,0 @@ ### Changed |
@@ -15,3 +15,10 @@ /** | ||
import { Part } 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 | ||
* sanitized or escaped, as it may lead to cross-site-scripting | ||
* vulnerabilities. | ||
*/ | ||
export declare const unsafeHTML: (value: any) => (part: Part) => void; | ||
//# sourceMappingURL=unsafe-html.d.ts.map |
@@ -14,3 +14,10 @@ /** | ||
*/ | ||
import { directive, isPrimitive, NodePart } from '../lit-html.js'; | ||
import { isPrimitive } from '../lib/parts.js'; | ||
import { directive, NodePart } from '../lit-html.js'; | ||
// For each part, remember the value that was last rendered to the part by the | ||
// unsafeHTML directive, and the DocumentFragment that was last set as a value. | ||
// The DocumentFragment is used as a unique key to check if the last value | ||
// rendered to the part was with unsafeHTML. If not, we'll always re-render the | ||
// value passed to unsafeHTML. | ||
const previousValues = new WeakMap(); | ||
/** | ||
@@ -23,3 +30,2 @@ * Renders the result as HTML, rather than text. | ||
*/ | ||
const previousValues = new WeakMap(); | ||
export const unsafeHTML = directive((value) => (part) => { | ||
@@ -29,13 +35,13 @@ if (!(part instanceof NodePart)) { | ||
} | ||
// Dirty check primitive values | ||
const previousValue = previousValues.get(part); | ||
if (previousValue === value && isPrimitive(value)) { | ||
if (previousValue !== undefined && isPrimitive(value) && | ||
value === previousValue.value && part.value === previousValue.fragment) { | ||
return; | ||
} | ||
// Use a <template> to parse HTML into Nodes | ||
const tmp = document.createElement('template'); | ||
tmp.innerHTML = value; | ||
part.setValue(document.importNode(tmp.content, true)); | ||
previousValues.set(part, value); | ||
const template = document.createElement('template'); | ||
template.innerHTML = value; | ||
const fragment = document.importNode(template.content, true); | ||
part.setValue(fragment); | ||
previousValues.set(part, { value, fragment }); | ||
}); | ||
//# sourceMappingURL=unsafe-html.js.map |
@@ -45,11 +45,9 @@ /** | ||
const previousValues = state.values; | ||
let changedSinceLastRender = false; | ||
state.values = args; | ||
for (let i = 0; i < args.length; i++) { | ||
// If we've rendered a higher-priority value already, stop. | ||
if (state.lastRenderedIndex !== undefined && i > state.lastRenderedIndex) { | ||
break; | ||
} | ||
const value = args[i]; | ||
// If we've seen this value before, we've already handled it. | ||
if (value === previousValues[i] && !changedSinceLastRender) { | ||
continue; | ||
} | ||
changedSinceLastRender = true; | ||
// Render non-Promise values immediately | ||
@@ -63,2 +61,7 @@ if (isPrimitive(value) || typeof value.then !== 'function') { | ||
} | ||
// If this is a Promise we've already handled, skip it. | ||
if (state.lastRenderedIndex !== undefined && | ||
typeof value.then === 'function' && value === previousValues[i]) { | ||
continue; | ||
} | ||
// We have a Promise that we haven't seen before, so priorities may have | ||
@@ -65,0 +68,0 @@ // changed. Forget what we rendered before. |
@@ -14,2 +14,5 @@ /** | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
import { Part } from './part.js'; | ||
@@ -16,0 +19,0 @@ import { NodePart } from './parts.js'; |
@@ -14,2 +14,5 @@ /** | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
import { Part } from './part.js'; | ||
@@ -16,0 +19,0 @@ export declare type DirectiveFn = (part: Part) => void; |
@@ -14,2 +14,8 @@ /** | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
/** | ||
* True if the custom elements polyfill is in use. | ||
*/ | ||
export declare const isCEPolyfill: boolean; | ||
@@ -16,0 +22,0 @@ /** |
@@ -14,2 +14,8 @@ /** | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
/** | ||
* True if the custom elements polyfill is in use. | ||
*/ | ||
export const isCEPolyfill = window.customElements !== undefined && | ||
@@ -16,0 +22,0 @@ window.customElements.polyfillWrapFlushCallback !== undefined; |
@@ -14,2 +14,5 @@ /** | ||
*/ | ||
/** | ||
* @module shady-render | ||
*/ | ||
import { Template } from './template.js'; | ||
@@ -16,0 +19,0 @@ /** |
@@ -14,4 +14,7 @@ /** | ||
*/ | ||
/** | ||
* @module shady-render | ||
*/ | ||
import { isTemplatePartActive } from './template.js'; | ||
const walkerNodeFilter = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT; | ||
const walkerNodeFilter = 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */; | ||
/** | ||
@@ -73,3 +76,3 @@ * Removes the list of nodes from a Template safely. In addition to removing | ||
const countNodes = (node) => { | ||
let count = (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) ? 0 : 1; | ||
let count = (node.nodeType === 11 /* Node.DOCUMENT_FRAGMENT_NODE */) ? 0 : 1; | ||
const walker = document.createTreeWalker(node, walkerNodeFilter, null, false); | ||
@@ -76,0 +79,0 @@ while (walker.nextNode()) { |
@@ -15,2 +15,5 @@ /** | ||
/** | ||
* @module lit-html | ||
*/ | ||
/** | ||
* The Part interface represents a dynamic part of a template instance rendered | ||
@@ -36,2 +39,6 @@ * by lit-html. | ||
export declare const noChange: {}; | ||
/** | ||
* A sentinel value that signals a NodePart to fully clear its content. | ||
*/ | ||
export declare const nothing: {}; | ||
//# sourceMappingURL=part.d.ts.map |
@@ -19,2 +19,6 @@ /** | ||
export const noChange = {}; | ||
/** | ||
* A sentinel value that signals a NodePart to fully clear its content. | ||
*/ | ||
export const nothing = {}; | ||
//# sourceMappingURL=part.js.map |
@@ -14,5 +14,8 @@ /** | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
import { isDirective } from './directive.js'; | ||
import { removeNodes } from './dom.js'; | ||
import { noChange } from './part.js'; | ||
import { noChange, nothing } from './part.js'; | ||
import { TemplateInstance } from './template-instance.js'; | ||
@@ -174,2 +177,6 @@ import { TemplateResult } from './template-result.js'; | ||
} | ||
else if (value === nothing) { | ||
this.value = nothing; | ||
this.clear(); | ||
} | ||
else { | ||
@@ -195,7 +202,7 @@ // Fallback, will render the string representation | ||
if (node === this.endNode.previousSibling && | ||
node.nodeType === Node.TEXT_NODE) { | ||
node.nodeType === 3 /* Node.TEXT_NODE */) { | ||
// If we only have a single text node between the markers, we can just | ||
// set its value, rather than replacing it. | ||
// TODO(justinfagnani): Can we just check if this.value is primitive? | ||
node.textContent = value; | ||
node.data = value; | ||
} | ||
@@ -202,0 +209,0 @@ else { |
@@ -14,2 +14,5 @@ /** | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
import { TemplateFactory } from './template-factory.js'; | ||
@@ -16,0 +19,0 @@ export interface RenderOptions { |
@@ -14,2 +14,5 @@ /** | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
import { removeNodes } from './dom.js'; | ||
@@ -16,0 +19,0 @@ import { NodePart } from './parts.js'; |
@@ -31,3 +31,3 @@ /** | ||
* be automatically re-written with this `scopeName` selector and moved out | ||
* of the rendered DOM and into the document <head>. | ||
* of the rendered DOM and into the document `<head>`. | ||
* | ||
@@ -45,3 +45,3 @@ * It is common to use this render method in conjunction with a custom element | ||
* | ||
* * Part values in <style> elements are only applied the first time a given | ||
* * Part values in `<style>` elements are only applied the first time a given | ||
* `scopeName` renders. Subsequent changes to parts in style elements will have | ||
@@ -54,5 +54,5 @@ * no effect. Because of this, parts in style elements should only be used for | ||
* custom element's `constructor` is not supported. Instead rendering should | ||
* either done asynchronously, for example at microtask timing (e.g. | ||
* Promise.resolve()), or be deferred until the element's `connectedCallback` | ||
* first runs. | ||
* either done asynchronously, for example at microtask timing (for example | ||
* `Promise.resolve()`), or be deferred until the first time the element's | ||
* `connectedCallback` runs. | ||
* | ||
@@ -70,6 +70,6 @@ * Usage considerations when using shimmed custom properties or `@apply`: | ||
* * Shimmed custom properties may only be defined either for an entire | ||
* shadowRoot (e.g. via `:host`) or via a rule that directly matches an element | ||
* with a shadowRoot. In other words, instead of flowing from parent to child as | ||
* do native css custom properties, shimmed custom properties flow only from | ||
* shadowRoots to nested shadowRoots. | ||
* shadowRoot (for example, in a `:host` rule) or via a rule that directly | ||
* matches an element with a shadowRoot. In other words, instead of flowing from | ||
* parent to child as do native css custom properties, shimmed custom properties | ||
* flow only from shadowRoots to nested shadowRoots. | ||
* | ||
@@ -76,0 +76,0 @@ * * When using `@apply` mixing css shorthand property names with |
@@ -14,2 +14,13 @@ /** | ||
*/ | ||
/** | ||
* Module to add shady DOM/shady CSS polyfill support to lit-html template | ||
* rendering. See the [[render]] method for details. | ||
* | ||
* @module shady-render | ||
* @preferred | ||
*/ | ||
/** | ||
* Do not remove this comment; it keeps typedoc from misplacing the module | ||
* docs. | ||
*/ | ||
import { removeNodes } from './dom.js'; | ||
@@ -105,4 +116,8 @@ import { insertNodeIntoTemplate, removeNodesFromTemplate } from './modify-template.js'; | ||
const styles = renderedDOM.querySelectorAll('style'); | ||
// If there are no styles, there's no work to do. | ||
// If there are no styles, skip unnecessary work | ||
if (styles.length === 0) { | ||
// Ensure prepareTemplateStyles is called to support adding | ||
// styles via `prepareAdoptedCssText` since that requires that | ||
// `prepareTemplateStyles` is called. | ||
window.ShadyCSS.prepareTemplateStyles(template.element, scopeName); | ||
return; | ||
@@ -160,3 +175,3 @@ } | ||
* be automatically re-written with this `scopeName` selector and moved out | ||
* of the rendered DOM and into the document <head>. | ||
* of the rendered DOM and into the document `<head>`. | ||
* | ||
@@ -174,3 +189,3 @@ * It is common to use this render method in conjunction with a custom element | ||
* | ||
* * Part values in <style> elements are only applied the first time a given | ||
* * Part values in `<style>` elements are only applied the first time a given | ||
* `scopeName` renders. Subsequent changes to parts in style elements will have | ||
@@ -183,5 +198,5 @@ * no effect. Because of this, parts in style elements should only be used for | ||
* custom element's `constructor` is not supported. Instead rendering should | ||
* either done asynchronously, for example at microtask timing (e.g. | ||
* Promise.resolve()), or be deferred until the element's `connectedCallback` | ||
* first runs. | ||
* either done asynchronously, for example at microtask timing (for example | ||
* `Promise.resolve()`), or be deferred until the first time the element's | ||
* `connectedCallback` runs. | ||
* | ||
@@ -199,6 +214,6 @@ * Usage considerations when using shimmed custom properties or `@apply`: | ||
* * Shimmed custom properties may only be defined either for an entire | ||
* shadowRoot (e.g. via `:host`) or via a rule that directly matches an element | ||
* with a shadowRoot. In other words, instead of flowing from parent to child as | ||
* do native css custom properties, shimmed custom properties flow only from | ||
* shadowRoots to nested shadowRoots. | ||
* shadowRoot (for example, in a `:host` rule) or via a rule that directly | ||
* matches an element with a shadowRoot. In other words, instead of flowing from | ||
* parent to child as do native css custom properties, shimmed custom properties | ||
* flow only from shadowRoots to nested shadowRoots. | ||
* | ||
@@ -205,0 +220,0 @@ * * When using `@apply` mixing css shorthand property names with |
@@ -14,2 +14,5 @@ /** | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
import { TemplateResult } from './template-result.js'; | ||
@@ -16,0 +19,0 @@ import { Template } from './template.js'; |
@@ -14,2 +14,5 @@ /** | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
import { isCEPolyfill } from './dom.js'; | ||
@@ -75,3 +78,3 @@ import { isTemplatePartActive } from './template.js'; | ||
const part = this.processor.handleTextExpression(this.options); | ||
part.insertAfterNode(node); | ||
part.insertAfterNode(node.previousSibling); | ||
this._parts.push(part); | ||
@@ -78,0 +81,0 @@ } |
@@ -14,2 +14,5 @@ /** | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
import { Part } from './part.js'; | ||
@@ -16,0 +19,0 @@ import { NodePart } from './parts.js'; |
@@ -14,2 +14,5 @@ /** | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
import { reparentNodes } from './dom.js'; | ||
@@ -36,7 +39,7 @@ import { boundAttributeSuffix, lastAttributeNameRegex, marker, nodeMarker } from './template.js'; | ||
const s = this.strings[i]; | ||
// This replace() call does two things: | ||
// 1) Appends a suffix to all bound attribute names to opt out of special | ||
// This exec() call does two things: | ||
// 1) Appends a suffix to the bound attribute name to opt out of special | ||
// attribute value parsing that IE11 and Edge do, like for style and | ||
// many SVG attributes. The Template class also appends the same suffix | ||
// when looking up attributes to creat Parts. | ||
// when looking up attributes to create Parts. | ||
// 2) Adds an unquoted-attribute-safe marker for the first expression in | ||
@@ -46,10 +49,15 @@ // an attribute. Subsequent attribute expressions will use node markers, | ||
// guaranteed to be quoted. | ||
let addedMarker = false; | ||
html += s.replace(lastAttributeNameRegex, (_match, whitespace, name, value) => { | ||
addedMarker = true; | ||
return whitespace + name + boundAttributeSuffix + value + marker; | ||
}); | ||
if (!addedMarker) { | ||
html += nodeMarker; | ||
const match = lastAttributeNameRegex.exec(s); | ||
if (match) { | ||
// We're starting a new bound attribute. | ||
// Add the safe attribute suffix, and use unquoted-attribute-safe | ||
// marker. | ||
html += s.substr(0, match.index) + match[1] + match[2] + | ||
boundAttributeSuffix + match[3] + marker; | ||
} | ||
else { | ||
// We're either in a bound node, or trailing bound attribute. | ||
// Either way, nodeMarker is safe to use. | ||
html += s + nodeMarker; | ||
} | ||
} | ||
@@ -56,0 +64,0 @@ return html + this.strings[endIndex]; |
@@ -14,2 +14,5 @@ /** | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
import { TemplateResult } from './template-result.js'; | ||
@@ -16,0 +19,0 @@ /** |
@@ -43,13 +43,10 @@ /** | ||
// null | ||
const walker = document.createTreeWalker(content, 133 /* NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | | ||
NodeFilter.SHOW_TEXT */, null, false); | ||
// The actual previous node, accounting for removals: if a node is removed | ||
// it will never be the previousNode. | ||
let previousNode; | ||
// Used to set previousNode at the top of the loop. | ||
let currentNode; | ||
const walker = document.createTreeWalker(content, 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */, null, false); | ||
// Keeps track of the last index associated with a part. We try to delete | ||
// unnecessary nodes, but we never want to associate two different parts | ||
// to the same index. They must have a constant node between. | ||
let lastPartIndex = 0; | ||
while (walker.nextNode()) { | ||
index++; | ||
previousNode = currentNode; | ||
const node = currentNode = walker.currentNode; | ||
const node = walker.currentNode; | ||
if (node.nodeType === 1 /* Node.ELEMENT_NODE */) { | ||
@@ -93,25 +90,29 @@ if (node.hasAttributes()) { | ||
else if (node.nodeType === 3 /* Node.TEXT_NODE */) { | ||
const nodeValue = node.nodeValue; | ||
if (nodeValue.indexOf(marker) < 0) { | ||
continue; | ||
const data = node.data; | ||
if (data.indexOf(marker) >= 0) { | ||
const parent = node.parentNode; | ||
const strings = data.split(markerRegex); | ||
const lastIndex = strings.length - 1; | ||
// Generate a new text node for each literal section | ||
// These nodes are also used as the markers for node parts | ||
for (let i = 0; i < lastIndex; i++) { | ||
parent.insertBefore((strings[i] === '') ? createMarker() : | ||
document.createTextNode(strings[i]), node); | ||
this.parts.push({ type: 'node', index: ++index }); | ||
} | ||
// If there's no text, we must insert a comment to mark our place. | ||
// Else, we can trust it will stick around after cloning. | ||
if (strings[lastIndex] === '') { | ||
parent.insertBefore(createMarker(), node); | ||
nodesToRemove.push(node); | ||
} | ||
else { | ||
node.data = strings[lastIndex]; | ||
} | ||
// We have a part for each match found | ||
partIndex += lastIndex; | ||
} | ||
const parent = node.parentNode; | ||
const strings = nodeValue.split(markerRegex); | ||
const lastIndex = strings.length - 1; | ||
// We have a part for each match found | ||
partIndex += lastIndex; | ||
// Generate a new text node for each literal section | ||
// These nodes are also used as the markers for node parts | ||
for (let i = 0; i < lastIndex; i++) { | ||
parent.insertBefore((strings[i] === '') ? createMarker() : | ||
document.createTextNode(strings[i]), node); | ||
this.parts.push({ type: 'node', index: index++ }); | ||
} | ||
parent.insertBefore(strings[lastIndex] === '' ? | ||
createMarker() : | ||
document.createTextNode(strings[lastIndex]), node); | ||
nodesToRemove.push(node); | ||
} | ||
else if (node.nodeType === 8 /* Node.COMMENT_NODE */) { | ||
if (node.nodeValue === marker) { | ||
if (node.data === marker) { | ||
const parent = node.parentNode; | ||
@@ -121,30 +122,18 @@ // Add a new marker node to be the startNode of the Part if any of | ||
// * 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) { | ||
// * The previousSibling is already the start of a previous part | ||
if (node.previousSibling === null || index === lastPartIndex) { | ||
index++; | ||
parent.insertBefore(createMarker(), node); | ||
} | ||
else { | ||
index--; | ||
} | ||
this.parts.push({ type: 'node', index: index++ }); | ||
nodesToRemove.push(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 new marker if so. | ||
lastPartIndex = index; | ||
this.parts.push({ type: 'node', index }); | ||
// If we don't have a nextSibling, keep this node so we have an end. | ||
// Else, we can remove it to save future costs. | ||
if (node.nextSibling === null) { | ||
parent.insertBefore(createMarker(), node); | ||
node.data = ''; | ||
} | ||
else { | ||
nodesToRemove.push(node); | ||
index--; | ||
} | ||
currentNode = previousNode; | ||
partIndex++; | ||
@@ -154,3 +143,4 @@ } | ||
let i = -1; | ||
while ((i = node.nodeValue.indexOf(marker, i + 1)) !== -1) { | ||
while ((i = node.data.indexOf(marker, i + 1)) !== | ||
-1) { | ||
// Comment node has a binding marker inside, make an inactive part | ||
@@ -157,0 +147,0 @@ // The binding won't work, but subsequent bindings will |
@@ -18,3 +18,3 @@ /** | ||
export { removeNodes, reparentNodes } from './lib/dom.js'; | ||
export { noChange, Part } from './lib/part.js'; | ||
export { noChange, nothing, Part } from './lib/part.js'; | ||
export { AttributeCommitter, AttributePart, BooleanAttributePart, EventPart, isPrimitive, NodePart, PropertyCommitter, PropertyPart } from './lib/parts.js'; | ||
@@ -21,0 +21,0 @@ export { RenderOptions } from './lib/render-options.js'; |
@@ -14,2 +14,19 @@ /** | ||
*/ | ||
/** | ||
* | ||
* Main lit-html module. | ||
* | ||
* Main exports: | ||
* | ||
* - [[html]] | ||
* - [[svg]] | ||
* - [[render]] | ||
* | ||
* @module lit-html | ||
* @preferred | ||
*/ | ||
/** | ||
* Do not remove this comment; it keeps typedoc from misplacing the module | ||
* docs. | ||
*/ | ||
import { defaultTemplateProcessor } from './lib/default-template-processor.js'; | ||
@@ -21,3 +38,3 @@ import { SVGTemplateResult, TemplateResult } from './lib/template-result.js'; | ||
export { removeNodes, reparentNodes } from './lib/dom.js'; | ||
export { noChange } from './lib/part.js'; | ||
export { noChange, nothing } from './lib/part.js'; | ||
export { AttributeCommitter, AttributePart, BooleanAttributePart, EventPart, isPrimitive, NodePart, PropertyCommitter, PropertyPart } from './lib/parts.js'; | ||
@@ -24,0 +41,0 @@ export { parts, render } from './lib/render.js'; |
{ | ||
"name": "lit-html", | ||
"version": "1.0.0-rc.1", | ||
"version": "1.0.0-rc.2", | ||
"description": "HTML template literals in JavaScript", | ||
@@ -26,3 +26,2 @@ "license": "BSD-3-Clause", | ||
"checksize": "rollup -c ; cat lit-html.bundled.js | gzip -9 | wc -c ; rm lit-html.bundled.js", | ||
"gen-docs": "typedoc --readme ./docs/_api-readme.md --mode file --ignoreCompilerErrors --excludeNotExported --excludePrivate --out ./docs/api --gaID UA-39334307-23 ./src/lit-html.ts ./src/lib/*.ts ./src/directives/*.ts", | ||
"test": "npm run build && npm run lint && wct --npm", | ||
@@ -47,3 +46,2 @@ "quicktest": "wct -l chrome -p --npm", | ||
"tslint": "^5.11.0", | ||
"typedoc": "^0.13.0", | ||
"typescript": "^3.1.1", | ||
@@ -50,0 +48,0 @@ "uglify-es": "^3.3.5", |
@@ -15,2 +15,4 @@ > ## 🛠 Status: In Development | ||
Docs source is in the `docs` folder. To build the site youself, see the instructions in [docs/README.md](docs/README.md). | ||
## Overview | ||
@@ -17,0 +19,0 @@ |
@@ -15,4 +15,17 @@ /** | ||
import {directive, isPrimitive, NodePart, Part} from '../lit-html.js'; | ||
import {isPrimitive} from '../lib/parts.js'; | ||
import {directive, NodePart, Part} from '../lit-html.js'; | ||
interface PreviousValue { | ||
value: any; | ||
fragment: DocumentFragment; | ||
} | ||
// For each part, remember the value that was last rendered to the part by the | ||
// unsafeHTML directive, and the DocumentFragment that was last set as a value. | ||
// The DocumentFragment is used as a unique key to check if the last value | ||
// rendered to the part was with unsafeHTML. If not, we'll always re-render the | ||
// value passed to unsafeHTML. | ||
const previousValues = new WeakMap<NodePart, PreviousValue>(); | ||
/** | ||
@@ -25,5 +38,2 @@ * Renders the result as HTML, rather than text. | ||
*/ | ||
const previousValues = new WeakMap<NodePart, string>(); | ||
export const unsafeHTML = directive((value: any) => (part: Part): void => { | ||
@@ -33,13 +43,15 @@ if (!(part instanceof NodePart)) { | ||
} | ||
// Dirty check primitive values | ||
const previousValue = previousValues.get(part); | ||
if (previousValue === value && isPrimitive(value)) { | ||
if (previousValue !== undefined && isPrimitive(value) && | ||
value === previousValue.value && part.value === previousValue.fragment) { | ||
return; | ||
} | ||
// Use a <template> to parse HTML into Nodes | ||
const tmp = document.createElement('template'); | ||
tmp.innerHTML = value; | ||
part.setValue(document.importNode(tmp.content, true)); | ||
previousValues.set(part, value); | ||
const template = document.createElement('template'); | ||
template.innerHTML = value; | ||
const fragment = document.importNode(template.content, true); | ||
part.setValue(fragment); | ||
previousValues.set(part, {value, fragment}); | ||
}); |
@@ -58,14 +58,12 @@ /** | ||
const previousValues = state.values; | ||
let changedSinceLastRender = false; | ||
state.values = args; | ||
for (let i = 0; i < args.length; i++) { | ||
// If we've rendered a higher-priority value already, stop. | ||
if (state.lastRenderedIndex !== undefined && i > state.lastRenderedIndex) { | ||
break; | ||
} | ||
const value = args[i]; | ||
// If we've seen this value before, we've already handled it. | ||
if (value === previousValues[i] && !changedSinceLastRender) { | ||
continue; | ||
} | ||
changedSinceLastRender = true; | ||
// Render non-Promise values immediately | ||
@@ -80,2 +78,8 @@ if (isPrimitive(value) || typeof value.then !== 'function') { | ||
// If this is a Promise we've already handled, skip it. | ||
if (state.lastRenderedIndex !== undefined && | ||
typeof value.then === 'function' && value === previousValues[i]) { | ||
continue; | ||
} | ||
// We have a Promise that we haven't seen before, so priorities may have | ||
@@ -82,0 +86,0 @@ // changed. Forget what we rendered before. |
@@ -15,2 +15,6 @@ /** | ||
/** | ||
* @module lit-html | ||
*/ | ||
import {Part} from './part.js'; | ||
@@ -17,0 +21,0 @@ import {AttributeCommitter, BooleanAttributePart, EventPart, NodePart, PropertyCommitter} from './parts.js'; |
@@ -1,2 +0,1 @@ | ||
/** | ||
@@ -16,2 +15,6 @@ * @license | ||
/** | ||
* @module lit-html | ||
*/ | ||
import {Part} from './part.js'; | ||
@@ -18,0 +21,0 @@ |
@@ -15,2 +15,9 @@ /** | ||
/** | ||
* @module lit-html | ||
*/ | ||
/** | ||
* True if the custom elements polyfill is in use. | ||
*/ | ||
export const isCEPolyfill = window.customElements !== undefined && | ||
@@ -17,0 +24,0 @@ (window.customElements as any).polyfillWrapFlushCallback !== undefined; |
@@ -15,6 +15,9 @@ /** | ||
/** | ||
* @module shady-render | ||
*/ | ||
import {isTemplatePartActive, Template, TemplatePart} from './template.js'; | ||
const walkerNodeFilter = | ||
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT; | ||
const walkerNodeFilter = 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */; | ||
@@ -80,3 +83,3 @@ /** | ||
const countNodes = (node: Node) => { | ||
let count = (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) ? 0 : 1; | ||
let count = (node.nodeType === 11 /* Node.DOCUMENT_FRAGMENT_NODE */) ? 0 : 1; | ||
const walker = | ||
@@ -83,0 +86,0 @@ document.createTreeWalker(node, walkerNodeFilter, null as any, false); |
@@ -16,2 +16,6 @@ /** | ||
/** | ||
* @module lit-html | ||
*/ | ||
/** | ||
* The Part interface represents a dynamic part of a template instance rendered | ||
@@ -40,1 +44,6 @@ * by lit-html. | ||
export const noChange = {}; | ||
/** | ||
* A sentinel value that signals a NodePart to fully clear its content. | ||
*/ | ||
export const nothing = {}; |
@@ -15,5 +15,9 @@ /** | ||
/** | ||
* @module lit-html | ||
*/ | ||
import {isDirective} from './directive.js'; | ||
import {removeNodes} from './dom.js'; | ||
import {noChange, Part} from './part.js'; | ||
import {noChange, nothing, Part} from './part.js'; | ||
import {RenderOptions} from './render-options.js'; | ||
@@ -200,2 +204,5 @@ import {TemplateInstance} from './template-instance.js'; | ||
this._commitIterable(value); | ||
} else if (value === nothing) { | ||
this.value = nothing; | ||
this.clear(); | ||
} else { | ||
@@ -224,7 +231,7 @@ // Fallback, will render the string representation | ||
if (node === this.endNode.previousSibling && | ||
node.nodeType === Node.TEXT_NODE) { | ||
node.nodeType === 3 /* Node.TEXT_NODE */) { | ||
// If we only have a single text node between the markers, we can just | ||
// set its value, rather than replacing it. | ||
// TODO(justinfagnani): Can we just check if this.value is primitive? | ||
node.textContent = value; | ||
(node as Text).data = value; | ||
} else { | ||
@@ -231,0 +238,0 @@ this._commitNode(document.createTextNode( |
@@ -15,2 +15,6 @@ /** | ||
/** | ||
* @module lit-html | ||
*/ | ||
import {TemplateFactory} from './template-factory.js'; | ||
@@ -17,0 +21,0 @@ |
@@ -15,2 +15,6 @@ /** | ||
/** | ||
* @module lit-html | ||
*/ | ||
import {removeNodes} from './dom.js'; | ||
@@ -17,0 +21,0 @@ import {NodePart} from './parts.js'; |
@@ -15,2 +15,14 @@ /** | ||
/** | ||
* Module to add shady DOM/shady CSS polyfill support to lit-html template | ||
* rendering. See the [[render]] method for details. | ||
* | ||
* @module shady-render | ||
* @preferred | ||
*/ | ||
/** | ||
* Do not remove this comment; it keeps typedoc from misplacing the module | ||
* docs. | ||
*/ | ||
import {removeNodes} from './dom.js'; | ||
@@ -121,4 +133,8 @@ import {insertNodeIntoTemplate, removeNodesFromTemplate} from './modify-template.js'; | ||
const styles = renderedDOM.querySelectorAll('style'); | ||
// If there are no styles, there's no work to do. | ||
// If there are no styles, skip unnecessary work | ||
if (styles.length === 0) { | ||
// Ensure prepareTemplateStyles is called to support adding | ||
// styles via `prepareAdoptedCssText` since that requires that | ||
// `prepareTemplateStyles` is called. | ||
window.ShadyCSS!.prepareTemplateStyles(template.element, scopeName); | ||
return; | ||
@@ -182,3 +198,3 @@ } | ||
* be automatically re-written with this `scopeName` selector and moved out | ||
* of the rendered DOM and into the document <head>. | ||
* of the rendered DOM and into the document `<head>`. | ||
* | ||
@@ -196,3 +212,3 @@ * It is common to use this render method in conjunction with a custom element | ||
* | ||
* * Part values in <style> elements are only applied the first time a given | ||
* * Part values in `<style>` elements are only applied the first time a given | ||
* `scopeName` renders. Subsequent changes to parts in style elements will have | ||
@@ -205,5 +221,5 @@ * no effect. Because of this, parts in style elements should only be used for | ||
* custom element's `constructor` is not supported. Instead rendering should | ||
* either done asynchronously, for example at microtask timing (e.g. | ||
* Promise.resolve()), or be deferred until the element's `connectedCallback` | ||
* first runs. | ||
* either done asynchronously, for example at microtask timing (for example | ||
* `Promise.resolve()`), or be deferred until the first time the element's | ||
* `connectedCallback` runs. | ||
* | ||
@@ -221,6 +237,6 @@ * Usage considerations when using shimmed custom properties or `@apply`: | ||
* * Shimmed custom properties may only be defined either for an entire | ||
* shadowRoot (e.g. via `:host`) or via a rule that directly matches an element | ||
* with a shadowRoot. In other words, instead of flowing from parent to child as | ||
* do native css custom properties, shimmed custom properties flow only from | ||
* shadowRoots to nested shadowRoots. | ||
* shadowRoot (for example, in a `:host` rule) or via a rule that directly | ||
* matches an element with a shadowRoot. In other words, instead of flowing from | ||
* parent to child as do native css custom properties, shimmed custom properties | ||
* flow only from shadowRoots to nested shadowRoots. | ||
* | ||
@@ -281,2 +297,2 @@ * * When using `@apply` mixing css shorthand property names with | ||
} | ||
}; | ||
}; |
@@ -15,2 +15,6 @@ /** | ||
/** | ||
* @module lit-html | ||
*/ | ||
import {TemplateResult} from './template-result.js'; | ||
@@ -17,0 +21,0 @@ import {marker, Template} from './template.js'; |
@@ -15,2 +15,5 @@ /** | ||
/** | ||
* @module lit-html | ||
*/ | ||
@@ -93,3 +96,3 @@ import {isCEPolyfill} from './dom.js'; | ||
const part = this.processor.handleTextExpression(this.options); | ||
part.insertAfterNode(node); | ||
part.insertAfterNode(node.previousSibling!); | ||
this._parts.push(part); | ||
@@ -96,0 +99,0 @@ } else { |
@@ -15,2 +15,6 @@ /** | ||
/** | ||
* @module lit-html | ||
*/ | ||
import {Part} from './part.js'; | ||
@@ -17,0 +21,0 @@ import {NodePart} from './parts.js'; |
@@ -15,2 +15,6 @@ /** | ||
/** | ||
* @module lit-html | ||
*/ | ||
import {reparentNodes} from './dom.js'; | ||
@@ -47,7 +51,7 @@ import {TemplateProcessor} from './template-processor.js'; | ||
const s = this.strings[i]; | ||
// This replace() call does two things: | ||
// 1) Appends a suffix to all bound attribute names to opt out of special | ||
// This exec() call does two things: | ||
// 1) Appends a suffix to the bound attribute name to opt out of special | ||
// attribute value parsing that IE11 and Edge do, like for style and | ||
// many SVG attributes. The Template class also appends the same suffix | ||
// when looking up attributes to creat Parts. | ||
// when looking up attributes to create Parts. | ||
// 2) Adds an unquoted-attribute-safe marker for the first expression in | ||
@@ -57,10 +61,13 @@ // an attribute. Subsequent attribute expressions will use node markers, | ||
// guaranteed to be quoted. | ||
let addedMarker = false; | ||
html += s.replace( | ||
lastAttributeNameRegex, (_match, whitespace, name, value) => { | ||
addedMarker = true; | ||
return whitespace + name + boundAttributeSuffix + value + marker; | ||
}); | ||
if (!addedMarker) { | ||
html += nodeMarker; | ||
const match = lastAttributeNameRegex.exec(s); | ||
if (match) { | ||
// We're starting a new bound attribute. | ||
// Add the safe attribute suffix, and use unquoted-attribute-safe | ||
// marker. | ||
html += s.substr(0, match.index) + match[1] + match[2] + | ||
boundAttributeSuffix + match[3] + marker; | ||
} else { | ||
// We're either in a bound node, or trailing bound attribute. | ||
// Either way, nodeMarker is safe to use. | ||
html += s + nodeMarker; | ||
} | ||
@@ -67,0 +74,0 @@ } |
@@ -15,2 +15,6 @@ /** | ||
/** | ||
* @module lit-html | ||
*/ | ||
import {TemplateResult} from './template-result.js'; | ||
@@ -55,19 +59,15 @@ | ||
content, | ||
133 /* NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | | ||
NodeFilter.SHOW_TEXT */ | ||
, | ||
133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */, | ||
null as any, | ||
false); | ||
// The actual previous node, accounting for removals: if a node is removed | ||
// it will never be the previousNode. | ||
let previousNode: Node|undefined; | ||
// Used to set previousNode at the top of the loop. | ||
let currentNode: Node|undefined; | ||
// Keeps track of the last index associated with a part. We try to delete | ||
// unnecessary nodes, but we never want to associate two different parts | ||
// to the same index. They must have a constant node between. | ||
let lastPartIndex = 0; | ||
while (walker.nextNode()) { | ||
index++; | ||
previousNode = currentNode; | ||
const node = currentNode = walker.currentNode as Element; | ||
const node = walker.currentNode as Element | Comment | Text; | ||
if (node.nodeType === 1 /* Node.ELEMENT_NODE */) { | ||
if (node.hasAttributes()) { | ||
const attributes = node.attributes; | ||
if ((node as Element).hasAttributes()) { | ||
const attributes = (node as Element).attributes; | ||
// Per | ||
@@ -97,39 +97,41 @@ // https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap, | ||
name.toLowerCase() + boundAttributeSuffix; | ||
const attributeValue = node.getAttribute(attributeLookupName)!; | ||
const attributeValue = | ||
(node as Element).getAttribute(attributeLookupName)!; | ||
const strings = attributeValue.split(markerRegex); | ||
this.parts.push({type: 'attribute', index, name, strings}); | ||
node.removeAttribute(attributeLookupName); | ||
(node as Element).removeAttribute(attributeLookupName); | ||
partIndex += strings.length - 1; | ||
} | ||
} | ||
if (node.tagName === 'TEMPLATE') { | ||
if ((node as Element).tagName === 'TEMPLATE') { | ||
_prepareTemplate(node as HTMLTemplateElement); | ||
} | ||
} else if (node.nodeType === 3 /* Node.TEXT_NODE */) { | ||
const nodeValue = node.nodeValue!; | ||
if (nodeValue.indexOf(marker) < 0) { | ||
continue; | ||
const data = (node as Text).data!; | ||
if (data.indexOf(marker) >= 0) { | ||
const parent = node.parentNode!; | ||
const strings = data.split(markerRegex); | ||
const lastIndex = strings.length - 1; | ||
// Generate a new text node for each literal section | ||
// These nodes are also used as the markers for node parts | ||
for (let i = 0; i < lastIndex; i++) { | ||
parent.insertBefore( | ||
(strings[i] === '') ? createMarker() : | ||
document.createTextNode(strings[i]), | ||
node); | ||
this.parts.push({type: 'node', index: ++index}); | ||
} | ||
// If there's no text, we must insert a comment to mark our place. | ||
// Else, we can trust it will stick around after cloning. | ||
if (strings[lastIndex] === '') { | ||
parent.insertBefore(createMarker(), node); | ||
nodesToRemove.push(node); | ||
} else { | ||
(node as Text).data = strings[lastIndex]; | ||
} | ||
// We have a part for each match found | ||
partIndex += lastIndex; | ||
} | ||
const parent = node.parentNode!; | ||
const strings = nodeValue.split(markerRegex); | ||
const lastIndex = strings.length - 1; | ||
// We have a part for each match found | ||
partIndex += lastIndex; | ||
// Generate a new text node for each literal section | ||
// These nodes are also used as the markers for node parts | ||
for (let i = 0; i < lastIndex; i++) { | ||
parent.insertBefore( | ||
(strings[i] === '') ? createMarker() : | ||
document.createTextNode(strings[i]), | ||
node); | ||
this.parts.push({type: 'node', index: index++}); | ||
} | ||
parent.insertBefore( | ||
strings[lastIndex] === '' ? | ||
createMarker() : | ||
document.createTextNode(strings[lastIndex]), | ||
node); | ||
nodesToRemove.push(node); | ||
} else if (node.nodeType === 8 /* Node.COMMENT_NODE */) { | ||
if (node.nodeValue === marker) { | ||
if ((node as Comment).data === marker) { | ||
const parent = node.parentNode!; | ||
@@ -139,32 +141,22 @@ // Add a new marker node to be the startNode of the Part if any of | ||
// * 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) { | ||
// * The previousSibling is already the start of a previous part | ||
if (node.previousSibling === null || index === lastPartIndex) { | ||
index++; | ||
parent.insertBefore(createMarker(), node); | ||
} else { | ||
index--; | ||
} | ||
this.parts.push({type: 'node', index: index++}); | ||
nodesToRemove.push(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 new marker if so. | ||
lastPartIndex = index; | ||
this.parts.push({type: 'node', index}); | ||
// If we don't have a nextSibling, keep this node so we have an end. | ||
// Else, we can remove it to save future costs. | ||
if (node.nextSibling === null) { | ||
parent.insertBefore(createMarker(), node); | ||
(node as Comment).data = ''; | ||
} else { | ||
nodesToRemove.push(node); | ||
index--; | ||
} | ||
currentNode = previousNode; | ||
partIndex++; | ||
} else { | ||
let i = -1; | ||
while ((i = node.nodeValue!.indexOf(marker, i + 1)) !== -1) { | ||
while ((i = (node as Comment).data!.indexOf(marker, i + 1)) !== | ||
-1) { | ||
// Comment node has a binding marker inside, make an inactive part | ||
@@ -171,0 +163,0 @@ // The binding won't work, but subsequent bindings will |
@@ -15,2 +15,20 @@ /** | ||
/** | ||
* | ||
* Main lit-html module. | ||
* | ||
* Main exports: | ||
* | ||
* - [[html]] | ||
* - [[svg]] | ||
* - [[render]] | ||
* | ||
* @module lit-html | ||
* @preferred | ||
*/ | ||
/** | ||
* Do not remove this comment; it keeps typedoc from misplacing the module | ||
* docs. | ||
*/ | ||
import {defaultTemplateProcessor} from './lib/default-template-processor.js'; | ||
@@ -23,3 +41,3 @@ import {SVGTemplateResult, TemplateResult} from './lib/template-result.js'; | ||
export {removeNodes, reparentNodes} from './lib/dom.js'; | ||
export {noChange, Part} from './lib/part.js'; | ||
export {noChange, nothing, Part} from './lib/part.js'; | ||
export {AttributeCommitter, AttributePart, BooleanAttributePart, EventPart, isPrimitive, NodePart, PropertyCommitter, PropertyPart} from './lib/parts.js'; | ||
@@ -26,0 +44,0 @@ export {RenderOptions} from './lib/render-options.js'; |
@@ -49,3 +49,4 @@ /** | ||
const capturedCreateElement = Document.prototype.createElement; | ||
Document.prototype.createElement = function createElement(tagName: string, options?: ElementCreationOptions) { | ||
Document.prototype.createElement = function createElement( | ||
tagName: string, options?: ElementCreationOptions) { | ||
let el = capturedCreateElement.call(this, tagName, options); | ||
@@ -52,0 +53,0 @@ if (el.localName === 'template') { |
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
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
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
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
15
55
475952
135
6548