/** * @license * Copyright (c) 2017 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 */ import {AttributePart, defaultPartCallback, directiveValue, getValue, Part, SVGTemplateResult, TemplateInstance, TemplatePart, TemplateResult} from '../lit-html.js'; export {render} from '../lit-html.js'; /** * Interprets a template literal as a lit-extended HTML template. */ export const html = (strings: TemplateStringsArray, ...values: any[]) => new TemplateResult(strings, values, 'html', extendedPartCallback); /** * Interprets a template literal as a lit-extended SVG template. */ export const svg = (strings: TemplateStringsArray, ...values: any[]) => new SVGTemplateResult(strings, values, 'svg', extendedPartCallback); /** * A PartCallback which allows templates to set properties and declarative * event handlers. * * Properties are set by default, instead of attributes. Attribute names in * lit-html templates preserve case, so properties are case sensitive. If an * expression takes up an entire attribute value, then the property is set to * that value. If an expression is interpolated with a string or other * expressions then the property is set to the string result of the * interpolation. * * To set an attribute instead of a property, append a `$` suffix to the * attribute name. * * Example: * * html`` * * To set an event handler, prefix the attribute name with `on-`: * * Example: * * html`` * */ export const extendedPartCallback = (instance: TemplateInstance, templatePart: TemplatePart, node: Node): Part => { if (templatePart.type === 'attribute') { if (templatePart.rawName!.startsWith('on-')) { const eventName = templatePart.rawName!.slice(3); return new EventPart(instance, node as Element, eventName); } if (templatePart.name!.endsWith('$')) { const name = templatePart.name!.slice(0, -1); return new AttributePart( instance, node as Element, name, templatePart.strings!); } if (templatePart.name!.endsWith('?')) { const name = templatePart.name!.slice(0, -1); return new BooleanAttributePart( instance, node as Element, name, templatePart.strings!); } return new PropertyPart( instance, node as Element, templatePart.rawName!, templatePart.strings!); } return defaultPartCallback(instance, templatePart, node); }; /** * Implements a boolean attribute, roughly as defined in the HTML * specification. * * If the value is truthy, then the attribute is present with a value of * ''. If the value is falsey, the attribute is removed. */ export class BooleanAttributePart extends AttributePart { setValue(values: any[], startIndex: number): void { const s = this.strings; if (s.length === 2 && s[0] === '' && s[1] === '') { const value = getValue(this, values[startIndex]); if (value === directiveValue) { return; } if (value) { this.element.setAttribute(this.name, ''); } else { this.element.removeAttribute(this.name); } } else { throw new Error( 'boolean attributes can only contain a single expression'); } } } export class PropertyPart extends AttributePart { setValue(values: any[], startIndex: number): void { const s = this.strings; let value: any; if (this._equalToPreviousValues(values, startIndex)) { return; } if (s.length === 2 && s[0] === '' && s[1] === '') { // An expression that occupies the whole attribute value will leave // leading and trailing empty strings. value = getValue(this, values[startIndex]); } else { // Interpolation, so interpolate value = this._interpolate(values, startIndex); } if (value !== directiveValue) { (this.element as any)[this.name] = value; } this._previousValues = values; } } export class EventPart implements Part { instance: TemplateInstance; element: Element; eventName: string; private _listener: any; constructor(instance: TemplateInstance, element: Element, eventName: string) { this.instance = instance; this.element = element; this.eventName = eventName; } setValue(value: any): void { const listener = getValue(this, value); const previous = this._listener; if (listener === previous) { return; } this._listener = listener; if (previous != null) { this.element.removeEventListener(this.eventName, previous); } if (listener != null) { this.element.addEventListener(this.eventName, listener); } } }