UNPKG

es5-lit-html

Version:
687 lines (563 loc) 22 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EventPart = exports.PropertyPart = exports.PropertyCommitter = exports.BooleanAttributePart = exports.NodePart = exports.AttributePart = exports.AttributeCommitter = exports.isIterable = exports.isPrimitive = void 0; var _directive = require("./directive.js"); var _dom = require("./dom.js"); var _part = require("./part.js"); var _templateInstance = require("./template-instance.js"); var _templateResult = require("./template-result.js"); var _template = require("./template.js"); function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } var isPrimitive = function isPrimitive(value) { return value === null || !(_typeof(value) === 'object' || typeof value === 'function'); }; exports.isPrimitive = isPrimitive; var isIterable = function isIterable(value) { return Array.isArray(value) || // tslint:disable-next-line:no-any !!(value && value[Symbol.iterator]); }; /** * Writes attribute values to the DOM for a group of AttributeParts bound to a * single attibute. The value is only set once even if there are multiple parts * for an attribute. */ exports.isIterable = isIterable; var AttributeCommitter = /*#__PURE__*/ function () { function AttributeCommitter(element, name, strings) { _classCallCheck(this, AttributeCommitter); this.dirty = true; this.element = element; this.name = name; this.strings = strings; this.parts = []; for (var i = 0; i < strings.length - 1; i++) { this.parts[i] = this._createPart(); } } /** * Creates a single part. Override this to create a differnt type of part. */ _createClass(AttributeCommitter, [{ key: "_createPart", value: function _createPart() { return new AttributePart(this); } }, { key: "_getValue", value: function _getValue() { var strings = this.strings; var l = strings.length - 1; var text = ''; for (var i = 0; i < l; i++) { text += strings[i]; var part = this.parts[i]; if (part !== undefined) { var v = part.value; if (isPrimitive(v) || !isIterable(v)) { text += typeof v === 'string' ? v : String(v); } else { var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = v[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var t = _step.value; text += typeof t === 'string' ? t : String(t); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator["return"] != null) { _iterator["return"](); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } } } text += strings[l]; return text; } }, { key: "commit", value: function commit() { if (this.dirty) { this.dirty = false; this.element.setAttribute(this.name, this._getValue()); } } }]); return AttributeCommitter; }(); /** * A Part that controls all or part of an attribute value. */ exports.AttributeCommitter = AttributeCommitter; var AttributePart = /*#__PURE__*/ function () { function AttributePart(committer) { _classCallCheck(this, AttributePart); this.value = undefined; this.committer = committer; } _createClass(AttributePart, [{ key: "setValue", value: function setValue(value) { if (value !== _part.noChange && (!isPrimitive(value) || value !== this.value)) { this.value = value; // If the value is a not a directive, dirty the committer so that it'll // call setAttribute. If the value is a directive, it'll dirty the // committer if it calls setValue(). if (!(0, _directive.isDirective)(value)) { this.committer.dirty = true; } } } }, { key: "commit", value: function commit() { while ((0, _directive.isDirective)(this.value)) { var directive = this.value; this.value = _part.noChange; directive(this); } if (this.value === _part.noChange) { return; } this.committer.commit(); } }]); return AttributePart; }(); /** * A Part that controls a location within a Node tree. Like a Range, NodePart * has start and end locations and can set and update the Nodes between those * locations. * * NodeParts support several value types: primitives, Nodes, TemplateResults, * as well as arrays and iterables of those types. */ exports.AttributePart = AttributePart; var NodePart = /*#__PURE__*/ function () { function NodePart(options) { _classCallCheck(this, NodePart); this.value = undefined; this.__pendingValue = undefined; this.options = options; } /** * Appends this part into a container. * * This part must be empty, as its contents are not automatically moved. */ _createClass(NodePart, [{ key: "appendInto", value: function appendInto(container) { this.startNode = container.appendChild((0, _template.createMarker)()); this.endNode = container.appendChild((0, _template.createMarker)()); } /** * Inserts this part after the `ref` node (between `ref` and `ref`'s next * sibling). Both `ref` and its next sibling must be static, unchanging nodes * such as those that appear in a literal section of a template. * * This part must be empty, as its contents are not automatically moved. */ }, { key: "insertAfterNode", value: function insertAfterNode(ref) { this.startNode = ref; this.endNode = ref.nextSibling; } /** * Appends this part into a parent part. * * This part must be empty, as its contents are not automatically moved. */ }, { key: "appendIntoPart", value: function appendIntoPart(part) { part.__insert(this.startNode = (0, _template.createMarker)()); part.__insert(this.endNode = (0, _template.createMarker)()); } /** * Inserts this part after the `ref` part. * * This part must be empty, as its contents are not automatically moved. */ }, { key: "insertAfterPart", value: function insertAfterPart(ref) { ref.__insert(this.startNode = (0, _template.createMarker)()); this.endNode = ref.endNode; ref.endNode = this.startNode; } }, { key: "setValue", value: function setValue(value) { this.__pendingValue = value; } }, { key: "commit", value: function commit() { while ((0, _directive.isDirective)(this.__pendingValue)) { var directive = this.__pendingValue; this.__pendingValue = _part.noChange; directive(this); } var value = this.__pendingValue; if (value === _part.noChange) { return; } if (isPrimitive(value)) { if (value !== this.value) { this.__commitText(value); } } else if (value instanceof _templateResult.TemplateResult) { this.__commitTemplateResult(value); } else if (value instanceof Node) { this.__commitNode(value); } else if (isIterable(value)) { this.__commitIterable(value); } else if (value === _part.nothing) { this.value = _part.nothing; this.clear(); } else { // Fallback, will render the string representation this.__commitText(value); } } }, { key: "__insert", value: function __insert(node) { this.endNode.parentNode.insertBefore(node, this.endNode); } }, { key: "__commitNode", value: function __commitNode(value) { if (this.value === value) { return; } this.clear(); this.__insert(value); this.value = value; } }, { key: "__commitText", value: function __commitText(value) { var node = this.startNode.nextSibling; value = value == null ? '' : value; // If `value` isn't already a string, we explicitly convert it here in case // it can't be implicitly converted - i.e. it's a symbol. var valueAsString = typeof value === 'string' ? value : String(value); if (node === this.endNode.previousSibling && 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.data = valueAsString; } else { this.__commitNode(document.createTextNode(valueAsString)); } this.value = value; } }, { key: "__commitTemplateResult", value: function __commitTemplateResult(value) { var template = this.options.templateFactory(value); if (this.value instanceof _templateInstance.TemplateInstance && this.value.template === template) { this.value.update(value.values); } else { // Make sure we propagate the template processor from the TemplateResult // so that we use its syntax extension, etc. The template factory comes // from the render function options so that it can control template // caching and preprocessing. var instance = new _templateInstance.TemplateInstance(template, value.processor, this.options); var fragment = instance._clone(); instance.update(value.values); this.__commitNode(fragment); this.value = instance; } } }, { key: "__commitIterable", value: function __commitIterable(value) { // For an Iterable, we create a new InstancePart per item, then set its // value to the item. This is a little bit of overhead for every item in // an Iterable, but it lets us recurse easily and efficiently update Arrays // of TemplateResults that will be commonly returned from expressions like: // array.map((i) => html`${i}`), by reusing existing TemplateInstances. // If _value is an array, then the previous render was of an // iterable and _value will contain the NodeParts from the previous // render. If _value is not an array, clear this part and make a new // array for NodeParts. if (!Array.isArray(this.value)) { this.value = []; this.clear(); } // Lets us keep track of how many items we stamped so we can clear leftover // items from a previous render var itemParts = this.value; var partIndex = 0; var itemPart; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = value[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var item = _step2.value; // Try to reuse an existing part itemPart = itemParts[partIndex]; // If no existing part, create a new one if (itemPart === undefined) { itemPart = new NodePart(this.options); itemParts.push(itemPart); if (partIndex === 0) { itemPart.appendIntoPart(this); } else { itemPart.insertAfterPart(itemParts[partIndex - 1]); } } itemPart.setValue(item); itemPart.commit(); partIndex++; } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2["return"] != null) { _iterator2["return"](); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } if (partIndex < itemParts.length) { // Truncate the parts array so _value reflects the current state itemParts.length = partIndex; this.clear(itemPart && itemPart.endNode); } } }, { key: "clear", value: function clear() { var startNode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.startNode; (0, _dom.removeNodes)(this.startNode.parentNode, startNode.nextSibling, this.endNode); } }]); return NodePart; }(); /** * 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. */ exports.NodePart = NodePart; var BooleanAttributePart = /*#__PURE__*/ function () { function BooleanAttributePart(element, name, strings) { _classCallCheck(this, BooleanAttributePart); this.value = undefined; this.__pendingValue = undefined; if (strings.length !== 2 || strings[0] !== '' || strings[1] !== '') { throw new Error('Boolean attributes can only contain a single expression'); } this.element = element; this.name = name; this.strings = strings; } _createClass(BooleanAttributePart, [{ key: "setValue", value: function setValue(value) { this.__pendingValue = value; } }, { key: "commit", value: function commit() { while ((0, _directive.isDirective)(this.__pendingValue)) { var directive = this.__pendingValue; this.__pendingValue = _part.noChange; directive(this); } if (this.__pendingValue === _part.noChange) { return; } var value = !!this.__pendingValue; if (this.value !== value) { if (value) { this.element.setAttribute(this.name, ''); } else { this.element.removeAttribute(this.name); } this.value = value; } this.__pendingValue = _part.noChange; } }]); return BooleanAttributePart; }(); /** * Sets attribute values for PropertyParts, so that the value is only set once * even if there are multiple parts for a property. * * If an expression controls the whole property value, then the value is simply * assigned to the property under control. If there are string literals or * multiple expressions, then the strings are expressions are interpolated into * a string first. */ exports.BooleanAttributePart = BooleanAttributePart; var PropertyCommitter = /*#__PURE__*/ function (_AttributeCommitter) { _inherits(PropertyCommitter, _AttributeCommitter); function PropertyCommitter(element, name, strings) { var _this; _classCallCheck(this, PropertyCommitter); _this = _possibleConstructorReturn(this, _getPrototypeOf(PropertyCommitter).call(this, element, name, strings)); _this.single = strings.length === 2 && strings[0] === '' && strings[1] === ''; return _this; } _createClass(PropertyCommitter, [{ key: "_createPart", value: function _createPart() { return new PropertyPart(this); } }, { key: "_getValue", value: function _getValue() { if (this.single) { return this.parts[0].value; } return _get(_getPrototypeOf(PropertyCommitter.prototype), "_getValue", this).call(this); } }, { key: "commit", value: function commit() { if (this.dirty) { this.dirty = false; // tslint:disable-next-line:no-any this.element[this.name] = this._getValue(); } } }]); return PropertyCommitter; }(AttributeCommitter); exports.PropertyCommitter = PropertyCommitter; var PropertyPart = /*#__PURE__*/ function (_AttributePart) { _inherits(PropertyPart, _AttributePart); function PropertyPart() { _classCallCheck(this, PropertyPart); return _possibleConstructorReturn(this, _getPrototypeOf(PropertyPart).apply(this, arguments)); } return PropertyPart; }(AttributePart); // Detect event listener options support. If the `capture` property is read // from the options object, then options are supported. If not, then the thrid // argument to add/removeEventListener is interpreted as the boolean capture // value so we should only pass the `capture` property. exports.PropertyPart = PropertyPart; var eventOptionsSupported = false; try { var options = { get capture() { eventOptionsSupported = true; return false; } }; // tslint:disable-next-line:no-any window.addEventListener('test', options, options); // tslint:disable-next-line:no-any window.removeEventListener('test', options, options); } catch (_e) {} var EventPart = /*#__PURE__*/ function () { function EventPart(element, eventName, eventContext) { var _this2 = this; _classCallCheck(this, EventPart); this.value = undefined; this.__pendingValue = undefined; this.element = element; this.eventName = eventName; this.eventContext = eventContext; this.__boundHandleEvent = function (e) { return _this2.handleEvent(e); }; } _createClass(EventPart, [{ key: "setValue", value: function setValue(value) { this.__pendingValue = value; } }, { key: "commit", value: function commit() { while ((0, _directive.isDirective)(this.__pendingValue)) { var directive = this.__pendingValue; this.__pendingValue = _part.noChange; directive(this); } if (this.__pendingValue === _part.noChange) { return; } var newListener = this.__pendingValue; var oldListener = this.value; var shouldRemoveListener = newListener == null || oldListener != null && (newListener.capture !== oldListener.capture || newListener.once !== oldListener.once || newListener.passive !== oldListener.passive); var shouldAddListener = newListener != null && (oldListener == null || shouldRemoveListener); if (shouldRemoveListener) { this.element.removeEventListener(this.eventName, this.__boundHandleEvent, this.__options); } if (shouldAddListener) { this.__options = getOptions(newListener); this.element.addEventListener(this.eventName, this.__boundHandleEvent, this.__options); } this.value = newListener; this.__pendingValue = _part.noChange; } }, { key: "handleEvent", value: function handleEvent(event) { if (typeof this.value === 'function') { this.value.call(this.eventContext || this.element, event); } else { this.value.handleEvent(event); } } }]); return EventPart; }(); // We copy options because of the inconsistent behavior of browsers when reading // the third argument of add/removeEventListener. IE11 doesn't support options // at all. Chrome 41 only reads `capture` if the argument is an object. exports.EventPart = EventPart; var getOptions = function getOptions(o) { return o && (eventOptionsSupported ? { capture: o.capture, passive: o.passive, once: o.once } : o.capture); };