lit-html
Advanced tools
Comparing version 0.14.0 to 1.0.0-dev.c8753bc
@@ -19,4 +19,19 @@ # Change Log | ||
## Unreleased | ||
## [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 | ||
### Changed | ||
* `until()` can now take any number of sync or async arguments. ([#555](https://github.com/Polymer/lit-html/pull/555)) | ||
@@ -23,0 +38,0 @@ * [Breaking] `guard()` supports multiple dependencies. If the first argument to `guard()` is an array, the array items are checked for equality to previous values. ([#666](https://github.com/Polymer/lit-html/pull/666)) |
@@ -32,3 +32,3 @@ /** | ||
*/ | ||
export declare const asyncAppend: <T>(value: AsyncIterable<T>, mapper?: ((v: T, index?: number | undefined) => any) | undefined) => (part: Part) => Promise<void>; | ||
export declare const asyncAppend: (value: AsyncIterable<{}>, mapper?: ((v: {}, index?: number | undefined) => unknown) | undefined) => (part: Part) => Promise<void>; | ||
//# sourceMappingURL=async-append.d.ts.map |
@@ -72,2 +72,4 @@ /** | ||
if (mapper !== undefined) { | ||
// This is safe because T must otherwise be treated as unknown by | ||
// the rest of the system. | ||
v = mapper(v, i); | ||
@@ -74,0 +76,0 @@ } |
@@ -33,3 +33,3 @@ /** | ||
*/ | ||
export declare const asyncReplace: <T>(value: AsyncIterable<T>, mapper?: ((v: T, index?: number | undefined) => any) | undefined) => (part: Part) => Promise<void>; | ||
export declare const asyncReplace: (value: AsyncIterable<{}>, mapper?: ((v: {}, index?: number | undefined) => unknown) | undefined) => (part: Part) => Promise<void>; | ||
//# sourceMappingURL=async-replace.d.ts.map |
@@ -74,2 +74,4 @@ /** | ||
if (mapper !== undefined) { | ||
// This is safe because T must otherwise be treated as unknown by | ||
// the rest of the system. | ||
v = mapper(v, i); | ||
@@ -76,0 +78,0 @@ } |
@@ -29,3 +29,3 @@ /** | ||
*/ | ||
export declare const cache: (value: any) => (part: Part) => void; | ||
export declare const cache: (value: unknown) => (part: Part) => void; | ||
//# sourceMappingURL=cache.d.ts.map |
@@ -21,3 +21,3 @@ /** | ||
*/ | ||
export declare const ifDefined: (value: any) => (part: Part) => void; | ||
export declare const ifDefined: (value: unknown) => (part: Part) => void; | ||
//# sourceMappingURL=if-defined.d.ts.map |
@@ -15,4 +15,4 @@ /** | ||
import { DirectiveFn } from '../lib/directive.js'; | ||
export declare type KeyFn<T> = (item: T, index: number) => any; | ||
export declare type ItemTemplate<T> = (item: T, index: number) => any; | ||
export declare type KeyFn<T> = (item: T, index: number) => unknown; | ||
export declare type ItemTemplate<T> = (item: T, index: number) => unknown; | ||
/** | ||
@@ -19,0 +19,0 @@ * A directive that repeats a series of values (usually `TemplateResults`) |
@@ -87,12 +87,12 @@ /** | ||
} | ||
// Old part & key lists are retrieved from the last update (associated | ||
// with the part for this instance of the directive) | ||
// Old part & key lists are retrieved from the last update | ||
// (associated with the part for this instance of the directive) | ||
const oldParts = partListCache.get(containerPart) || []; | ||
const oldKeys = keyListCache.get(containerPart) || []; | ||
// New part list will be built up as we go (either reused from old parts | ||
// or created for new keys in this update). This is saved in the above | ||
// cache at the end of the update. | ||
// New part list will be built up as we go (either reused from | ||
// old parts or created for new keys in this update). This is | ||
// saved in the above cache at the end of the update. | ||
const newParts = []; | ||
// New value list is eagerly generated from items along with a parallel | ||
// array indicating its key. | ||
// New value list is eagerly generated from items along with a | ||
// parallel array indicating its key. | ||
const newValues = []; | ||
@@ -106,6 +106,6 @@ const newKeys = []; | ||
} | ||
// Maps from key to index for current and previous update; these are | ||
// generated lazily only when needed as a performance optimization, | ||
// since they are only required for multiple non-contiguous changes in | ||
// the list, which are less common. | ||
// Maps from key to index for current and previous update; these | ||
// are generated lazily only when needed as a performance | ||
// optimization, since they are only required for multiple | ||
// non-contiguous changes in the list, which are less common. | ||
let newKeyToIndexMap; | ||
@@ -118,13 +118,15 @@ let oldKeyToIndexMap; | ||
let newTail = newValues.length - 1; | ||
// Overview of O(n) reconciliation algorithm (general approach based on | ||
// ideas found in ivi, vue, snabbdom, etc.): | ||
// Overview of O(n) reconciliation algorithm (general approach | ||
// based on ideas found in ivi, vue, snabbdom, etc.): | ||
// | ||
// * We start with the list of old parts and new values (and arrays of | ||
// their respective keys), head/tail pointers into each, and we build | ||
// up the new list of parts by updating (and when needed, moving) old | ||
// parts or creating new ones. The initial scenario might look like | ||
// this (for brevity of the diagrams, the numbers in the array reflect | ||
// keys associated with the old parts or new values, although keys and | ||
// parts/values are actually stored in parallel arrays indexed using | ||
// the same head/tail pointers): | ||
// * We start with the list of old parts and new values (and | ||
// arrays of | ||
// their respective keys), head/tail pointers into each, and | ||
// we build up the new list of parts by updating (and when | ||
// needed, moving) old parts or creating new ones. The initial | ||
// scenario might look like this (for brevity of the diagrams, | ||
// the numbers in the array reflect keys associated with the | ||
// old parts or new values, although keys and parts/values are | ||
// actually stored in parallel arrays indexed using the same | ||
// head/tail pointers): | ||
// | ||
@@ -134,148 +136,169 @@ // oldHead v v oldTail | ||
// newParts: [ , , , , , , ] | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] <- reflects the user's new item | ||
// order | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] <- reflects the user's new | ||
// item order | ||
// newHead ^ ^ newTail | ||
// | ||
// * Iterate old & new lists from both sides, updating, swapping, or | ||
// removing parts at the head/tail locations until neither head nor | ||
// tail can move. | ||
// * Iterate old & new lists from both sides, updating, | ||
// swapping, or | ||
// removing parts at the head/tail locations until neither | ||
// head nor tail can move. | ||
// | ||
// * Example below: keys at head pointers match, so update old part 0 | ||
// in- | ||
// place (no need to move it) and record part 0 in the `newParts` | ||
// list. The last thing we do is advance the `oldHead` and `newHead` | ||
// pointers (will be reflected in the next diagram). | ||
// * Example below: keys at head pointers match, so update old | ||
// part 0 in- | ||
// place (no need to move it) and record part 0 in the | ||
// `newParts` list. The last thing we do is advance the | ||
// `oldHead` and `newHead` pointers (will be reflected in the | ||
// next diagram). | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, 2, 3, 4, 5, 6] | ||
// newParts: [0, , , , , , ] <- heads matched: update 0 and | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] advance both oldHead & newHead | ||
// newParts: [0, , , , , , ] <- heads matched: update 0 | ||
// and newKeys: [0, 2, 1, 4, 3, 7, 6] advance both oldHead | ||
// & newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * Example below: head pointers don't match, but tail pointers do, so | ||
// update part 6 in place (no need to move it), and record part 6 in | ||
// the `newParts` list. Last, advance the `oldTail` and `oldHead` | ||
// pointers. | ||
// * Example below: head pointers don't match, but tail pointers | ||
// do, so | ||
// update part 6 in place (no need to move it), and record | ||
// part 6 in the `newParts` list. Last, advance the `oldTail` | ||
// and `oldHead` pointers. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, 2, 3, 4, 5, 6] | ||
// newParts: [0, , , , , , 6] <- tails matched: update 6 and | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] advance both oldTail & newTail | ||
// newParts: [0, , , , , , 6] <- tails matched: update 6 | ||
// and newKeys: [0, 2, 1, 4, 3, 7, 6] advance both oldTail | ||
// & newTail | ||
// newHead ^ ^ newTail | ||
// | ||
// * If neither head nor tail match; next check if one of the old | ||
// head/tail | ||
// items was removed. We first need to generate the reverse map of new | ||
// keys to index (`newKeyToIndexMap`), which is done once lazily as a | ||
// performance optimization, since we only hit this case if multiple | ||
// non-contiguous changes were made. Note that for contiguous removal | ||
// anywhere in the list, the head and tails would advance from either | ||
// end and pass each other before we get to this case and removals | ||
// * If neither head nor tail match; next check if one of the | ||
// old head/tail | ||
// items was removed. We first need to generate the reverse | ||
// map of new keys to index (`newKeyToIndexMap`), which is | ||
// done once lazily as a performance optimization, since we | ||
// only hit this case if multiple non-contiguous changes were | ||
// made. Note that for contiguous removal anywhere in the | ||
// list, the head and tails would advance from either end and | ||
// pass each other before we get to this case and removals | ||
// would be handled in the final while loop without needing to | ||
// generate the map. | ||
// | ||
// * Example below: The key at `oldTail` was removed (no longer in the | ||
// `newKeyToIndexMap`), so remove that part from the DOM and advance | ||
// just the `oldTail` pointer. | ||
// * Example below: The key at `oldTail` was removed (no longer | ||
// in the | ||
// `newKeyToIndexMap`), so remove that part from the DOM and | ||
// advance just the `oldTail` pointer. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, 2, 3, 4, 5, 6] | ||
// newParts: [0, , , , , , 6] <- 5 not in new map; remove 5 and | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] advance oldTail | ||
// newParts: [0, , , , , , 6] <- 5 not in new map; remove | ||
// 5 and newKeys: [0, 2, 1, 4, 3, 7, 6] advance oldTail | ||
// newHead ^ ^ newTail | ||
// | ||
// * Once head and tail cannot move, any mismatches are due to either | ||
// new or | ||
// moved items; if a new key is in the previous "old key to old index" | ||
// map, move the old part to the new location, otherwise create and | ||
// insert a new part. Note that when moving an old part we null its | ||
// position in the oldParts array if it lies between the head and tail | ||
// so we know to skip it when the pointers get there. | ||
// * Once head and tail cannot move, any mismatches are due to | ||
// either new or | ||
// moved items; if a new key is in the previous "old key to | ||
// old index" map, move the old part to the new location, | ||
// otherwise create and insert a new part. Note that when | ||
// moving an old part we null its position in the oldParts | ||
// array if it lies between the head and tail so we know to | ||
// skip it when the pointers get there. | ||
// | ||
// * Example below: neither head nor tail match, and neither were | ||
// removed; | ||
// so find the `newHead` key in the `oldKeyToIndexMap`, and move that | ||
// old part's DOM into the next head position (before | ||
// `oldParts[oldHead]`). Last, null the part in the `oldPart` array | ||
// since it was somewhere in the remaining oldParts still to be | ||
// scanned (between the head and tail pointers) so that we know to | ||
// skip that old part on future iterations. | ||
// * Example below: neither head nor tail match, and neither | ||
// were removed; | ||
// so find the `newHead` key in the `oldKeyToIndexMap`, and | ||
// move that old part's DOM into the next head position | ||
// (before `oldParts[oldHead]`). Last, null the part in the | ||
// `oldPart` array since it was somewhere in the remaining | ||
// oldParts still to be scanned (between the head and tail | ||
// pointers) so that we know to skip that old part on future | ||
// iterations. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, , , , , 6] <- stuck; update & move 2 into | ||
// place newKeys: [0, 2, 1, 4, 3, 7, 6] and advance newHead | ||
// newParts: [0, 2, , , , , 6] <- stuck; update & move 2 | ||
// into place newKeys: [0, 2, 1, 4, 3, 7, 6] and advance | ||
// newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * Note that for moves/insertions like the one above, a part inserted | ||
// at | ||
// * Note that for moves/insertions like the one above, a part | ||
// inserted at | ||
// the head pointer is inserted before the current | ||
// `oldParts[oldHead]`, and a part inserted at the tail pointer is | ||
// inserted before `newParts[newTail+1]`. The seeming asymmetry lies | ||
// in the fact that new parts are moved into place outside in, so to | ||
// the right of the head pointer are old parts, and to the right of | ||
// the tail pointer are new parts. | ||
// `oldParts[oldHead]`, and a part inserted at the tail | ||
// pointer is inserted before `newParts[newTail+1]`. The | ||
// seeming asymmetry lies in the fact that new parts are moved | ||
// into place outside in, so to the right of the head pointer | ||
// are old parts, and to the right of the tail pointer are new | ||
// parts. | ||
// | ||
// * We always restart back from the top of the algorithm, allowing | ||
// matching | ||
// * We always restart back from the top of the algorithm, | ||
// allowing matching | ||
// and simple updates in place to continue... | ||
// | ||
// * Example below: the head pointers once again match, so simply update | ||
// part 1 and record it in the `newParts` array. Last, advance both | ||
// head pointers. | ||
// * Example below: the head pointers once again match, so | ||
// simply update | ||
// part 1 and record it in the `newParts` array. Last, | ||
// advance both head pointers. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, 1, , , , 6] <- heads matched; update 1 and | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] advance both oldHead & newHead | ||
// newParts: [0, 2, 1, , , , 6] <- heads matched; update 1 | ||
// and newKeys: [0, 2, 1, 4, 3, 7, 6] advance both oldHead | ||
// & newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * As mentioned above, items that were moved as a result of being | ||
// stuck | ||
// (the final else clause in the code below) are marked with null, so | ||
// we always advance old pointers over these so we're comparing the | ||
// next actual old value on either end. | ||
// * As mentioned above, items that were moved as a result of | ||
// being stuck | ||
// (the final else clause in the code below) are marked with | ||
// null, so we always advance old pointers over these so we're | ||
// comparing the next actual old value on either end. | ||
// | ||
// * Example below: `oldHead` is null (already placed in newParts), so | ||
// * Example below: `oldHead` is null (already placed in | ||
// newParts), so | ||
// advance `oldHead`. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] // old head already used; advance | ||
// newParts: [0, 2, 1, , , , 6] // oldHead | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] // old head already used; | ||
// advance newParts: [0, 2, 1, , , , 6] // oldHead newKeys: | ||
// [0, 2, 1, 4, 3, 7, 6] | ||
// newHead ^ ^ newTail | ||
// | ||
// * Note it's not critical to mark old parts as null when they are | ||
// moved | ||
// from head to tail or tail to head, since they will be outside the | ||
// pointer range and never visited again. | ||
// * Note it's not critical to mark old parts as null when they | ||
// are moved | ||
// from head to tail or tail to head, since they will be | ||
// outside the pointer range and never visited again. | ||
// | ||
// * Example below: Here the old tail key matches the new head key, so | ||
// the part at the `oldTail` position and move its DOM to the new | ||
// head position (before `oldParts[oldHead]`). Last, advance `oldTail` | ||
// and `newHead` pointers. | ||
// * Example below: Here the old tail key matches the new head | ||
// key, so | ||
// the part at the `oldTail` position and move its DOM to the | ||
// new head position (before `oldParts[oldHead]`). Last, | ||
// advance `oldTail` and `newHead` pointers. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, 1, 4, , , 6] <- old tail matches new head: | ||
// update newKeys: [0, 2, 1, 4, 3, 7, 6] & move 4, advance oldTail | ||
// & newHead | ||
// newParts: [0, 2, 1, 4, , , 6] <- old tail matches new | ||
// head: update newKeys: [0, 2, 1, 4, 3, 7, 6] & move 4, | ||
// advance oldTail & newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * Example below: Old and new head keys match, so update the old head | ||
// part in place, and advance the `oldHead` and `newHead` pointers. | ||
// * Example below: Old and new head keys match, so update the | ||
// old head | ||
// part in place, and advance the `oldHead` and `newHead` | ||
// pointers. | ||
// | ||
// oldHead v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, 1, 4, 3, ,6] <- heads match: update 3 and | ||
// advance newKeys: [0, 2, 1, 4, 3, 7, 6] oldHead & newHead | ||
// newParts: [0, 2, 1, 4, 3, ,6] <- heads match: update 3 | ||
// and advance newKeys: [0, 2, 1, 4, 3, 7, 6] oldHead & | ||
// newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * Once the new or old pointers move past each other then all we have | ||
// left is additions (if old list exhausted) or removals (if new list | ||
// exhausted). Those are handled in the final while loops at the end. | ||
// * Once the new or old pointers move past each other then all | ||
// we have | ||
// left is additions (if old list exhausted) or removals (if | ||
// new list exhausted). Those are handled in the final while | ||
// loops at the end. | ||
// | ||
// * Example below: `oldHead` exceeded `oldTail`, so we're done with the | ||
// main loop. Create the remaining part and insert it at the new head | ||
// position, and the update is complete. | ||
// * Example below: `oldHead` exceeded `oldTail`, so we're done | ||
// with the | ||
// main loop. Create the remaining part and insert it at the | ||
// new head position, and the update is complete. | ||
// | ||
@@ -288,26 +311,31 @@ // (oldHead > oldTail) | ||
// | ||
// * Note that the order of the if/else clauses is not important to the | ||
// algorithm, as long as the null checks come first (to ensure we're | ||
// always working on valid old parts) and that the final else clause | ||
// comes last (since that's where the expensive moves occur). The | ||
// order of remaining clauses is is just a simple guess at which cases | ||
// will be most common. | ||
// * Note that the order of the if/else clauses is not important | ||
// to the | ||
// algorithm, as long as the null checks come first (to ensure | ||
// we're always working on valid old parts) and that the final | ||
// else clause comes last (since that's where the expensive | ||
// moves occur). The order of remaining clauses is is just a | ||
// simple guess at which cases will be most common. | ||
// | ||
// * TODO(kschaaf) Note, we could calculate the longest increasing | ||
// subsequence (LIS) of old items in new position, and only move those | ||
// not in the LIS set. However that costs O(nlogn) time and adds a bit | ||
// more code, and only helps make rare types of mutations require | ||
// fewer moves. The above handles removes, adds, reversal, swaps, and | ||
// single moves of contiguous items in linear time, in the minimum | ||
// number of moves. As the number of multiple moves where LIS might | ||
// help approaches a random shuffle, the LIS optimization becomes less | ||
// helpful, so it seems not worth the code at this point. Could | ||
// reconsider if a compelling case arises. | ||
// * TODO(kschaaf) Note, we could calculate the longest | ||
// increasing | ||
// subsequence (LIS) of old items in new position, and only | ||
// move those not in the LIS set. However that costs O(nlogn) | ||
// time and adds a bit more code, and only helps make rare | ||
// types of mutations require fewer moves. The above handles | ||
// removes, adds, reversal, swaps, and single moves of | ||
// contiguous items in linear time, in the minimum number of | ||
// moves. As the number of multiple moves where LIS might help | ||
// approaches a random shuffle, the LIS optimization becomes | ||
// less helpful, so it seems not worth the code at this point. | ||
// Could reconsider if a compelling case arises. | ||
while (oldHead <= oldTail && newHead <= newTail) { | ||
if (oldParts[oldHead] === null) { | ||
// `null` means old part at head has already been used below; skip | ||
// `null` means old part at head has already been used | ||
// below; skip | ||
oldHead++; | ||
} | ||
else if (oldParts[oldTail] === null) { | ||
// `null` means old part at tail has already been used below; skip | ||
// `null` means old part at tail has already been used | ||
// below; skip | ||
oldTail--; | ||
@@ -347,4 +375,4 @@ } | ||
if (newKeyToIndexMap === undefined) { | ||
// Lazily generate key-to-index maps, used for removals & moves | ||
// below | ||
// Lazily generate key-to-index maps, used for removals & | ||
// moves below | ||
newKeyToIndexMap = generateMap(newKeys, newHead, newTail); | ||
@@ -364,8 +392,10 @@ oldKeyToIndexMap = generateMap(oldKeys, oldHead, oldTail); | ||
else { | ||
// Any mismatches at this point are due to additions or moves; see | ||
// if we have an old part we can reuse and move into place | ||
// Any mismatches at this point are due to additions or | ||
// moves; see if we have an old part we can reuse and move | ||
// into place | ||
const oldIndex = oldKeyToIndexMap.get(newKeys[newHead]); | ||
const oldPart = oldIndex !== undefined ? oldParts[oldIndex] : null; | ||
if (oldPart === null) { | ||
// No old part for this value; create a new one and insert it | ||
// No old part for this value; create a new one and | ||
// insert it | ||
const newPart = createAndInsertPart(containerPart, oldParts[oldHead]); | ||
@@ -377,6 +407,7 @@ updatePart(newPart, newValues[newHead]); | ||
// Reuse old part | ||
newParts[newHead] = updatePart(oldPart, newValues[newHead]); | ||
newParts[newHead] = | ||
updatePart(oldPart, newValues[newHead]); | ||
insertPartBefore(containerPart, oldPart, oldParts[oldHead]); | ||
// This marks the old part as having been used, so that it will | ||
// be skipped in the first two checks above | ||
// This marks the old part as having been used, so that | ||
// it will be skipped in the first two checks above | ||
oldParts[oldIndex] = null; | ||
@@ -390,4 +421,4 @@ } | ||
while (newHead <= newTail) { | ||
// For all remaining additions, we insert before last new tail, | ||
// since old pointers are no longer valid | ||
// For all remaining additions, we insert before last new | ||
// tail, since old pointers are no longer valid | ||
const newPart = createAndInsertPart(containerPart, newParts[newTail + 1]); | ||
@@ -394,0 +425,0 @@ updatePart(newPart, newValues[newHead]); |
@@ -62,2 +62,3 @@ /** | ||
if (name.indexOf('-') === -1) { | ||
// tslint:disable-next-line:no-any | ||
style[name] = null; | ||
@@ -73,2 +74,3 @@ } | ||
if (name.indexOf('-') === -1) { | ||
// tslint:disable-next-line:no-any | ||
style[name] = styleInfo[name]; | ||
@@ -75,0 +77,0 @@ } |
@@ -15,3 +15,10 @@ /** | ||
import { Part } from '../lit-html.js'; | ||
export declare const unsafeHTML: (value: any) => (part: Part) => void; | ||
/** | ||
* 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: unknown) => (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; // innerHTML casts to string internally | ||
const fragment = document.importNode(template.content, true); | ||
part.setValue(fragment); | ||
previousValues.set(part, { value, fragment }); | ||
}); | ||
//# sourceMappingURL=unsafe-html.js.map |
@@ -34,3 +34,3 @@ /** | ||
*/ | ||
export declare const until: (...args: any[]) => (part: Part) => void; | ||
export declare const until: (...args: unknown[]) => (part: Part) => void; | ||
//# sourceMappingURL=until.d.ts.map |
@@ -45,13 +45,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 | ||
if (isPrimitive(value) || typeof value.then !== 'function') { | ||
if (isPrimitive(value) || | ||
typeof value.then !== 'function') { | ||
part.setValue(value); | ||
@@ -63,2 +62,8 @@ state.lastRenderedIndex = i; | ||
} | ||
// 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 +70,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,3 +14,7 @@ /** | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
import { Part } from './part.js'; | ||
export declare type DirectiveFactory = (...args: any[]) => object; | ||
export declare type DirectiveFn = (part: Part) => void; | ||
@@ -37,4 +41,4 @@ /** | ||
*/ | ||
export declare const directive: <F extends Function>(f: F) => F; | ||
export declare const isDirective: (o: any) => boolean; | ||
export declare const directive: <F extends DirectiveFactory>(f: F) => F; | ||
export declare const isDirective: (o: unknown) => o is DirectiveFn; | ||
//# sourceMappingURL=directive.d.ts.map |
@@ -35,2 +35,3 @@ /** | ||
*/ | ||
// tslint:disable-next-line:no-any | ||
export const directive = (f) => ((...args) => { | ||
@@ -41,3 +42,5 @@ const d = f(...args); | ||
}); | ||
export const isDirective = (o) => typeof o === 'function' && directives.has(o); | ||
export const isDirective = (o) => { | ||
return typeof o === 'function' && directives.has(o); | ||
}; | ||
//# sourceMappingURL=directive.js.map |
@@ -14,2 +14,5 @@ /** | ||
*/ | ||
/** | ||
* True if the custom elements polyfill is in use. | ||
*/ | ||
export declare const isCEPolyfill: boolean; | ||
@@ -16,0 +19,0 @@ /** |
@@ -14,4 +14,8 @@ /** | ||
*/ | ||
/** | ||
* True if the custom elements polyfill is in use. | ||
*/ | ||
export const isCEPolyfill = window.customElements !== undefined && | ||
window.customElements.polyfillWrapFlushCallback !== undefined; | ||
window.customElements.polyfillWrapFlushCallback !== | ||
undefined; | ||
/** | ||
@@ -18,0 +22,0 @@ * Reparents nodes, starting from `startNode` (inclusive) to `endNode` |
@@ -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()) { |
/** | ||
* @license | ||
* Copyright (c) 2018 The Polymer Project Authors. All rights reserved. | ||
* This code may only be used under the BSD style license found at | ||
* http://polymer.github.io/LICENSE.txt | ||
* The complete set of authors may be found at | ||
* http://polymer.github.io/AUTHORS.txt | ||
* The complete set of contributors may be found at | ||
* http://polymer.github.io/CONTRIBUTORS.txt | ||
* Code distributed by Google as part of the polymer project is also | ||
* subject to an additional IP rights grant found at | ||
* http://polymer.github.io/PATENTS.txt | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
/** | ||
* The Part interface represents a dynamic part of a template instance rendered | ||
@@ -6,3 +22,3 @@ * by lit-html. | ||
export interface Part { | ||
value: any; | ||
value: unknown; | ||
/** | ||
@@ -12,3 +28,3 @@ * Sets the current part value, but does not write it to the DOM. | ||
*/ | ||
setValue(value: any): void; | ||
setValue(value: unknown): void; | ||
/** | ||
@@ -23,3 +39,7 @@ * Commits the current part value, cause it to actually be written to the DOM. | ||
*/ | ||
export declare const noChange: {}; | ||
export declare const noChange: object; | ||
/** | ||
* A sentinel value that signals a NodePart to fully clear its content. | ||
*/ | ||
export declare const nothing: {}; | ||
//# sourceMappingURL=part.d.ts.map |
/** | ||
* @license | ||
* Copyright (c) 2018 The Polymer Project Authors. All rights reserved. | ||
* This code may only be used under the BSD style license found at | ||
* http://polymer.github.io/LICENSE.txt | ||
* The complete set of authors may be found at | ||
* http://polymer.github.io/AUTHORS.txt | ||
* The complete set of contributors may be found at | ||
* http://polymer.github.io/CONTRIBUTORS.txt | ||
* Code distributed by Google as part of the polymer project is also | ||
* subject to an additional IP rights grant found at | ||
* http://polymer.github.io/PATENTS.txt | ||
*/ | ||
/** | ||
* A sentinel value that signals that a value was handled by a directive and | ||
@@ -6,2 +19,6 @@ * should not be written to the DOM. | ||
export const noChange = {}; | ||
/** | ||
* A sentinel value that signals a NodePart to fully clear its content. | ||
*/ | ||
export const nothing = {}; | ||
//# sourceMappingURL=part.js.map |
@@ -16,3 +16,4 @@ /** | ||
import { RenderOptions } from './render-options.js'; | ||
export declare const isPrimitive: (value: any) => boolean; | ||
export declare type Primitive = null | undefined | boolean | number | string | Symbol | bigint; | ||
export declare const isPrimitive: (value: unknown) => value is Primitive; | ||
/** | ||
@@ -33,3 +34,3 @@ * Sets attribute values for AttributeParts, so that the value is only set once | ||
protected _createPart(): AttributePart; | ||
protected _getValue(): any; | ||
protected _getValue(): unknown; | ||
commit(): void; | ||
@@ -39,5 +40,5 @@ } | ||
committer: AttributeCommitter; | ||
value: any; | ||
value: unknown; | ||
constructor(comitter: AttributeCommitter); | ||
setValue(value: any): void; | ||
setValue(value: unknown): void; | ||
commit(): void; | ||
@@ -49,4 +50,4 @@ } | ||
endNode: Node; | ||
value: any; | ||
_pendingValue: any; | ||
value: unknown; | ||
_pendingValue: unknown; | ||
constructor(options: RenderOptions); | ||
@@ -79,3 +80,3 @@ /** | ||
insertAfterPart(ref: NodePart): void; | ||
setValue(value: any): void; | ||
setValue(value: unknown): void; | ||
commit(): void; | ||
@@ -100,6 +101,6 @@ private _insert; | ||
strings: string[]; | ||
value: any; | ||
_pendingValue: any; | ||
value: unknown; | ||
_pendingValue: unknown; | ||
constructor(element: Element, name: string, strings: string[]); | ||
setValue(value: any): void; | ||
setValue(value: unknown): void; | ||
commit(): void; | ||
@@ -120,3 +121,3 @@ } | ||
protected _createPart(): PropertyPart; | ||
_getValue(): any; | ||
_getValue(): unknown; | ||
commit(): void; | ||
@@ -126,2 +127,3 @@ } | ||
} | ||
declare type EventHandlerWithOptions = EventListenerOrEventListenerObject & Partial<AddEventListenerOptions>; | ||
export declare class EventPart implements Part { | ||
@@ -131,11 +133,12 @@ element: Element; | ||
eventContext?: EventTarget; | ||
value: any; | ||
value: undefined | EventHandlerWithOptions; | ||
_options?: AddEventListenerOptions; | ||
_pendingValue: any; | ||
_pendingValue: undefined | EventHandlerWithOptions; | ||
_boundHandleEvent: (event: Event) => void; | ||
constructor(element: Element, eventName: string, eventContext?: EventTarget); | ||
setValue(value: any): void; | ||
setValue(value: undefined | EventHandlerWithOptions): void; | ||
commit(): void; | ||
handleEvent(event: Event): void; | ||
} | ||
export {}; | ||
//# sourceMappingURL=parts.d.ts.map |
@@ -14,10 +14,15 @@ /** | ||
*/ | ||
/** | ||
* @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'; | ||
import { TemplateResult } from './template-result.js'; | ||
import { createMarker } from './template.js'; | ||
export const isPrimitive = (value) => (value === null || | ||
!(typeof value === 'object' || typeof value === 'function')); | ||
export const isPrimitive = (value) => { | ||
return (value === null || | ||
!(typeof value === 'object' || typeof value === 'function')); | ||
}; | ||
/** | ||
@@ -54,3 +59,5 @@ * Sets attribute values for AttributeParts, so that the value is only set once | ||
if (v != null && | ||
(Array.isArray(v) || typeof v !== 'string' && v[Symbol.iterator])) { | ||
(Array.isArray(v) || | ||
// tslint:disable-next-line:no-any | ||
typeof v !== 'string' && v[Symbol.iterator])) { | ||
for (const t of v) { | ||
@@ -172,5 +179,11 @@ text += typeof t === 'string' ? t : String(t); | ||
} | ||
else if (Array.isArray(value) || value[Symbol.iterator]) { | ||
else if (Array.isArray(value) || | ||
// tslint:disable-next-line:no-any | ||
value[Symbol.iterator]) { | ||
this._commitIterable(value); | ||
} | ||
else if (value === nothing) { | ||
this.value = nothing; | ||
this.clear(); | ||
} | ||
else { | ||
@@ -196,7 +209,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; | ||
} | ||
@@ -210,3 +223,4 @@ else { | ||
const template = this.options.templateFactory(value); | ||
if (this.value && this.value.template === template) { | ||
if (this.value instanceof TemplateInstance && | ||
this.value.template === template) { | ||
this.value.update(value.values); | ||
@@ -343,2 +357,3 @@ } | ||
this.dirty = false; | ||
// tslint:disable-next-line:no-any | ||
this.element[this.name] = this._getValue(); | ||
@@ -362,3 +377,5 @@ } | ||
}; | ||
// tslint:disable-next-line:no-any | ||
window.addEventListener('test', options, options); | ||
// tslint:disable-next-line:no-any | ||
window.removeEventListener('test', options, options); | ||
@@ -365,0 +382,0 @@ } |
@@ -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'; |
@@ -28,5 +28,5 @@ /** | ||
constructor(template: Template, processor: TemplateProcessor, options: RenderOptions); | ||
update(values: any[]): void; | ||
update(values: unknown[]): void; | ||
_clone(): DocumentFragment; | ||
} | ||
//# sourceMappingURL=template-instance.d.ts.map |
@@ -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'; |
@@ -21,6 +21,6 @@ /** | ||
strings: TemplateStringsArray; | ||
values: any[]; | ||
values: unknown[]; | ||
type: string; | ||
processor: TemplateProcessor; | ||
constructor(strings: TemplateStringsArray, values: any[], type: string, processor: TemplateProcessor); | ||
constructor(strings: TemplateStringsArray, values: unknown[], type: string, processor: TemplateProcessor); | ||
/** | ||
@@ -27,0 +27,0 @@ * Returns a string of HTML used to create a `<template>` element. |
@@ -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'; | ||
@@ -28,2 +28,7 @@ export { RenderOptions } from './lib/render-options.js'; | ||
export { createMarker, isTemplatePartActive, Template } from './lib/template.js'; | ||
declare global { | ||
interface Window { | ||
litHtmlVersions: string[]; | ||
} | ||
} | ||
/** | ||
@@ -33,3 +38,3 @@ * Interprets a template literal as an HTML template that can efficiently | ||
*/ | ||
export declare const html: (strings: TemplateStringsArray, ...values: any[]) => TemplateResult; | ||
export declare const html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult; | ||
/** | ||
@@ -39,3 +44,3 @@ * Interprets a template literal as an SVG template that can efficiently | ||
*/ | ||
export declare const svg: (strings: TemplateStringsArray, ...values: any[]) => SVGTemplateResult; | ||
export declare const svg: (strings: TemplateStringsArray, ...values: unknown[]) => SVGTemplateResult; | ||
//# sourceMappingURL=lit-html.d.ts.map |
@@ -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'; | ||
@@ -29,2 +46,6 @@ export { parts, render } from './lib/render.js'; | ||
export { createMarker, isTemplatePartActive, Template } from './lib/template.js'; | ||
// IMPORTANT: do not change the property name or the assignment expression. | ||
// This line will be used in regexes to search for lit-html usage. | ||
// TODO(justinfagnani): inject version number at build time | ||
(window['litHtmlVersions'] || (window['litHtmlVersions'] = [])).push('1.0.0'); | ||
/** | ||
@@ -31,0 +52,0 @@ * Interprets a template literal as an HTML template that can efficiently |
{ | ||
"name": "lit-html", | ||
"version": "0.14.0", | ||
"version": "1.0.0-dev.c8753bc", | ||
"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 none --mode modules --ignoreCompilerErrors --excludeNotExported --excludePrivate --exclude **/*_test.ts --out ./docs/api --gaID UA-39334307-23 ./src", | ||
"test": "npm run build && npm run lint && wct --npm", | ||
@@ -32,3 +31,4 @@ "quicktest": "wct -l chrome -p --npm", | ||
"lint": "tslint --project ./", | ||
"prepare": "npm run build" | ||
"prepare": "npm run build", | ||
"publish-dev": "npm test && VERSION=${npm_package_version%-*}-dev.`git rev-parse --short HEAD` && npm version --no-git-tag-version $VERSION && npm publish --tag dev" | ||
}, | ||
@@ -48,4 +48,3 @@ "author": "The Polymer Authors", | ||
"tslint": "^5.11.0", | ||
"typedoc": "^0.13.0", | ||
"typescript": "^3.1.1", | ||
"typescript": "^3.2.2", | ||
"uglify-es": "^3.3.5", | ||
@@ -52,0 +51,0 @@ "wct-mocha": "^1.0.0", |
@@ -28,2 +28,3 @@ /** | ||
const contentDoc = document.implementation.createHTMLDocument('template'); | ||
// tslint:disable-next-line:no-any | ||
const upgrade = (template) => { | ||
@@ -47,4 +48,4 @@ template.content = contentDoc.createDocumentFragment(); | ||
const capturedCreateElement = Document.prototype.createElement; | ||
Document.prototype.createElement = function createElement() { | ||
let el = capturedCreateElement.apply(this, arguments); | ||
Document.prototype.createElement = function createElement(tagName, options) { | ||
let el = capturedCreateElement.call(this, tagName, options); | ||
if (el.localName === 'template') { | ||
@@ -51,0 +52,0 @@ el = capturedCreateElement.call(this, 'div'); |
@@ -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 @@ |
@@ -36,3 +36,3 @@ /** | ||
<T>(value: AsyncIterable<T>, | ||
mapper?: (v: T, index?: number) => any) => async (part: Part) => { | ||
mapper?: (v: T, index?: number) => unknown) => async (part: Part) => { | ||
if (!(part instanceof NodePart)) { | ||
@@ -71,3 +71,5 @@ throw new Error('asyncAppend can only be used in text bindings'); | ||
if (mapper !== undefined) { | ||
v = mapper(v, i); | ||
// This is safe because T must otherwise be treated as unknown by | ||
// the rest of the system. | ||
v = mapper(v, i) as T; | ||
} | ||
@@ -74,0 +76,0 @@ |
@@ -36,3 +36,3 @@ /** | ||
export const asyncReplace = directive( | ||
<T>(value: AsyncIterable<T>, mapper?: (v: T, index?: number) => any) => | ||
<T>(value: AsyncIterable<T>, mapper?: (v: T, index?: number) => unknown) => | ||
async (part: Part) => { | ||
@@ -74,3 +74,5 @@ if (!(part instanceof NodePart)) { | ||
if (mapper !== undefined) { | ||
v = mapper(v, i); | ||
// This is safe because T must otherwise be treated as unknown by | ||
// the rest of the system. | ||
v = mapper(v, i) as T; | ||
} | ||
@@ -77,0 +79,0 @@ |
@@ -40,3 +40,3 @@ /** | ||
*/ | ||
export const cache = directive((value: any) => (part: Part) => { | ||
export const cache = directive((value: unknown) => (part: Part) => { | ||
if (!(part instanceof NodePart)) { | ||
@@ -43,0 +43,0 @@ throw new Error('cache can only be used in text bindings'); |
@@ -23,3 +23,3 @@ /** | ||
*/ | ||
export const ifDefined = directive((value: any) => (part: Part) => { | ||
export const ifDefined = directive((value: unknown) => (part: Part) => { | ||
if (value === undefined && part instanceof AttributePart) { | ||
@@ -26,0 +26,0 @@ if (value !== part.value) { |
@@ -18,4 +18,4 @@ /** | ||
export type KeyFn<T> = (item: T, index: number) => any; | ||
export type ItemTemplate<T> = (item: T, index: number) => any; | ||
export type KeyFn<T> = (item: T, index: number) => unknown; | ||
export type ItemTemplate<T> = (item: T, index: number) => unknown; | ||
@@ -91,331 +91,370 @@ // Helper functions for manipulating parts | ||
*/ | ||
export const repeat = directive( | ||
<T>(items: Iterable<T>, | ||
keyFnOrTemplate: KeyFn<T>|ItemTemplate<T>, | ||
template?: ItemTemplate<T>): DirectiveFn => { | ||
let keyFn: KeyFn<T>; | ||
if (template === undefined) { | ||
template = keyFnOrTemplate; | ||
} else if (keyFnOrTemplate !== undefined) { | ||
keyFn = keyFnOrTemplate as KeyFn<T>; | ||
} | ||
export const repeat = | ||
directive( | ||
<T>(items: Iterable<T>, | ||
keyFnOrTemplate: KeyFn<T>|ItemTemplate<T>, | ||
template?: ItemTemplate<T>): | ||
DirectiveFn => { | ||
let keyFn: KeyFn<T>; | ||
if (template === undefined) { | ||
template = keyFnOrTemplate; | ||
} else if (keyFnOrTemplate !== undefined) { | ||
keyFn = keyFnOrTemplate as KeyFn<T>; | ||
} | ||
return (containerPart: Part): void => { | ||
if (!(containerPart instanceof NodePart)) { | ||
throw new Error('repeat can only be used in text bindings'); | ||
} | ||
// Old part & key lists are retrieved from the last update (associated | ||
// with the part for this instance of the directive) | ||
const oldParts = partListCache.get(containerPart) || []; | ||
const oldKeys = keyListCache.get(containerPart) || []; | ||
return (containerPart: Part): void => { | ||
if (!(containerPart instanceof NodePart)) { | ||
throw new Error('repeat can only be used in text bindings'); | ||
} | ||
// Old part & key lists are retrieved from the last update | ||
// (associated with the part for this instance of the directive) | ||
const oldParts = partListCache.get(containerPart) || []; | ||
const oldKeys = keyListCache.get(containerPart) || []; | ||
// New part list will be built up as we go (either reused from old parts | ||
// or created for new keys in this update). This is saved in the above | ||
// cache at the end of the update. | ||
const newParts: NodePart[] = []; | ||
// New part list will be built up as we go (either reused from | ||
// old parts or created for new keys in this update). This is | ||
// saved in the above cache at the end of the update. | ||
const newParts: NodePart[] = []; | ||
// New value list is eagerly generated from items along with a parallel | ||
// array indicating its key. | ||
const newValues: unknown[] = []; | ||
const newKeys: unknown[] = []; | ||
let index = 0; | ||
for (const item of items) { | ||
newKeys[index] = keyFn ? keyFn(item, index) : index; | ||
newValues[index] = template !(item, index); | ||
index++; | ||
} | ||
// New value list is eagerly generated from items along with a | ||
// parallel array indicating its key. | ||
const newValues: unknown[] = []; | ||
const newKeys: unknown[] = []; | ||
let index = 0; | ||
for (const item of items) { | ||
newKeys[index] = keyFn ? keyFn(item, index) : index; | ||
newValues[index] = template !(item, index); | ||
index++; | ||
} | ||
// Maps from key to index for current and previous update; these are | ||
// generated lazily only when needed as a performance optimization, | ||
// since they are only required for multiple non-contiguous changes in | ||
// the list, which are less common. | ||
let newKeyToIndexMap!: Map<unknown, number>; | ||
let oldKeyToIndexMap!: Map<unknown, number>; | ||
// Maps from key to index for current and previous update; these | ||
// are generated lazily only when needed as a performance | ||
// optimization, since they are only required for multiple | ||
// non-contiguous changes in the list, which are less common. | ||
let newKeyToIndexMap!: Map<unknown, number>; | ||
let oldKeyToIndexMap!: Map<unknown, number>; | ||
// Head and tail pointers to old parts and new values | ||
let oldHead = 0; | ||
let oldTail = oldParts.length - 1; | ||
let newHead = 0; | ||
let newTail = newValues.length - 1; | ||
// Head and tail pointers to old parts and new values | ||
let oldHead = 0; | ||
let oldTail = oldParts.length - 1; | ||
let newHead = 0; | ||
let newTail = newValues.length - 1; | ||
// Overview of O(n) reconciliation algorithm (general approach based on | ||
// ideas found in ivi, vue, snabbdom, etc.): | ||
// | ||
// * We start with the list of old parts and new values (and arrays of | ||
// their respective keys), head/tail pointers into each, and we build | ||
// up the new list of parts by updating (and when needed, moving) old | ||
// parts or creating new ones. The initial scenario might look like | ||
// this (for brevity of the diagrams, the numbers in the array reflect | ||
// keys associated with the old parts or new values, although keys and | ||
// parts/values are actually stored in parallel arrays indexed using | ||
// the same head/tail pointers): | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, 2, 3, 4, 5, 6] | ||
// newParts: [ , , , , , , ] | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] <- reflects the user's new item | ||
// order | ||
// newHead ^ ^ newTail | ||
// | ||
// * Iterate old & new lists from both sides, updating, swapping, or | ||
// removing parts at the head/tail locations until neither head nor | ||
// tail can move. | ||
// | ||
// * Example below: keys at head pointers match, so update old part 0 | ||
// in- | ||
// place (no need to move it) and record part 0 in the `newParts` | ||
// list. The last thing we do is advance the `oldHead` and `newHead` | ||
// pointers (will be reflected in the next diagram). | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, 2, 3, 4, 5, 6] | ||
// newParts: [0, , , , , , ] <- heads matched: update 0 and | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] advance both oldHead & newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * Example below: head pointers don't match, but tail pointers do, so | ||
// update part 6 in place (no need to move it), and record part 6 in | ||
// the `newParts` list. Last, advance the `oldTail` and `oldHead` | ||
// pointers. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, 2, 3, 4, 5, 6] | ||
// newParts: [0, , , , , , 6] <- tails matched: update 6 and | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] advance both oldTail & newTail | ||
// newHead ^ ^ newTail | ||
// | ||
// * If neither head nor tail match; next check if one of the old | ||
// head/tail | ||
// items was removed. We first need to generate the reverse map of new | ||
// keys to index (`newKeyToIndexMap`), which is done once lazily as a | ||
// performance optimization, since we only hit this case if multiple | ||
// non-contiguous changes were made. Note that for contiguous removal | ||
// anywhere in the list, the head and tails would advance from either | ||
// end and pass each other before we get to this case and removals | ||
// would be handled in the final while loop without needing to | ||
// generate the map. | ||
// | ||
// * Example below: The key at `oldTail` was removed (no longer in the | ||
// `newKeyToIndexMap`), so remove that part from the DOM and advance | ||
// just the `oldTail` pointer. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, 2, 3, 4, 5, 6] | ||
// newParts: [0, , , , , , 6] <- 5 not in new map; remove 5 and | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] advance oldTail | ||
// newHead ^ ^ newTail | ||
// | ||
// * Once head and tail cannot move, any mismatches are due to either | ||
// new or | ||
// moved items; if a new key is in the previous "old key to old index" | ||
// map, move the old part to the new location, otherwise create and | ||
// insert a new part. Note that when moving an old part we null its | ||
// position in the oldParts array if it lies between the head and tail | ||
// so we know to skip it when the pointers get there. | ||
// | ||
// * Example below: neither head nor tail match, and neither were | ||
// removed; | ||
// so find the `newHead` key in the `oldKeyToIndexMap`, and move that | ||
// old part's DOM into the next head position (before | ||
// `oldParts[oldHead]`). Last, null the part in the `oldPart` array | ||
// since it was somewhere in the remaining oldParts still to be | ||
// scanned (between the head and tail pointers) so that we know to | ||
// skip that old part on future iterations. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, , , , , 6] <- stuck; update & move 2 into | ||
// place newKeys: [0, 2, 1, 4, 3, 7, 6] and advance newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * Note that for moves/insertions like the one above, a part inserted | ||
// at | ||
// the head pointer is inserted before the current | ||
// `oldParts[oldHead]`, and a part inserted at the tail pointer is | ||
// inserted before `newParts[newTail+1]`. The seeming asymmetry lies | ||
// in the fact that new parts are moved into place outside in, so to | ||
// the right of the head pointer are old parts, and to the right of | ||
// the tail pointer are new parts. | ||
// | ||
// * We always restart back from the top of the algorithm, allowing | ||
// matching | ||
// and simple updates in place to continue... | ||
// | ||
// * Example below: the head pointers once again match, so simply update | ||
// part 1 and record it in the `newParts` array. Last, advance both | ||
// head pointers. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, 1, , , , 6] <- heads matched; update 1 and | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] advance both oldHead & newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * As mentioned above, items that were moved as a result of being | ||
// stuck | ||
// (the final else clause in the code below) are marked with null, so | ||
// we always advance old pointers over these so we're comparing the | ||
// next actual old value on either end. | ||
// | ||
// * Example below: `oldHead` is null (already placed in newParts), so | ||
// advance `oldHead`. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] // old head already used; advance | ||
// newParts: [0, 2, 1, , , , 6] // oldHead | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] | ||
// newHead ^ ^ newTail | ||
// | ||
// * Note it's not critical to mark old parts as null when they are | ||
// moved | ||
// from head to tail or tail to head, since they will be outside the | ||
// pointer range and never visited again. | ||
// | ||
// * Example below: Here the old tail key matches the new head key, so | ||
// the part at the `oldTail` position and move its DOM to the new | ||
// head position (before `oldParts[oldHead]`). Last, advance `oldTail` | ||
// and `newHead` pointers. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, 1, 4, , , 6] <- old tail matches new head: | ||
// update newKeys: [0, 2, 1, 4, 3, 7, 6] & move 4, advance oldTail | ||
// & newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * Example below: Old and new head keys match, so update the old head | ||
// part in place, and advance the `oldHead` and `newHead` pointers. | ||
// | ||
// oldHead v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, 1, 4, 3, ,6] <- heads match: update 3 and | ||
// advance newKeys: [0, 2, 1, 4, 3, 7, 6] oldHead & newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * Once the new or old pointers move past each other then all we have | ||
// left is additions (if old list exhausted) or removals (if new list | ||
// exhausted). Those are handled in the final while loops at the end. | ||
// | ||
// * Example below: `oldHead` exceeded `oldTail`, so we're done with the | ||
// main loop. Create the remaining part and insert it at the new head | ||
// position, and the update is complete. | ||
// | ||
// (oldHead > oldTail) | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, 1, 4, 3, 7 ,6] <- create and insert 7 | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] | ||
// newHead ^ newTail | ||
// | ||
// * Note that the order of the if/else clauses is not important to the | ||
// algorithm, as long as the null checks come first (to ensure we're | ||
// always working on valid old parts) and that the final else clause | ||
// comes last (since that's where the expensive moves occur). The | ||
// order of remaining clauses is is just a simple guess at which cases | ||
// will be most common. | ||
// | ||
// * TODO(kschaaf) Note, we could calculate the longest increasing | ||
// subsequence (LIS) of old items in new position, and only move those | ||
// not in the LIS set. However that costs O(nlogn) time and adds a bit | ||
// more code, and only helps make rare types of mutations require | ||
// fewer moves. The above handles removes, adds, reversal, swaps, and | ||
// single moves of contiguous items in linear time, in the minimum | ||
// number of moves. As the number of multiple moves where LIS might | ||
// help approaches a random shuffle, the LIS optimization becomes less | ||
// helpful, so it seems not worth the code at this point. Could | ||
// reconsider if a compelling case arises. | ||
// Overview of O(n) reconciliation algorithm (general approach | ||
// based on ideas found in ivi, vue, snabbdom, etc.): | ||
// | ||
// * We start with the list of old parts and new values (and | ||
// arrays of | ||
// their respective keys), head/tail pointers into each, and | ||
// we build up the new list of parts by updating (and when | ||
// needed, moving) old parts or creating new ones. The initial | ||
// scenario might look like this (for brevity of the diagrams, | ||
// the numbers in the array reflect keys associated with the | ||
// old parts or new values, although keys and parts/values are | ||
// actually stored in parallel arrays indexed using the same | ||
// head/tail pointers): | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, 2, 3, 4, 5, 6] | ||
// newParts: [ , , , , , , ] | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] <- reflects the user's new | ||
// item order | ||
// newHead ^ ^ newTail | ||
// | ||
// * Iterate old & new lists from both sides, updating, | ||
// swapping, or | ||
// removing parts at the head/tail locations until neither | ||
// head nor tail can move. | ||
// | ||
// * Example below: keys at head pointers match, so update old | ||
// part 0 in- | ||
// place (no need to move it) and record part 0 in the | ||
// `newParts` list. The last thing we do is advance the | ||
// `oldHead` and `newHead` pointers (will be reflected in the | ||
// next diagram). | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, 2, 3, 4, 5, 6] | ||
// newParts: [0, , , , , , ] <- heads matched: update 0 | ||
// and newKeys: [0, 2, 1, 4, 3, 7, 6] advance both oldHead | ||
// & newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * Example below: head pointers don't match, but tail pointers | ||
// do, so | ||
// update part 6 in place (no need to move it), and record | ||
// part 6 in the `newParts` list. Last, advance the `oldTail` | ||
// and `oldHead` pointers. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, 2, 3, 4, 5, 6] | ||
// newParts: [0, , , , , , 6] <- tails matched: update 6 | ||
// and newKeys: [0, 2, 1, 4, 3, 7, 6] advance both oldTail | ||
// & newTail | ||
// newHead ^ ^ newTail | ||
// | ||
// * If neither head nor tail match; next check if one of the | ||
// old head/tail | ||
// items was removed. We first need to generate the reverse | ||
// map of new keys to index (`newKeyToIndexMap`), which is | ||
// done once lazily as a performance optimization, since we | ||
// only hit this case if multiple non-contiguous changes were | ||
// made. Note that for contiguous removal anywhere in the | ||
// list, the head and tails would advance from either end and | ||
// pass each other before we get to this case and removals | ||
// would be handled in the final while loop without needing to | ||
// generate the map. | ||
// | ||
// * Example below: The key at `oldTail` was removed (no longer | ||
// in the | ||
// `newKeyToIndexMap`), so remove that part from the DOM and | ||
// advance just the `oldTail` pointer. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, 2, 3, 4, 5, 6] | ||
// newParts: [0, , , , , , 6] <- 5 not in new map; remove | ||
// 5 and newKeys: [0, 2, 1, 4, 3, 7, 6] advance oldTail | ||
// newHead ^ ^ newTail | ||
// | ||
// * Once head and tail cannot move, any mismatches are due to | ||
// either new or | ||
// moved items; if a new key is in the previous "old key to | ||
// old index" map, move the old part to the new location, | ||
// otherwise create and insert a new part. Note that when | ||
// moving an old part we null its position in the oldParts | ||
// array if it lies between the head and tail so we know to | ||
// skip it when the pointers get there. | ||
// | ||
// * Example below: neither head nor tail match, and neither | ||
// were removed; | ||
// so find the `newHead` key in the `oldKeyToIndexMap`, and | ||
// move that old part's DOM into the next head position | ||
// (before `oldParts[oldHead]`). Last, null the part in the | ||
// `oldPart` array since it was somewhere in the remaining | ||
// oldParts still to be scanned (between the head and tail | ||
// pointers) so that we know to skip that old part on future | ||
// iterations. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, , , , , 6] <- stuck; update & move 2 | ||
// into place newKeys: [0, 2, 1, 4, 3, 7, 6] and advance | ||
// newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * Note that for moves/insertions like the one above, a part | ||
// inserted at | ||
// the head pointer is inserted before the current | ||
// `oldParts[oldHead]`, and a part inserted at the tail | ||
// pointer is inserted before `newParts[newTail+1]`. The | ||
// seeming asymmetry lies in the fact that new parts are moved | ||
// into place outside in, so to the right of the head pointer | ||
// are old parts, and to the right of the tail pointer are new | ||
// parts. | ||
// | ||
// * We always restart back from the top of the algorithm, | ||
// allowing matching | ||
// and simple updates in place to continue... | ||
// | ||
// * Example below: the head pointers once again match, so | ||
// simply update | ||
// part 1 and record it in the `newParts` array. Last, | ||
// advance both head pointers. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, 1, , , , 6] <- heads matched; update 1 | ||
// and newKeys: [0, 2, 1, 4, 3, 7, 6] advance both oldHead | ||
// & newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * As mentioned above, items that were moved as a result of | ||
// being stuck | ||
// (the final else clause in the code below) are marked with | ||
// null, so we always advance old pointers over these so we're | ||
// comparing the next actual old value on either end. | ||
// | ||
// * Example below: `oldHead` is null (already placed in | ||
// newParts), so | ||
// advance `oldHead`. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] // old head already used; | ||
// advance newParts: [0, 2, 1, , , , 6] // oldHead newKeys: | ||
// [0, 2, 1, 4, 3, 7, 6] | ||
// newHead ^ ^ newTail | ||
// | ||
// * Note it's not critical to mark old parts as null when they | ||
// are moved | ||
// from head to tail or tail to head, since they will be | ||
// outside the pointer range and never visited again. | ||
// | ||
// * Example below: Here the old tail key matches the new head | ||
// key, so | ||
// the part at the `oldTail` position and move its DOM to the | ||
// new head position (before `oldParts[oldHead]`). Last, | ||
// advance `oldTail` and `newHead` pointers. | ||
// | ||
// oldHead v v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, 1, 4, , , 6] <- old tail matches new | ||
// head: update newKeys: [0, 2, 1, 4, 3, 7, 6] & move 4, | ||
// advance oldTail & newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * Example below: Old and new head keys match, so update the | ||
// old head | ||
// part in place, and advance the `oldHead` and `newHead` | ||
// pointers. | ||
// | ||
// oldHead v oldTail | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, 1, 4, 3, ,6] <- heads match: update 3 | ||
// and advance newKeys: [0, 2, 1, 4, 3, 7, 6] oldHead & | ||
// newHead | ||
// newHead ^ ^ newTail | ||
// | ||
// * Once the new or old pointers move past each other then all | ||
// we have | ||
// left is additions (if old list exhausted) or removals (if | ||
// new list exhausted). Those are handled in the final while | ||
// loops at the end. | ||
// | ||
// * Example below: `oldHead` exceeded `oldTail`, so we're done | ||
// with the | ||
// main loop. Create the remaining part and insert it at the | ||
// new head position, and the update is complete. | ||
// | ||
// (oldHead > oldTail) | ||
// oldKeys: [0, 1, -, 3, 4, 5, 6] | ||
// newParts: [0, 2, 1, 4, 3, 7 ,6] <- create and insert 7 | ||
// newKeys: [0, 2, 1, 4, 3, 7, 6] | ||
// newHead ^ newTail | ||
// | ||
// * Note that the order of the if/else clauses is not important | ||
// to the | ||
// algorithm, as long as the null checks come first (to ensure | ||
// we're always working on valid old parts) and that the final | ||
// else clause comes last (since that's where the expensive | ||
// moves occur). The order of remaining clauses is is just a | ||
// simple guess at which cases will be most common. | ||
// | ||
// * TODO(kschaaf) Note, we could calculate the longest | ||
// increasing | ||
// subsequence (LIS) of old items in new position, and only | ||
// move those not in the LIS set. However that costs O(nlogn) | ||
// time and adds a bit more code, and only helps make rare | ||
// types of mutations require fewer moves. The above handles | ||
// removes, adds, reversal, swaps, and single moves of | ||
// contiguous items in linear time, in the minimum number of | ||
// moves. As the number of multiple moves where LIS might help | ||
// approaches a random shuffle, the LIS optimization becomes | ||
// less helpful, so it seems not worth the code at this point. | ||
// Could reconsider if a compelling case arises. | ||
while (oldHead <= oldTail && newHead <= newTail) { | ||
if (oldParts[oldHead] === null) { | ||
// `null` means old part at head has already been used below; skip | ||
oldHead++; | ||
} else if (oldParts[oldTail] === null) { | ||
// `null` means old part at tail has already been used below; skip | ||
oldTail--; | ||
} else if (oldKeys[oldHead] === newKeys[newHead]) { | ||
// Old head matches new head; update in place | ||
newParts[newHead] = | ||
updatePart(oldParts[oldHead]!, newValues[newHead]); | ||
oldHead++; | ||
newHead++; | ||
} else if (oldKeys[oldTail] === newKeys[newTail]) { | ||
// Old tail matches new tail; update in place | ||
newParts[newTail] = | ||
updatePart(oldParts[oldTail]!, newValues[newTail]); | ||
oldTail--; | ||
newTail--; | ||
} else if (oldKeys[oldHead] === newKeys[newTail]) { | ||
// Old head matches new tail; update and move to new tail | ||
newParts[newTail] = | ||
updatePart(oldParts[oldHead]!, newValues[newTail]); | ||
insertPartBefore( | ||
containerPart, oldParts[oldHead]!, newParts[newTail + 1]); | ||
oldHead++; | ||
newTail--; | ||
} else if (oldKeys[oldTail] === newKeys[newHead]) { | ||
// Old tail matches new head; update and move to new head | ||
newParts[newHead] = | ||
updatePart(oldParts[oldTail]!, newValues[newHead]); | ||
insertPartBefore( | ||
containerPart, oldParts[oldTail]!, oldParts[oldHead]!); | ||
oldTail--; | ||
newHead++; | ||
} else { | ||
if (newKeyToIndexMap === undefined) { | ||
// Lazily generate key-to-index maps, used for removals & moves | ||
// below | ||
newKeyToIndexMap = generateMap(newKeys, newHead, newTail); | ||
oldKeyToIndexMap = generateMap(oldKeys, oldHead, oldTail); | ||
} | ||
if (!newKeyToIndexMap.has(oldKeys[oldHead])) { | ||
// Old head is no longer in new list; remove | ||
removePart(oldParts[oldHead]!); | ||
oldHead++; | ||
} else if (!newKeyToIndexMap.has(oldKeys[oldTail])) { | ||
// Old tail is no longer in new list; remove | ||
removePart(oldParts[oldTail]!); | ||
oldTail--; | ||
} else { | ||
// Any mismatches at this point are due to additions or moves; see | ||
// if we have an old part we can reuse and move into place | ||
const oldIndex = oldKeyToIndexMap.get(newKeys[newHead]); | ||
const oldPart = | ||
oldIndex !== undefined ? oldParts[oldIndex] : null; | ||
if (oldPart === null) { | ||
// No old part for this value; create a new one and insert it | ||
const newPart = | ||
createAndInsertPart(containerPart, oldParts[oldHead]!); | ||
updatePart(newPart, newValues[newHead]); | ||
newParts[newHead] = newPart; | ||
} else { | ||
// Reuse old part | ||
newParts[newHead] = updatePart(oldPart, newValues[newHead]); | ||
insertPartBefore(containerPart, oldPart, oldParts[oldHead]!); | ||
// This marks the old part as having been used, so that it will | ||
// be skipped in the first two checks above | ||
oldParts[oldIndex as number] = null; | ||
} | ||
newHead++; | ||
} | ||
} | ||
} | ||
// Add parts for any remaining new values | ||
while (newHead <= newTail) { | ||
// For all remaining additions, we insert before last new tail, | ||
// since old pointers are no longer valid | ||
const newPart = | ||
createAndInsertPart(containerPart, newParts[newTail + 1]!); | ||
updatePart(newPart, newValues[newHead]); | ||
newParts[newHead++] = newPart; | ||
} | ||
// Remove any remaining unused old parts | ||
while (oldHead <= oldTail) { | ||
const oldPart = oldParts[oldHead++]; | ||
if (oldPart !== null) { | ||
removePart(oldPart); | ||
} | ||
} | ||
// Save order of new parts for next round | ||
partListCache.set(containerPart, newParts); | ||
keyListCache.set(containerPart, newKeys); | ||
}; | ||
}); | ||
while (oldHead <= oldTail && newHead <= newTail) { | ||
if (oldParts[oldHead] === null) { | ||
// `null` means old part at head has already been used | ||
// below; skip | ||
oldHead++; | ||
} else if (oldParts[oldTail] === null) { | ||
// `null` means old part at tail has already been used | ||
// below; skip | ||
oldTail--; | ||
} else if (oldKeys[oldHead] === newKeys[newHead]) { | ||
// Old head matches new head; update in place | ||
newParts[newHead] = | ||
updatePart(oldParts[oldHead]!, newValues[newHead]); | ||
oldHead++; | ||
newHead++; | ||
} else if (oldKeys[oldTail] === newKeys[newTail]) { | ||
// Old tail matches new tail; update in place | ||
newParts[newTail] = | ||
updatePart(oldParts[oldTail]!, newValues[newTail]); | ||
oldTail--; | ||
newTail--; | ||
} else if (oldKeys[oldHead] === newKeys[newTail]) { | ||
// Old head matches new tail; update and move to new tail | ||
newParts[newTail] = | ||
updatePart(oldParts[oldHead]!, newValues[newTail]); | ||
insertPartBefore( | ||
containerPart, | ||
oldParts[oldHead]!, | ||
newParts[newTail + 1]); | ||
oldHead++; | ||
newTail--; | ||
} else if (oldKeys[oldTail] === newKeys[newHead]) { | ||
// Old tail matches new head; update and move to new head | ||
newParts[newHead] = | ||
updatePart(oldParts[oldTail]!, newValues[newHead]); | ||
insertPartBefore( | ||
containerPart, oldParts[oldTail]!, oldParts[oldHead]!); | ||
oldTail--; | ||
newHead++; | ||
} else { | ||
if (newKeyToIndexMap === undefined) { | ||
// Lazily generate key-to-index maps, used for removals & | ||
// moves below | ||
newKeyToIndexMap = generateMap(newKeys, newHead, newTail); | ||
oldKeyToIndexMap = generateMap(oldKeys, oldHead, oldTail); | ||
} | ||
if (!newKeyToIndexMap.has(oldKeys[oldHead])) { | ||
// Old head is no longer in new list; remove | ||
removePart(oldParts[oldHead]!); | ||
oldHead++; | ||
} else if (!newKeyToIndexMap.has(oldKeys[oldTail])) { | ||
// Old tail is no longer in new list; remove | ||
removePart(oldParts[oldTail]!); | ||
oldTail--; | ||
} else { | ||
// Any mismatches at this point are due to additions or | ||
// moves; see if we have an old part we can reuse and move | ||
// into place | ||
const oldIndex = oldKeyToIndexMap.get(newKeys[newHead]); | ||
const oldPart = | ||
oldIndex !== undefined ? oldParts[oldIndex] : null; | ||
if (oldPart === null) { | ||
// No old part for this value; create a new one and | ||
// insert it | ||
const newPart = createAndInsertPart( | ||
containerPart, oldParts[oldHead]!); | ||
updatePart(newPart, newValues[newHead]); | ||
newParts[newHead] = newPart; | ||
} else { | ||
// Reuse old part | ||
newParts[newHead] = | ||
updatePart(oldPart, newValues[newHead]); | ||
insertPartBefore( | ||
containerPart, oldPart, oldParts[oldHead]!); | ||
// This marks the old part as having been used, so that | ||
// it will be skipped in the first two checks above | ||
oldParts[oldIndex as number] = null; | ||
} | ||
newHead++; | ||
} | ||
} | ||
} | ||
// Add parts for any remaining new values | ||
while (newHead <= newTail) { | ||
// For all remaining additions, we insert before last new | ||
// tail, since old pointers are no longer valid | ||
const newPart = createAndInsertPart( | ||
containerPart, newParts[newTail + 1]!); | ||
updatePart(newPart, newValues[newHead]); | ||
newParts[newHead++] = newPart; | ||
} | ||
// Remove any remaining unused old parts | ||
while (oldHead <= oldTail) { | ||
const oldPart = oldParts[oldHead++]; | ||
if (oldPart !== null) { | ||
removePart(oldPart); | ||
} | ||
} | ||
// Save order of new parts for next round | ||
partListCache.set(containerPart, newParts); | ||
keyListCache.set(containerPart, newKeys); | ||
}; | ||
}) as | ||
<T>(items: Iterable<T>, | ||
keyFnOrTemplate: KeyFn<T>|ItemTemplate<T>, | ||
template?: ItemTemplate<T>) => DirectiveFn; |
@@ -25,3 +25,3 @@ /** | ||
*/ | ||
const styleMapCache = new WeakMap(); | ||
const styleMapCache = new WeakMap<AttributePart, StyleInfo>(); | ||
@@ -34,3 +34,3 @@ /** | ||
// Note, could be a WeakSet, but prefer not requiring this polyfill. | ||
const styleMapStatics = new WeakMap(); | ||
const styleMapStatics = new WeakMap<AttributePart, true>(); | ||
@@ -75,2 +75,3 @@ /** | ||
if (name.indexOf('-') === -1) { | ||
// tslint:disable-next-line:no-any | ||
(style as any)[name] = null; | ||
@@ -86,2 +87,3 @@ } else { | ||
if (name.indexOf('-') === -1) { | ||
// tslint:disable-next-line:no-any | ||
(style as any)[name] = styleInfo[name]; | ||
@@ -88,0 +90,0 @@ } else { |
@@ -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: unknown; | ||
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,20 +38,19 @@ * Renders the result as HTML, rather than text. | ||
*/ | ||
const previousValues = new WeakMap<NodePart, string>(); | ||
export const unsafeHTML = directive((value: any) => (part: Part): void => { | ||
export const unsafeHTML = directive((value: unknown) => (part: Part): void => { | ||
if (!(part instanceof NodePart)) { | ||
throw new Error('unsafeHTML can only be used in text bindings'); | ||
} | ||
// 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 as string; // innerHTML casts to string internally | ||
const fragment = document.importNode(template.content, true); | ||
part.setValue(fragment); | ||
previousValues.set(part, {value, fragment}); | ||
}); |
@@ -49,3 +49,3 @@ /** | ||
*/ | ||
export const until = directive((...args: any[]) => (part: Part) => { | ||
export const until = directive((...args: unknown[]) => (part: Part) => { | ||
let state = _state.get(part)!; | ||
@@ -59,16 +59,15 @@ if (state === undefined) { | ||
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 | ||
if (isPrimitive(value) || typeof value.then !== 'function') { | ||
if (isPrimitive(value) || | ||
typeof (value as {then?: unknown}).then !== 'function') { | ||
part.setValue(value); | ||
@@ -81,2 +80,9 @@ state.lastRenderedIndex = i; | ||
// If this is a Promise we've already handled, skip it. | ||
if (state.lastRenderedIndex !== undefined && | ||
typeof (value as {then?: unknown}).then === 'function' && | ||
value === previousValues[i]) { | ||
continue; | ||
} | ||
// We have a Promise that we haven't seen before, so priorities may have | ||
@@ -83,0 +89,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,6 +15,13 @@ * @license | ||
/** | ||
* @module lit-html | ||
*/ | ||
import {Part} from './part.js'; | ||
const directives = new WeakMap<any, Boolean>(); | ||
const directives = new WeakMap<object, true>(); | ||
// tslint:disable-next-line:no-any | ||
export type DirectiveFactory = (...args: any[]) => object; | ||
export type DirectiveFn = (part: Part) => void; | ||
@@ -43,10 +49,12 @@ | ||
*/ | ||
export const directive = <F extends Function>(f: F): F => | ||
((...args: any[]) => { | ||
// tslint:disable-next-line:no-any | ||
export const directive = <F extends DirectiveFactory>(f: F): F => | ||
((...args: unknown[]) => { | ||
const d = f(...args); | ||
directives.set(d, true); | ||
return d; | ||
}) as unknown as F; | ||
}) as F; | ||
export const isDirective = (o: any) => | ||
typeof o === 'function' && directives.has(o); | ||
export const isDirective = (o: unknown): o is DirectiveFn => { | ||
return typeof o === 'function' && directives.has(o); | ||
}; |
@@ -15,4 +15,16 @@ /** | ||
/** | ||
* @module lit-html | ||
*/ | ||
interface MaybePolyfilledCe extends CustomElementRegistry { | ||
polyfillWrapFlushCallback?: object; | ||
} | ||
/** | ||
* True if the custom elements polyfill is in use. | ||
*/ | ||
export const isCEPolyfill = window.customElements !== undefined && | ||
(window.customElements as any).polyfillWrapFlushCallback !== undefined; | ||
(window.customElements as MaybePolyfilledCe).polyfillWrapFlushCallback !== | ||
undefined; | ||
@@ -19,0 +31,0 @@ /** |
@@ -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} */; | ||
@@ -41,3 +44,3 @@ /** | ||
const walker = | ||
document.createTreeWalker(content, walkerNodeFilter, null as any, false); | ||
document.createTreeWalker(content, walkerNodeFilter, null, false); | ||
let partIndex = nextActiveIndexInTemplateParts(parts); | ||
@@ -81,5 +84,4 @@ let part = parts[partIndex]; | ||
const countNodes = (node: Node) => { | ||
let count = (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) ? 0 : 1; | ||
const walker = | ||
document.createTreeWalker(node, walkerNodeFilter, null as any, false); | ||
let count = (node.nodeType === 11 /* Node.DOCUMENT_FRAGMENT_NODE */) ? 0 : 1; | ||
const walker = document.createTreeWalker(node, walkerNodeFilter, null, false); | ||
while (walker.nextNode()) { | ||
@@ -117,3 +119,3 @@ count++; | ||
const walker = | ||
document.createTreeWalker(content, walkerNodeFilter, null as any, false); | ||
document.createTreeWalker(content, walkerNodeFilter, null, false); | ||
let partIndex = nextActiveIndexInTemplateParts(parts); | ||
@@ -120,0 +122,0 @@ let insertCount = 0; |
/** | ||
* @license | ||
* Copyright (c) 2018 The Polymer Project Authors. All rights reserved. | ||
* This code may only be used under the BSD style license found at | ||
* http://polymer.github.io/LICENSE.txt | ||
* The complete set of authors may be found at | ||
* http://polymer.github.io/AUTHORS.txt | ||
* The complete set of contributors may be found at | ||
* http://polymer.github.io/CONTRIBUTORS.txt | ||
* Code distributed by Google as part of the polymer project is also | ||
* subject to an additional IP rights grant found at | ||
* http://polymer.github.io/PATENTS.txt | ||
*/ | ||
/** | ||
* @module lit-html | ||
*/ | ||
/** | ||
* The Part interface represents a dynamic part of a template instance rendered | ||
@@ -6,3 +24,3 @@ * by lit-html. | ||
export interface Part { | ||
value: any; | ||
value: unknown; | ||
@@ -13,3 +31,3 @@ /** | ||
*/ | ||
setValue(value: any): void; | ||
setValue(value: unknown): void; | ||
@@ -26,2 +44,7 @@ /** | ||
*/ | ||
export const noChange = {}; | ||
export const noChange: object = {}; | ||
/** | ||
* 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'; | ||
@@ -24,5 +28,9 @@ import {TemplateInstance} from './template-instance.js'; | ||
export const isPrimitive = (value: any) => | ||
(value === null || | ||
!(typeof value === 'object' || typeof value === 'function')); | ||
// https://tc39.github.io/ecma262/#sec-typeof-operator | ||
export type Primitive = null|undefined|boolean|number|string|Symbol|bigint; | ||
export const isPrimitive = (value: unknown): value is Primitive => { | ||
return ( | ||
value === null || | ||
!(typeof value === 'object' || typeof value === 'function')); | ||
}; | ||
@@ -57,3 +65,3 @@ /** | ||
protected _getValue(): any { | ||
protected _getValue(): unknown { | ||
const strings = this.strings; | ||
@@ -69,4 +77,6 @@ const l = strings.length - 1; | ||
if (v != null && | ||
(Array.isArray(v) || typeof v !== 'string' && v[Symbol.iterator])) { | ||
for (const t of v) { | ||
(Array.isArray(v) || | ||
// tslint:disable-next-line:no-any | ||
typeof v !== 'string' && (v as any)[Symbol.iterator])) { | ||
for (const t of v as Iterable<unknown>) { | ||
text += typeof t === 'string' ? t : String(t); | ||
@@ -87,3 +97,3 @@ } | ||
this.dirty = false; | ||
this.element.setAttribute(this.name, this._getValue()); | ||
this.element.setAttribute(this.name, this._getValue() as string); | ||
} | ||
@@ -95,3 +105,3 @@ } | ||
committer: AttributeCommitter; | ||
value: any = undefined; | ||
value: unknown = undefined; | ||
@@ -102,3 +112,3 @@ constructor(comitter: AttributeCommitter) { | ||
setValue(value: any): void { | ||
setValue(value: unknown): void { | ||
if (value !== noChange && (!isPrimitive(value) || value !== this.value)) { | ||
@@ -132,4 +142,4 @@ this.value = value; | ||
endNode!: Node; | ||
value: any = undefined; | ||
_pendingValue: any = undefined; | ||
value: unknown = undefined; | ||
_pendingValue: unknown = undefined; | ||
@@ -183,3 +193,3 @@ constructor(options: RenderOptions) { | ||
setValue(value: any): void { | ||
setValue(value: unknown): void { | ||
this._pendingValue = value; | ||
@@ -206,4 +216,10 @@ } | ||
this._commitNode(value); | ||
} else if (Array.isArray(value) || value[Symbol.iterator]) { | ||
this._commitIterable(value); | ||
} else if ( | ||
Array.isArray(value) || | ||
// tslint:disable-next-line:no-any | ||
(value as any)[Symbol.iterator]) { | ||
this._commitIterable(value as Iterable<unknown>); | ||
} else if (value === nothing) { | ||
this.value = nothing; | ||
this.clear(); | ||
} else { | ||
@@ -228,11 +244,11 @@ // Fallback, will render the string representation | ||
private _commitText(value: string): void { | ||
private _commitText(value: unknown): void { | ||
const node = this.startNode.nextSibling!; | ||
value = value == null ? '' : value; | ||
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 as string; | ||
} else { | ||
@@ -247,3 +263,4 @@ this._commitNode(document.createTextNode( | ||
const template = this.options.templateFactory(value); | ||
if (this.value && this.value.template === template) { | ||
if (this.value instanceof TemplateInstance && | ||
this.value.template === template) { | ||
this.value.update(value.values); | ||
@@ -264,3 +281,3 @@ } else { | ||
private _commitIterable(value: any): void { | ||
private _commitIterable(value: Iterable<unknown>): void { | ||
// For an Iterable, we create a new InstancePart per item, then set its | ||
@@ -330,4 +347,4 @@ // value to the item. This is a little bit of overhead for every item in | ||
strings: string[]; | ||
value: any = undefined; | ||
_pendingValue: any = undefined; | ||
value: unknown = undefined; | ||
_pendingValue: unknown = undefined; | ||
@@ -344,3 +361,3 @@ constructor(element: Element, name: string, strings: string[]) { | ||
setValue(value: any): void { | ||
setValue(value: unknown): void { | ||
this._pendingValue = value; | ||
@@ -403,2 +420,3 @@ } | ||
this.dirty = false; | ||
// tslint:disable-next-line:no-any | ||
(this.element as any)[this.name] = this._getValue(); | ||
@@ -424,3 +442,5 @@ } | ||
}; | ||
// tslint:disable-next-line:no-any | ||
window.addEventListener('test', options as any, options); | ||
// tslint:disable-next-line:no-any | ||
window.removeEventListener('test', options as any, options); | ||
@@ -430,2 +450,5 @@ } catch (_e) { | ||
type EventHandlerWithOptions = | ||
EventListenerOrEventListenerObject&Partial<AddEventListenerOptions>; | ||
export class EventPart implements Part { | ||
@@ -435,5 +458,5 @@ element: Element; | ||
eventContext?: EventTarget; | ||
value: any = undefined; | ||
value: undefined|EventHandlerWithOptions = undefined; | ||
_options?: AddEventListenerOptions; | ||
_pendingValue: any = undefined; | ||
_pendingValue: undefined|EventHandlerWithOptions = undefined; | ||
_boundHandleEvent: (event: Event) => void; | ||
@@ -448,3 +471,3 @@ | ||
setValue(value: any): void { | ||
setValue(value: undefined|EventHandlerWithOptions): void { | ||
this._pendingValue = value; | ||
@@ -456,3 +479,3 @@ } | ||
const directive = this._pendingValue; | ||
this._pendingValue = noChange; | ||
this._pendingValue = noChange as EventHandlerWithOptions; | ||
directive(this); | ||
@@ -484,3 +507,3 @@ } | ||
this.value = newListener; | ||
this._pendingValue = noChange; | ||
this._pendingValue = noChange as EventHandlerWithOptions; | ||
} | ||
@@ -492,3 +515,3 @@ | ||
} else { | ||
this.value.handleEvent(event); | ||
(this.value as EventListenerObject).handleEvent(event); | ||
} | ||
@@ -501,5 +524,5 @@ } | ||
// at all. Chrome 41 only reads `capture` if the argument is an object. | ||
const getOptions = (o: any) => o && | ||
const getOptions = (o: AddEventListenerOptions|undefined) => o && | ||
(eventOptionsSupported ? | ||
{capture: o.capture, passive: o.passive, once: o.once} : | ||
o.capture); | ||
o.capture as AddEventListenerOptions); |
@@ -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 | ||
*/ | ||
@@ -41,3 +44,3 @@ import {isCEPolyfill} from './dom.js'; | ||
update(values: any[]) { | ||
update(values: unknown[]) { | ||
let i = 0; | ||
@@ -76,3 +79,3 @@ for (const part of this._parts) { | ||
133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */, | ||
null as any, | ||
null, | ||
false); | ||
@@ -95,3 +98,3 @@ let node = walker.nextNode(); | ||
const part = this.processor.handleTextExpression(this.options); | ||
part.insertAfterNode(node); | ||
part.insertAfterNode(node.previousSibling!); | ||
this._parts.push(part); | ||
@@ -98,0 +101,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'; | ||
@@ -26,3 +30,3 @@ import {TemplateProcessor} from './template-processor.js'; | ||
strings: TemplateStringsArray; | ||
values: any[]; | ||
values: unknown[]; | ||
type: string; | ||
@@ -32,3 +36,3 @@ processor: TemplateProcessor; | ||
constructor( | ||
strings: TemplateStringsArray, values: any[], type: string, | ||
strings: TemplateStringsArray, values: unknown[], type: string, | ||
processor: TemplateProcessor) { | ||
@@ -49,7 +53,7 @@ this.strings = strings; | ||
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 | ||
@@ -59,10 +63,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; | ||
} | ||
@@ -69,0 +76,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 */ | ||
, | ||
null as any, | ||
133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */, | ||
null, | ||
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'; | ||
@@ -34,2 +52,13 @@ export {RenderOptions} from './lib/render-options.js'; | ||
declare global { | ||
interface Window { | ||
litHtmlVersions: string[]; | ||
} | ||
} | ||
// IMPORTANT: do not change the property name or the assignment expression. | ||
// This line will be used in regexes to search for lit-html usage. | ||
// TODO(justinfagnani): inject version number at build time | ||
(window['litHtmlVersions'] || (window['litHtmlVersions'] = [])).push('1.0.0'); | ||
/** | ||
@@ -39,3 +68,3 @@ * Interprets a template literal as an HTML template that can efficiently | ||
*/ | ||
export const html = (strings: TemplateStringsArray, ...values: any[]) => | ||
export const html = (strings: TemplateStringsArray, ...values: unknown[]) => | ||
new TemplateResult(strings, values, 'html', defaultTemplateProcessor); | ||
@@ -47,3 +76,3 @@ | ||
*/ | ||
export const svg = (strings: TemplateStringsArray, ...values: any[]) => | ||
export const svg = (strings: TemplateStringsArray, ...values: unknown[]) => | ||
new SVGTemplateResult(strings, values, 'svg', defaultTemplateProcessor); |
@@ -30,2 +30,3 @@ /** | ||
// tslint:disable-next-line:no-any | ||
const upgrade = (template: any) => { | ||
@@ -50,4 +51,5 @@ template.content = contentDoc.createDocumentFragment(); | ||
const capturedCreateElement = Document.prototype.createElement; | ||
Document.prototype.createElement = function createElement() { | ||
let el = capturedCreateElement.apply(this, arguments); | ||
Document.prototype.createElement = function createElement( | ||
tagName: string, options?: ElementCreationOptions) { | ||
let el = capturedCreateElement.call(this, tagName, options); | ||
if (el.localName === 'template') { | ||
@@ -54,0 +56,0 @@ el = capturedCreateElement.call(this, 'div'); |
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
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
545992
15
153
7337
55