es5-lit-html
Version:
An ES5 transpiled version of lit-html
687 lines (563 loc) • 22 kB
JavaScript
"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);
};