Comparing version 0.10.0 to 0.10.1
@@ -15,2 +15,11 @@ # Change Log | ||
## [0.10.1] - 2018-06-13 | ||
* Added `noChange` - Value in favour of `directiveValue` (deprecated). | ||
* A `noChange` - Value signals that a value was handled by a directive and should not be written to the DOM | ||
* Updated shady-render to render styles in order, work with `@apply`, and work in browers where CSS Custom Properties must be polyfilled, like IE 11. | ||
* Introduced API to modify template contents safely without breaking template parts | ||
* `insertNodeIntoTemplate(template: Template, node: Node, refNode: Node|null)` | ||
* `removeNodesFromTemplate(template: Template, nodesToRemove: Set<Node>)` | ||
## [0.10.0] - 2018-05-03 | ||
@@ -24,3 +33,3 @@ * Added IE11 support | ||
specialization of template syntax is done in tags, not `render()`, allowing | ||
for the mixining of templates of different syntaxes, and for hooks in | ||
for the mixining of templates of different syntaxes, and for hooks in | ||
`render()` to change templates before they're initially processed. | ||
@@ -27,0 +36,0 @@ * Added ShadyCSS support in lib/shady-render.js. It's exported render function |
@@ -14,3 +14,3 @@ /** | ||
*/ | ||
import { NodePart } from '../lit-html.js'; | ||
import { DirectiveFn, NodePart } from '../lit-html.js'; | ||
/** | ||
@@ -33,2 +33,2 @@ * A directive that renders the items of an async iterable[1], appending new | ||
*/ | ||
export declare const asyncAppend: <T>(value: AsyncIterable<T>, mapper?: ((v: T, index?: number | undefined) => any) | undefined) => (part: NodePart) => Promise<void>; | ||
export declare const asyncAppend: <T>(value: AsyncIterable<T>, mapper?: ((v: T, index?: number | undefined) => any) | undefined) => DirectiveFn<NodePart>; |
@@ -16,4 +16,6 @@ /** | ||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); | ||
var m = o[Symbol.asyncIterator]; | ||
return m ? m.call(o) : typeof __values === "function" ? __values(o) : o[Symbol.iterator](); | ||
var m = o[Symbol.asyncIterator], i; | ||
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); | ||
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } | ||
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } | ||
}; | ||
@@ -39,2 +41,3 @@ import { directive, NodePart } from '../lit-html.js'; | ||
export const asyncAppend = (value, mapper) => directive(async (part) => { | ||
var e_1, _a; | ||
// If we've already set up this particular iterable, we don't need | ||
@@ -52,3 +55,3 @@ // to do anything. | ||
for (var value_1 = __asyncValues(value), value_1_1; value_1_1 = await value_1.next(), !value_1_1.done;) { | ||
let v = await value_1_1.value; | ||
let v = value_1_1.value; | ||
// When we get the first value, clear the part. This lets the previous | ||
@@ -100,4 +103,3 @@ // value display until we can replace it. | ||
} | ||
var e_1, _a; | ||
}); | ||
//# sourceMappingURL=async-append.js.map |
@@ -14,3 +14,3 @@ /** | ||
*/ | ||
import { NodePart } from '../lit-html.js'; | ||
import { DirectiveFn, NodePart } from '../lit-html.js'; | ||
/** | ||
@@ -34,2 +34,2 @@ * A directive that renders the items of an async iterable[1], replacing | ||
*/ | ||
export declare const asyncReplace: <T>(value: AsyncIterable<T>, mapper?: ((v: T, index?: number | undefined) => any) | undefined) => (part: NodePart) => Promise<void>; | ||
export declare const asyncReplace: <T>(value: AsyncIterable<T>, mapper?: ((v: T, index?: number | undefined) => any) | undefined) => DirectiveFn<NodePart>; |
@@ -16,4 +16,6 @@ /** | ||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); | ||
var m = o[Symbol.asyncIterator]; | ||
return m ? m.call(o) : typeof __values === "function" ? __values(o) : o[Symbol.iterator](); | ||
var m = o[Symbol.asyncIterator], i; | ||
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); | ||
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } | ||
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } | ||
}; | ||
@@ -40,2 +42,3 @@ import { directive, NodePart } from '../lit-html.js'; | ||
export const asyncReplace = (value, mapper) => directive(async (part) => { | ||
var e_1, _a; | ||
// If we've already set up this particular iterable, we don't need | ||
@@ -53,3 +56,3 @@ // to do anything. | ||
for (var value_1 = __asyncValues(value), value_1_1; value_1_1 = await value_1.next(), !value_1_1.done;) { | ||
let v = await value_1_1.value; | ||
let v = value_1_1.value; | ||
// When we get the first value, clear the part. This let's the | ||
@@ -83,4 +86,3 @@ // previous value display until we can replace it. | ||
} | ||
var e_1, _a; | ||
}); | ||
//# sourceMappingURL=async-replace.js.map |
@@ -14,3 +14,3 @@ /** | ||
*/ | ||
import { AttributePart, defaultPartCallback, directiveValue, getValue, SVGTemplateResult, TemplateResult } from '../lit-html.js'; | ||
import { AttributePart, defaultPartCallback, noChange, getValue, SVGTemplateResult, TemplateResult } from '../lit-html.js'; | ||
export { render } from '../lit-html.js'; | ||
@@ -81,3 +81,3 @@ /** | ||
const value = getValue(this, values[startIndex]); | ||
if (value === directiveValue) { | ||
if (value === noChange) { | ||
return; | ||
@@ -113,3 +113,3 @@ } | ||
} | ||
if (value !== directiveValue) { | ||
if (value !== noChange) { | ||
this.element[this.name] = value; | ||
@@ -116,0 +116,0 @@ } |
@@ -14,6 +14,6 @@ /** | ||
*/ | ||
import { DirectiveFn } from '../lit-html.js'; | ||
import { DirectiveFn, NodePart } from '../lit-html.js'; | ||
export declare type KeyFn<T> = (item: T) => any; | ||
export declare type ItemTemplate<T> = (item: T, index: number) => any; | ||
export declare function repeat<T>(items: T[], keyFn: KeyFn<T>, template: ItemTemplate<T>): DirectiveFn; | ||
export declare function repeat<T>(items: T[], template: ItemTemplate<T>): DirectiveFn; | ||
export declare function repeat<T>(items: T[], keyFn: KeyFn<T>, template: ItemTemplate<T>): DirectiveFn<NodePart>; | ||
export declare function repeat<T>(items: T[], template: ItemTemplate<T>): DirectiveFn<NodePart>; |
@@ -30,5 +30,2 @@ /** | ||
return directive((part) => { | ||
if (!(part instanceof NodePart)) { | ||
throw new Error('repeat can only be used on NodeParts'); | ||
} | ||
let keyMap = keyMapCache.get(part); | ||
@@ -35,0 +32,0 @@ if (keyMap === undefined) { |
@@ -16,7 +16,9 @@ /** | ||
export { html, svg, TemplateResult } from '../lit-html.js'; | ||
declare global { | ||
declare global { | ||
interface Window { | ||
ShadyCSS: any; | ||
} | ||
class ShadowRoot { | ||
} | ||
} | ||
export declare function render(result: TemplateResult, container: Element | DocumentFragment, scopeName: string): void; |
@@ -14,6 +14,13 @@ /** | ||
*/ | ||
import { render as baseRender, Template, templateCaches } from '../lit-html.js'; | ||
import { removeNodes, Template, templateCaches, TemplateInstance } from '../lit-html.js'; | ||
import { insertNodeIntoTemplate, removeNodesFromTemplate } from './modify-template.js'; | ||
export { html, svg, TemplateResult } from '../lit-html.js'; | ||
// Get a key to lookup in `templateCaches`. | ||
const getTemplateCacheKey = (type, scopeName) => `${type}--${scopeName}`; | ||
/** | ||
* Template factory which scopes template DOM using ShadyCSS. | ||
* @param scopeName {string} | ||
*/ | ||
const shadyTemplateFactory = (scopeName) => (result) => { | ||
const cacheKey = `${result.type}--${scopeName}`; | ||
const cacheKey = getTemplateCacheKey(result.type, scopeName); | ||
let templateCache = templateCaches.get(cacheKey); | ||
@@ -27,5 +34,3 @@ if (templateCache === undefined) { | ||
const element = result.getTemplateElement(); | ||
if (typeof window.ShadyCSS === 'object') { | ||
window.ShadyCSS.prepareTemplate(element, scopeName); | ||
} | ||
window.ShadyCSS.prepareTemplateDom(element, scopeName); | ||
template = new Template(result, element); | ||
@@ -36,5 +41,89 @@ templateCache.set(result.strings, template); | ||
}; | ||
const TEMPLATE_TYPES = ['html', 'svg']; | ||
/** | ||
* Removes all style elements from Templates for the given scopeName. | ||
*/ | ||
function removeStylesFromLitTemplates(scopeName) { | ||
TEMPLATE_TYPES.forEach((type) => { | ||
const templates = templateCaches.get(getTemplateCacheKey(type, scopeName)); | ||
if (templates !== undefined) { | ||
templates.forEach((template) => { | ||
const { element: { content } } = template; | ||
const styles = content.querySelectorAll('style'); | ||
removeNodesFromTemplate(template, new Set(Array.from(styles))); | ||
}); | ||
} | ||
}); | ||
} | ||
const shadyRenderSet = new Set(); | ||
/** | ||
* For the given scope name, ensures that ShadyCSS style scoping is performed. | ||
* This is done just once per scope name so the fragment and template cannot | ||
* be modified. | ||
* (1) extracts styles from the rendered fragment and hands them to ShadyCSS | ||
* to be scoped and appended to the document | ||
* (2) removes style elements from all lit-html Templates for this scope name. | ||
* | ||
* Note, <style> elements can only be placed into templates for the | ||
* initial rendering of the scope. If <style> elements are included in templates | ||
* dynamically rendered to the scope (after the first scope render), they will | ||
* not be scoped and the <style> will be left in the template and rendered output. | ||
*/ | ||
const ensureStylesScoped = (fragment, template, scopeName) => { | ||
// only scope element template once per scope name | ||
if (!shadyRenderSet.has(scopeName)) { | ||
shadyRenderSet.add(scopeName); | ||
const styleTemplate = document.createElement('template'); | ||
Array.from(fragment.querySelectorAll('style')).forEach((s) => { | ||
styleTemplate.content.appendChild(s); | ||
}); | ||
window.ShadyCSS.prepareTemplateStyles(styleTemplate, scopeName); | ||
// Fix templates: note the expectation here is that the given `fragment` | ||
// has been generated from the given `template` which contains | ||
// the set of templates rendered into this scope. | ||
// It is only from this set of initial templates from which styles | ||
// will be scoped and removed. | ||
removeStylesFromLitTemplates(scopeName); | ||
// ApplyShim case | ||
if (window.ShadyCSS.nativeShadow) { | ||
const style = styleTemplate.content.querySelector('style'); | ||
if (style !== null) { | ||
// Insert style into rendered fragment | ||
fragment.insertBefore(style, fragment.firstChild); | ||
// Insert into lit-template (for subsequent renders) | ||
insertNodeIntoTemplate(template, style.cloneNode(true), template.element.content.firstChild); | ||
} | ||
} | ||
} | ||
}; | ||
// NOTE: We're copying code from lit-html's `render` method here. | ||
// We're doing this explicitly because the API for rendering templates is likely | ||
// to change in the near term. | ||
export function render(result, container, scopeName) { | ||
return baseRender(result, container, shadyTemplateFactory(scopeName)); | ||
const templateFactory = shadyTemplateFactory(scopeName); | ||
const template = templateFactory(result); | ||
let instance = container.__templateInstance; | ||
// Repeat render, just call update() | ||
if (instance !== undefined && instance.template === template && | ||
instance._partCallback === result.partCallback) { | ||
instance.update(result.values); | ||
return; | ||
} | ||
// First render, create a new TemplateInstance and append it | ||
instance = | ||
new TemplateInstance(template, result.partCallback, templateFactory); | ||
container.__templateInstance = instance; | ||
const fragment = instance._clone(); | ||
instance.update(result.values); | ||
const host = container instanceof ShadowRoot ? | ||
container.host : | ||
undefined; | ||
// If there's a shadow host, do ShadyCSS scoping... | ||
if (host !== undefined && typeof window.ShadyCSS === 'object') { | ||
ensureStylesScoped(fragment, template, scopeName); | ||
window.ShadyCSS.styleElement(host); | ||
} | ||
removeNodes(container, container.firstChild); | ||
container.appendChild(fragment); | ||
} | ||
//# sourceMappingURL=shady-render.js.map |
@@ -14,3 +14,3 @@ /** | ||
*/ | ||
import { NodePart } from '../lit-html.js'; | ||
import { DirectiveFn, NodePart } from '../lit-html.js'; | ||
/** | ||
@@ -23,2 +23,2 @@ * Renders the result as HTML, rather than text. | ||
*/ | ||
export declare const unsafeHTML: (value: any) => (part: NodePart) => void; | ||
export declare const unsafeHTML: (value: any) => DirectiveFn<NodePart>; |
@@ -14,6 +14,6 @@ /** | ||
*/ | ||
import { NodePart } from '../lit-html.js'; | ||
import { DirectiveFn, NodePart } from '../lit-html.js'; | ||
/** | ||
* Display `defaultContent` until `promise` resolves. | ||
*/ | ||
export declare const until: (promise: Promise<any>, defaultContent: any) => (part: NodePart) => void; | ||
export declare const until: (promise: Promise<any>, defaultContent: any) => DirectiveFn<NodePart>; |
@@ -56,3 +56,3 @@ /** | ||
* This is a hook into the template-creation process for rendering that | ||
* requires some modification of templates before their used, like ShadyCSS, | ||
* requires some modification of templates before they're used, like ShadyCSS, | ||
* which must add classes to elements and remove styles. | ||
@@ -79,2 +79,5 @@ * | ||
export declare function defaultTemplateFactory(result: TemplateResult): Template; | ||
export declare type TemplateContainer = (Element | DocumentFragment) & { | ||
__templateInstance?: TemplateInstance; | ||
}; | ||
/** | ||
@@ -114,7 +117,8 @@ * Renders a template to a container. | ||
index: number; | ||
name: string | undefined; | ||
rawName: string | undefined; | ||
strings: string[] | undefined; | ||
name?: string | undefined; | ||
rawName?: string | undefined; | ||
strings?: string[] | undefined; | ||
constructor(type: string, index: number, name?: string | undefined, rawName?: string | undefined, strings?: string[] | undefined); | ||
} | ||
export declare const isTemplatePartActive: (part: TemplatePart) => boolean; | ||
/** | ||
@@ -136,4 +140,7 @@ * An updateable Template that tracks the location of dynamic parts. | ||
export declare const getValue: (part: Part, value: any) => any; | ||
export declare type DirectiveFn<P extends Part = Part> = (part: P) => any; | ||
export declare const directive: <P extends Part = Part, F = DirectiveFn<P>>(f: F) => F; | ||
export interface DirectiveFn<P = Part> { | ||
(part: P): void; | ||
__litDirective?: true; | ||
} | ||
export declare const directive: <P = Part>(f: DirectiveFn<P>) => DirectiveFn<P>; | ||
/** | ||
@@ -143,3 +150,7 @@ * A sentinel value that signals that a value was handled by a directive and | ||
*/ | ||
export declare const directiveValue: {}; | ||
export declare const noChange: {}; | ||
/** | ||
* @deprecated Use `noChange` instead. | ||
*/ | ||
export { noChange as directiveValue }; | ||
export interface Part { | ||
@@ -174,8 +185,8 @@ instance: TemplateInstance; | ||
setValue(value: any): void; | ||
private _insert(node); | ||
private _setNode(value); | ||
private _setText(value); | ||
private _setTemplateResult(value); | ||
private _setIterable(value); | ||
private _setPromise(value); | ||
private _insert; | ||
private _setNode; | ||
private _setText; | ||
private _setTemplateResult; | ||
private _setIterable; | ||
private _setPromise; | ||
clear(startNode?: Node): void; | ||
@@ -190,3 +201,3 @@ } | ||
export declare class TemplateInstance { | ||
_parts: Part[]; | ||
_parts: Array<Part | undefined>; | ||
_partCallback: PartCallback; | ||
@@ -193,0 +204,0 @@ _getTemplate: TemplateFactory; |
@@ -140,3 +140,3 @@ /** | ||
/** | ||
* An expression marker used text-posisitions, not attribute positions, | ||
* An expression marker used text-positions, not attribute positions, | ||
* in template. | ||
@@ -209,2 +209,3 @@ */ | ||
} | ||
export const isTemplatePartActive = (part) => part.index !== -1; | ||
/** | ||
@@ -250,3 +251,3 @@ * An updateable Template that tracks the location of dynamic parts. | ||
// Get the template literal section leading up to the first | ||
// expression in this attribute attribute | ||
// expression in this attribute | ||
const stringForPart = result.strings[partIndex]; | ||
@@ -341,3 +342,3 @@ // Find the attribute name | ||
value = value(part); | ||
return directiveValue; | ||
return noChange; | ||
} | ||
@@ -355,3 +356,7 @@ return value === null ? undefined : value; | ||
*/ | ||
export const directiveValue = {}; | ||
export const noChange = {}; | ||
/** | ||
* @deprecated Use `noChange` instead. | ||
*/ | ||
export { noChange as directiveValue }; | ||
const isPrimitiveValue = (value) => value === null || | ||
@@ -375,3 +380,3 @@ !(typeof value === 'object' || typeof value === 'function'); | ||
const v = getValue(this, values[startIndex + i]); | ||
if (v && v !== directiveValue && | ||
if (v && v !== noChange && | ||
(Array.isArray(v) || typeof v !== 'string' && v[Symbol.iterator])) { | ||
@@ -415,3 +420,3 @@ for (const t of v) { | ||
} | ||
if (value !== directiveValue) { | ||
if (value !== noChange) { | ||
this.element.setAttribute(this.name, value); | ||
@@ -431,3 +436,3 @@ } | ||
value = getValue(this, value); | ||
if (value === directiveValue) { | ||
if (value === noChange) { | ||
return; | ||
@@ -586,3 +591,6 @@ } | ||
for (const part of this._parts) { | ||
if (part.size === undefined) { | ||
if (!part) { | ||
valueIndex++; | ||
} | ||
else if (part.size === undefined) { | ||
part.setValue(values[valueIndex]); | ||
@@ -598,3 +606,6 @@ valueIndex++; | ||
_clone() { | ||
const fragment = document.importNode(this.template.element.content, true); | ||
// Clone the node, rather than importing it, to keep the fragment in the | ||
// template's document. This leaves the fragment inert so custom elements | ||
// won't upgrade until after the main document adopts the node. | ||
const fragment = this.template.element.content.cloneNode(true); | ||
const parts = this.template.parts; | ||
@@ -609,7 +620,11 @@ if (parts.length > 0) { | ||
const part = parts[i]; | ||
while (index < part.index) { | ||
index++; | ||
walker.nextNode(); | ||
const partActive = isTemplatePartActive(part); | ||
// An inactive part has no coresponding Template node. | ||
if (partActive) { | ||
while (index < part.index) { | ||
index++; | ||
walker.nextNode(); | ||
} | ||
} | ||
this._parts.push(this._partCallback(this, part, walker.currentNode)); | ||
this._parts.push(partActive ? this._partCallback(this, part, walker.currentNode) : undefined); | ||
} | ||
@@ -616,0 +631,0 @@ } |
{ | ||
"name": "lit-html", | ||
"version": "0.10.0", | ||
"version": "0.10.1", | ||
"description": "HTML template literals in JavaScript", | ||
@@ -33,4 +33,4 @@ "license": "BSD-3-Clause", | ||
"@types/chai": "^4.1.0", | ||
"@types/mocha": "^2.2.46", | ||
"@webcomponents/shadycss": "^1.1.0", | ||
"@types/mocha": "^5.2.0", | ||
"@webcomponents/shadycss": "^1.2.1", | ||
"@webcomponents/shadydom": "^1.0.11", | ||
@@ -40,9 +40,9 @@ "@webcomponents/template": "^1.2.2", | ||
"chai": "^4.1.2", | ||
"mocha": "^3.5.3", | ||
"mocha": "^5.2.0", | ||
"tslint": "^5.9.1", | ||
"typedoc": "^0.9.0", | ||
"typedoc": "^0.11.1", | ||
"typescript": "^2.8.3", | ||
"uglify-es": "^3.3.5", | ||
"wct-browser-legacy": "0.0.1-pre.12", | ||
"web-component-tester": "^6.6.0-pre.5" | ||
"wct-browser-legacy": "^1.0.1", | ||
"web-component-tester": "^6.6.0" | ||
}, | ||
@@ -49,0 +49,0 @@ "typings": "lit-html.d.ts", |
@@ -0,1 +1,4 @@ | ||
> ## 🛠 Status: In Development | ||
> lit-html is currently in development. It's on the fast track to a 1.0 release, so we encourage you to use it and give us your feedback, but there are things that haven't been finalized yet and you can expect some changes. | ||
# lit-html | ||
@@ -5,2 +8,3 @@ HTML templates, via JavaScript template literals | ||
[![Build Status](https://travis-ci.org/Polymer/lit-html.svg?branch=master)](https://travis-ci.org/Polymer/lit-html) | ||
[![Published on npm](https://img.shields.io/npm/v/lit-html.svg)](https://www.npmjs.com/package/lit-html) | ||
@@ -11,2 +15,4 @@ ## Overview | ||
Visit the [lit-html documentation](https://polymer.github.io/lit-html). | ||
```javascript | ||
@@ -126,3 +132,3 @@ import {html, render} from 'lit-html'; | ||
To create partial SVG templates - template that will rendering inside and `<svg>` tag (in the SVG namespace), use the `svg` template tag instead of the `html` template tag: | ||
To create partial SVG templates (templates that will render inside an `<svg>` tag in the SVG namespace), use the `svg` template tag instead of the `html` template tag: | ||
@@ -132,4 +138,4 @@ ```javascript | ||
<g> | ||
${[0, 10, 20].map((x) => svg`<line x1=${x} y1="0" x2=${x} y2="20"/>`)} | ||
${[0, 10, 20].map((y) => svg`<line x1="0" y1=${y} x2="0" y2=${y}/>`)} | ||
${[0, 10, 20].map((x) => svg`<line x1=${x} y1="0" x2=${x} y2="20" stroke="#000"/>`)} | ||
${[0, 10, 20].map((y) => svg`<line x1="0" y1=${y} x2="20" y2=${y} stroke="#000"/>`)} | ||
</g> | ||
@@ -326,5 +332,5 @@ `; | ||
`lit-html` is very new, under initial development, and not production-ready. | ||
`lit-html` is still under development. | ||
* It uses JavaScript modules, and there's no build set up yet, so out-of-the-box it only runs in Safari 10.1, Chrome 61, and Firefox 54 (behind a flag). | ||
* It uses JavaScript modules, and there's no build set up yet, so out-of-the-box it only runs in Safari 10.1, Chrome 61, and Firefox 60 and Edge. | ||
* It has a growing test suite, but it has only been run manually on Chrome Canary, Safari 10.1 and Firefox 54. | ||
@@ -331,0 +337,0 @@ * Much more test coverage is needed for complex templates, especially template composition and Function and Iterable values. |
@@ -15,3 +15,3 @@ /** | ||
import {directive, NodePart} from '../lit-html.js'; | ||
import {directive, DirectiveFn, NodePart} from '../lit-html.js'; | ||
@@ -36,3 +36,3 @@ /** | ||
export const asyncAppend = <T>( | ||
value: AsyncIterable<T>, mapper?: (v: T, index?: number) => any) => | ||
value: AsyncIterable<T>, mapper?: (v: T, index?: number) => any): DirectiveFn<NodePart> => | ||
directive(async (part: NodePart) => { | ||
@@ -39,0 +39,0 @@ // If we've already set up this particular iterable, we don't need |
@@ -15,3 +15,3 @@ /** | ||
import {directive, NodePart} from '../lit-html.js'; | ||
import {directive, DirectiveFn, NodePart} from '../lit-html.js'; | ||
@@ -37,3 +37,3 @@ /** | ||
export const asyncReplace = | ||
<T>(value: AsyncIterable<T>, mapper?: (v: T, index?: number) => any) => | ||
<T>(value: AsyncIterable<T>, mapper?: (v: T, index?: number) => any): DirectiveFn<NodePart> => | ||
directive(async (part: NodePart) => { | ||
@@ -40,0 +40,0 @@ // If we've already set up this particular iterable, we don't need |
@@ -15,3 +15,3 @@ /** | ||
import {AttributePart, defaultPartCallback, directiveValue, getValue, Part, SVGTemplateResult, TemplateInstance, TemplatePart, TemplateResult} from '../lit-html.js'; | ||
import {AttributePart, defaultPartCallback, noChange, getValue, Part, SVGTemplateResult, TemplateInstance, TemplatePart, TemplateResult} from '../lit-html.js'; | ||
@@ -97,3 +97,3 @@ export {render} from '../lit-html.js'; | ||
const value = getValue(this, values[startIndex]); | ||
if (value === directiveValue) { | ||
if (value === noChange) { | ||
return; | ||
@@ -128,3 +128,3 @@ } | ||
} | ||
if (value !== directiveValue) { | ||
if (value !== noChange) { | ||
(this.element as any)[this.name] = value; | ||
@@ -131,0 +131,0 @@ } |
@@ -15,3 +15,3 @@ /** | ||
import {directive, DirectiveFn, NodePart, Part, removeNodes, reparentNodes} from '../lit-html.js'; | ||
import {directive, DirectiveFn, NodePart, removeNodes, reparentNodes} from '../lit-html.js'; | ||
@@ -30,8 +30,8 @@ export type KeyFn<T> = (item: T) => any; | ||
export function repeat<T>( | ||
items: T[], keyFn: KeyFn<T>, template: ItemTemplate<T>): DirectiveFn; | ||
export function repeat<T>(items: T[], template: ItemTemplate<T>): DirectiveFn; | ||
items: T[], keyFn: KeyFn<T>, template: ItemTemplate<T>): DirectiveFn<NodePart>; | ||
export function repeat<T>(items: T[], template: ItemTemplate<T>): DirectiveFn<NodePart>; | ||
export function repeat<T>( | ||
items: Iterable<T>, | ||
keyFnOrTemplate: KeyFn<T>| ItemTemplate<T>, | ||
template?: ItemTemplate<T>): DirectiveFn { | ||
template?: ItemTemplate<T>): DirectiveFn<NodePart> { | ||
let keyFn: KeyFn<T>; | ||
@@ -44,7 +44,3 @@ if (arguments.length === 2) { | ||
return directive((part: Part): any => { | ||
if (!(part instanceof NodePart)) { | ||
throw new Error('repeat can only be used on NodeParts'); | ||
} | ||
return directive((part: NodePart): void => { | ||
let keyMap = keyMapCache.get(part); | ||
@@ -51,0 +47,0 @@ if (keyMap === undefined) { |
@@ -15,4 +15,6 @@ /** | ||
import {render as baseRender, Template, templateCaches, TemplateResult} from '../lit-html.js'; | ||
import {removeNodes, Template, templateCaches, TemplateContainer, TemplateInstance, TemplateResult} from '../lit-html.js'; | ||
import {insertNodeIntoTemplate, removeNodesFromTemplate} from './modify-template.js'; | ||
export {html, svg, TemplateResult} from '../lit-html.js'; | ||
@@ -24,7 +26,16 @@ | ||
} | ||
class ShadowRoot {} | ||
} | ||
// Get a key to lookup in `templateCaches`. | ||
const getTemplateCacheKey = (type: string, scopeName: string) => | ||
`${type}--${scopeName}`; | ||
/** | ||
* Template factory which scopes template DOM using ShadyCSS. | ||
* @param scopeName {string} | ||
*/ | ||
const shadyTemplateFactory = (scopeName: string) => | ||
(result: TemplateResult) => { | ||
const cacheKey = `${result.type}--${scopeName}`; | ||
const cacheKey = getTemplateCacheKey(result.type, scopeName); | ||
let templateCache = templateCaches.get(cacheKey); | ||
@@ -38,7 +49,3 @@ if (templateCache === undefined) { | ||
const element = result.getTemplateElement(); | ||
if (typeof window.ShadyCSS === 'object') { | ||
window.ShadyCSS.prepareTemplate(element, scopeName); | ||
} | ||
window.ShadyCSS.prepareTemplateDom(element, scopeName); | ||
template = new Template(result, element); | ||
@@ -50,2 +57,71 @@ templateCache.set(result.strings, template); | ||
const TEMPLATE_TYPES = ['html', 'svg']; | ||
/** | ||
* Removes all style elements from Templates for the given scopeName. | ||
*/ | ||
function removeStylesFromLitTemplates(scopeName: string) { | ||
TEMPLATE_TYPES.forEach((type) => { | ||
const templates = templateCaches.get(getTemplateCacheKey(type, scopeName)); | ||
if (templates !== undefined) { | ||
templates.forEach((template) => { | ||
const {element: {content}} = template; | ||
const styles = content.querySelectorAll('style'); | ||
removeNodesFromTemplate(template, new Set(Array.from(styles))); | ||
}); | ||
} | ||
}); | ||
} | ||
const shadyRenderSet = new Set<string>(); | ||
/** | ||
* For the given scope name, ensures that ShadyCSS style scoping is performed. | ||
* This is done just once per scope name so the fragment and template cannot | ||
* be modified. | ||
* (1) extracts styles from the rendered fragment and hands them to ShadyCSS | ||
* to be scoped and appended to the document | ||
* (2) removes style elements from all lit-html Templates for this scope name. | ||
* | ||
* Note, <style> elements can only be placed into templates for the | ||
* initial rendering of the scope. If <style> elements are included in templates | ||
* dynamically rendered to the scope (after the first scope render), they will | ||
* not be scoped and the <style> will be left in the template and rendered output. | ||
*/ | ||
const ensureStylesScoped = | ||
(fragment: DocumentFragment, template: Template, scopeName: string) => { | ||
// only scope element template once per scope name | ||
if (!shadyRenderSet.has(scopeName)) { | ||
shadyRenderSet.add(scopeName); | ||
const styleTemplate = document.createElement('template'); | ||
Array.from(fragment.querySelectorAll('style')).forEach((s: Element) => { | ||
styleTemplate.content.appendChild(s); | ||
}); | ||
window.ShadyCSS.prepareTemplateStyles(styleTemplate, scopeName); | ||
// Fix templates: note the expectation here is that the given `fragment` | ||
// has been generated from the given `template` which contains | ||
// the set of templates rendered into this scope. | ||
// It is only from this set of initial templates from which styles | ||
// will be scoped and removed. | ||
removeStylesFromLitTemplates(scopeName); | ||
// ApplyShim case | ||
if (window.ShadyCSS.nativeShadow) { | ||
const style = styleTemplate.content.querySelector('style'); | ||
if (style !== null) { | ||
// Insert style into rendered fragment | ||
fragment.insertBefore(style, fragment.firstChild); | ||
// Insert into lit-template (for subsequent renders) | ||
insertNodeIntoTemplate( | ||
template, | ||
style.cloneNode(true), | ||
template.element.content.firstChild); | ||
} | ||
} | ||
} | ||
}; | ||
// NOTE: We're copying code from lit-html's `render` method here. | ||
// We're doing this explicitly because the API for rendering templates is likely | ||
// to change in the near term. | ||
export function render( | ||
@@ -55,3 +131,33 @@ result: TemplateResult, | ||
scopeName: string) { | ||
return baseRender(result, container, shadyTemplateFactory(scopeName)); | ||
} | ||
const templateFactory = shadyTemplateFactory(scopeName); | ||
const template = templateFactory(result); | ||
let instance = (container as TemplateContainer).__templateInstance; | ||
// Repeat render, just call update() | ||
if (instance !== undefined && instance.template === template && | ||
instance._partCallback === result.partCallback) { | ||
instance.update(result.values); | ||
return; | ||
} | ||
// First render, create a new TemplateInstance and append it | ||
instance = | ||
new TemplateInstance(template, result.partCallback, templateFactory); | ||
(container as TemplateContainer).__templateInstance = instance; | ||
const fragment = instance._clone(); | ||
instance.update(result.values); | ||
const host = container instanceof ShadowRoot ? | ||
container.host : | ||
undefined; | ||
// If there's a shadow host, do ShadyCSS scoping... | ||
if (host !== undefined && typeof window.ShadyCSS === 'object') { | ||
ensureStylesScoped(fragment, template, scopeName); | ||
window.ShadyCSS.styleElement(host); | ||
} | ||
removeNodes(container, container.firstChild); | ||
container.appendChild(fragment); | ||
} |
@@ -15,3 +15,3 @@ /** | ||
import {directive, NodePart} from '../lit-html.js'; | ||
import {directive, DirectiveFn, NodePart} from '../lit-html.js'; | ||
@@ -25,6 +25,7 @@ /** | ||
*/ | ||
export const unsafeHTML = (value: any) => directive((part: NodePart) => { | ||
const tmp = document.createElement('template'); | ||
tmp.innerHTML = value; | ||
part.setValue(document.importNode(tmp.content, true)); | ||
}); | ||
export const unsafeHTML = (value: any): DirectiveFn<NodePart> => | ||
directive((part: NodePart): void => { | ||
const tmp = document.createElement('template'); | ||
tmp.innerHTML = value; | ||
part.setValue(document.importNode(tmp.content, true)); | ||
}); |
@@ -15,3 +15,3 @@ /** | ||
import {directive, NodePart} from '../lit-html.js'; | ||
import {directive, DirectiveFn, NodePart} from '../lit-html.js'; | ||
@@ -21,6 +21,6 @@ /** | ||
*/ | ||
export const until = (promise: Promise<any>, defaultContent: any) => | ||
directive((part: NodePart) => { | ||
export const until = (promise: Promise<any>, defaultContent: any): DirectiveFn<NodePart> => | ||
directive((part: NodePart): void => { | ||
part.setValue(defaultContent); | ||
part.setValue(promise); | ||
}); |
@@ -107,3 +107,3 @@ /** | ||
* This is a hook into the template-creation process for rendering that | ||
* requires some modification of templates before their used, like ShadyCSS, | ||
* requires some modification of templates before they're used, like ShadyCSS, | ||
* which must add classes to elements and remove styles. | ||
@@ -144,2 +144,6 @@ * | ||
export type TemplateContainer = (Element|DocumentFragment)&{ | ||
__templateInstance?: TemplateInstance; | ||
}; | ||
/** | ||
@@ -162,5 +166,6 @@ * Renders a template to a container. | ||
container: Element|DocumentFragment, | ||
templateFactory: TemplateFactory = defaultTemplateFactory) { | ||
templateFactory: TemplateFactory = defaultTemplateFactory | ||
) { | ||
const template = templateFactory(result); | ||
let instance = (container as any).__templateInstance as any; | ||
let instance = (container as TemplateContainer).__templateInstance; | ||
@@ -177,3 +182,3 @@ // Repeat render, just call update() | ||
new TemplateInstance(template, result.partCallback, templateFactory); | ||
(container as any).__templateInstance = instance; | ||
(container as TemplateContainer).__templateInstance = instance; | ||
@@ -194,3 +199,3 @@ const fragment = instance._clone(); | ||
/** | ||
* An expression marker used text-posisitions, not attribute positions, | ||
* An expression marker used text-positions, not attribute positions, | ||
* in template. | ||
@@ -266,2 +271,4 @@ */ | ||
export const isTemplatePartActive = (part: TemplatePart) => part.index !== -1; | ||
/** | ||
@@ -316,3 +323,3 @@ * An updateable Template that tracks the location of dynamic parts. | ||
// Get the template literal section leading up to the first | ||
// expression in this attribute attribute | ||
// expression in this attribute | ||
const stringForPart = result.strings[partIndex]; | ||
@@ -419,3 +426,3 @@ // Find the attribute name | ||
value = value(part); | ||
return directiveValue; | ||
return noChange; | ||
} | ||
@@ -425,9 +432,11 @@ return value === null ? undefined : value; | ||
export type DirectiveFn<P extends Part = Part> = (part: P) => any; | ||
export interface DirectiveFn<P= Part> { | ||
(part: P): void; | ||
__litDirective?: true; | ||
} | ||
export const directive = | ||
<P extends Part = Part, F = DirectiveFn<P>>(f: F): F => { | ||
(f as any).__litDirective = true; | ||
return f; | ||
}; | ||
export const directive = <P= Part>(f: DirectiveFn<P>): DirectiveFn<P> => { | ||
f.__litDirective = true; | ||
return f; | ||
}; | ||
@@ -441,4 +450,9 @@ const isDirective = (o: any) => | ||
*/ | ||
export const directiveValue = {}; | ||
export const noChange = {}; | ||
/** | ||
* @deprecated Use `noChange` instead. | ||
*/ | ||
export { noChange as directiveValue }; | ||
const isPrimitiveValue = (value: any) => value === null || | ||
@@ -486,3 +500,3 @@ !(typeof value === 'object' || typeof value === 'function'); | ||
const v = getValue(this, values[startIndex + i]); | ||
if (v && v !== directiveValue && | ||
if (v && v !== noChange && | ||
(Array.isArray(v) || typeof v !== 'string' && v[Symbol.iterator])) { | ||
@@ -526,3 +540,3 @@ for (const t of v) { | ||
} | ||
if (value !== directiveValue) { | ||
if (value !== noChange) { | ||
this.element.setAttribute(this.name, value); | ||
@@ -549,3 +563,3 @@ } | ||
value = getValue(this, value); | ||
if (value === directiveValue) { | ||
if (value === noChange) { | ||
return; | ||
@@ -707,2 +721,3 @@ } | ||
/** | ||
@@ -713,3 +728,3 @@ * An instance of a `Template` that can be attached to the DOM and updated | ||
export class TemplateInstance { | ||
_parts: Part[] = []; | ||
_parts: Array<Part|undefined> = []; | ||
_partCallback: PartCallback; | ||
@@ -730,3 +745,5 @@ _getTemplate: TemplateFactory; | ||
for (const part of this._parts) { | ||
if (part.size === undefined) { | ||
if (!part) { | ||
valueIndex++; | ||
} else if (part.size === undefined) { | ||
(part as SinglePart).setValue(values[valueIndex]); | ||
@@ -742,3 +759,6 @@ valueIndex++; | ||
_clone(): DocumentFragment { | ||
const fragment = document.importNode(this.template.element.content, true); | ||
// Clone the node, rather than importing it, to keep the fragment in the | ||
// template's document. This leaves the fragment inert so custom elements | ||
// won't upgrade until after the main document adopts the node. | ||
const fragment = this.template.element.content.cloneNode(true) as DocumentFragment; | ||
const parts = this.template.parts; | ||
@@ -760,7 +780,11 @@ | ||
const part = parts[i]; | ||
while (index < part.index) { | ||
index++; | ||
walker.nextNode(); | ||
const partActive = isTemplatePartActive(part); | ||
// An inactive part has no coresponding Template node. | ||
if (partActive) { | ||
while (index < part.index) { | ||
index++; | ||
walker.nextNode(); | ||
} | ||
} | ||
this._parts.push(this._partCallback(this, part, walker.currentNode)); | ||
this._parts.push(partActive ? this._partCallback(this, part, walker.currentNode) : undefined); | ||
} | ||
@@ -767,0 +791,0 @@ } |
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
243219
40
3268
429
1