UNPKG

63.1 kBJavaScriptView Raw
1/**
2 * @license
3 * Copyright 2017 Google LLC
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6const DEV_MODE = true;
7const ENABLE_EXTRA_SECURITY_HOOKS = true;
8const ENABLE_SHADYDOM_NOPATCH = true;
9const NODE_MODE = false;
10// Allows minifiers to rename references to globalThis
11const global = globalThis;
12/**
13 * Useful for visualizing and logging insights into what the Lit template system is doing.
14 *
15 * Compiled out of prod mode builds.
16 */
17const debugLogEvent = DEV_MODE
18 ? (event) => {
19 const shouldEmit = global
20 .emitLitDebugLogEvents;
21 if (!shouldEmit) {
22 return;
23 }
24 global.dispatchEvent(new CustomEvent('lit-debug', {
25 detail: event,
26 }));
27 }
28 : undefined;
29// Used for connecting beginRender and endRender events when there are nested
30// renders when errors are thrown preventing an endRender event from being
31// called.
32let debugLogRenderId = 0;
33let issueWarning;
34if (DEV_MODE) {
35 global.litIssuedWarnings ??= new Set();
36 // Issue a warning, if we haven't already.
37 issueWarning = (code, warning) => {
38 warning += code
39 ? ` See https://lit.dev/msg/${code} for more information.`
40 : '';
41 if (!global.litIssuedWarnings.has(warning)) {
42 console.warn(warning);
43 global.litIssuedWarnings.add(warning);
44 }
45 };
46 issueWarning('dev-mode', `Lit is in dev mode. Not recommended for production!`);
47}
48const wrap = ENABLE_SHADYDOM_NOPATCH &&
49 global.ShadyDOM?.inUse &&
50 global.ShadyDOM?.noPatch === true
51 ? global.ShadyDOM.wrap
52 : (node) => node;
53const trustedTypes = global.trustedTypes;
54/**
55 * Our TrustedTypePolicy for HTML which is declared using the html template
56 * tag function.
57 *
58 * That HTML is a developer-authored constant, and is parsed with innerHTML
59 * before any untrusted expressions have been mixed in. Therefor it is
60 * considered safe by construction.
61 */
62const policy = trustedTypes
63 ? trustedTypes.createPolicy('lit-html', {
64 createHTML: (s) => s,
65 })
66 : undefined;
67const identityFunction = (value) => value;
68const noopSanitizer = (_node, _name, _type) => identityFunction;
69/** Sets the global sanitizer factory. */
70const setSanitizer = (newSanitizer) => {
71 if (!ENABLE_EXTRA_SECURITY_HOOKS) {
72 return;
73 }
74 if (sanitizerFactoryInternal !== noopSanitizer) {
75 throw new Error(`Attempted to overwrite existing lit-html security policy.` +
76 ` setSanitizeDOMValueFactory should be called at most once.`);
77 }
78 sanitizerFactoryInternal = newSanitizer;
79};
80/**
81 * Only used in internal tests, not a part of the public API.
82 */
83const _testOnlyClearSanitizerFactoryDoNotCallOrElse = () => {
84 sanitizerFactoryInternal = noopSanitizer;
85};
86const createSanitizer = (node, name, type) => {
87 return sanitizerFactoryInternal(node, name, type);
88};
89// Added to an attribute name to mark the attribute as bound so we can find
90// it easily.
91const boundAttributeSuffix = '$lit$';
92// This marker is used in many syntactic positions in HTML, so it must be
93// a valid element name and attribute name. We don't support dynamic names (yet)
94// but this at least ensures that the parse tree is closer to the template
95// intention.
96const marker = `lit$${Math.random().toFixed(9).slice(2)}$`;
97// String used to tell if a comment is a marker comment
98const markerMatch = '?' + marker;
99// Text used to insert a comment marker node. We use processing instruction
100// syntax because it's slightly smaller, but parses as a comment node.
101const nodeMarker = `<${markerMatch}>`;
102const d = NODE_MODE && global.document === undefined
103 ? {
104 createTreeWalker() {
105 return {};
106 },
107 }
108 : document;
109// Creates a dynamic marker. We never have to search for these in the DOM.
110const createMarker = () => d.createComment('');
111const isPrimitive = (value) => value === null || (typeof value != 'object' && typeof value != 'function');
112const isArray = Array.isArray;
113const isIterable = (value) => isArray(value) ||
114 // eslint-disable-next-line @typescript-eslint/no-explicit-any
115 typeof value?.[Symbol.iterator] === 'function';
116const SPACE_CHAR = `[ \t\n\f\r]`;
117const ATTR_VALUE_CHAR = `[^ \t\n\f\r"'\`<>=]`;
118const NAME_CHAR = `[^\\s"'>=/]`;
119// These regexes represent the five parsing states that we care about in the
120// Template's HTML scanner. They match the *end* of the state they're named
121// after.
122// Depending on the match, we transition to a new state. If there's no match,
123// we stay in the same state.
124// Note that the regexes are stateful. We utilize lastIndex and sync it
125// across the multiple regexes used. In addition to the five regexes below
126// we also dynamically create a regex to find the matching end tags for raw
127// text elements.
128/**
129 * End of text is: `<` followed by:
130 * (comment start) or (tag) or (dynamic tag binding)
131 */
132const textEndRegex = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g;
133const COMMENT_START = 1;
134const TAG_NAME = 2;
135const DYNAMIC_TAG_NAME = 3;
136const commentEndRegex = /-->/g;
137/**
138 * Comments not started with <!--, like </{, can be ended by a single `>`
139 */
140const comment2EndRegex = />/g;
141/**
142 * The tagEnd regex matches the end of the "inside an opening" tag syntax
143 * position. It either matches a `>`, an attribute-like sequence, or the end
144 * of the string after a space (attribute-name position ending).
145 *
146 * See attributes in the HTML spec:
147 * https://www.w3.org/TR/html5/syntax.html#elements-attributes
148 *
149 * " \t\n\f\r" are HTML space characters:
150 * https://infra.spec.whatwg.org/#ascii-whitespace
151 *
152 * So an attribute is:
153 * * The name: any character except a whitespace character, ("), ('), ">",
154 * "=", or "/". Note: this is different from the HTML spec which also excludes control characters.
155 * * Followed by zero or more space characters
156 * * Followed by "="
157 * * Followed by zero or more space characters
158 * * Followed by:
159 * * Any character except space, ('), ("), "<", ">", "=", (`), or
160 * * (") then any non-("), or
161 * * (') then any non-(')
162 */
163const tagEndRegex = new RegExp(`>|${SPACE_CHAR}(?:(${NAME_CHAR}+)(${SPACE_CHAR}*=${SPACE_CHAR}*(?:${ATTR_VALUE_CHAR}|("|')|))|$)`, 'g');
164const ENTIRE_MATCH = 0;
165const ATTRIBUTE_NAME = 1;
166const SPACES_AND_EQUALS = 2;
167const QUOTE_CHAR = 3;
168const singleQuoteAttrEndRegex = /'/g;
169const doubleQuoteAttrEndRegex = /"/g;
170/**
171 * Matches the raw text elements.
172 *
173 * Comments are not parsed within raw text elements, so we need to search their
174 * text content for marker strings.
175 */
176const rawTextElement = /^(?:script|style|textarea|title)$/i;
177/** TemplateResult types */
178const HTML_RESULT = 1;
179const SVG_RESULT = 2;
180// TemplatePart types
181// IMPORTANT: these must match the values in PartType
182const ATTRIBUTE_PART = 1;
183const CHILD_PART = 2;
184const PROPERTY_PART = 3;
185const BOOLEAN_ATTRIBUTE_PART = 4;
186const EVENT_PART = 5;
187const ELEMENT_PART = 6;
188const COMMENT_PART = 7;
189/**
190 * Generates a template literal tag function that returns a TemplateResult with
191 * the given result type.
192 */
193const tag = (type) => (strings, ...values) => {
194 // Warn against templates octal escape sequences
195 // We do this here rather than in render so that the warning is closer to the
196 // template definition.
197 if (DEV_MODE && strings.some((s) => s === undefined)) {
198 console.warn('Some template strings are undefined.\n' +
199 'This is probably caused by illegal octal escape sequences.');
200 }
201 if (DEV_MODE) {
202 // Import static-html.js results in a circular dependency which g3 doesn't
203 // handle. Instead we know that static values must have the field
204 // `_$litStatic$`.
205 if (values.some((val) => val?.['_$litStatic$'])) {
206 issueWarning('', `Static values 'literal' or 'unsafeStatic' cannot be used as values to non-static templates.\n` +
207 `Please use the static 'html' tag function. See https://lit.dev/docs/templates/expressions/#static-expressions`);
208 }
209 }
210 return {
211 // This property needs to remain unminified.
212 ['_$litType$']: type,
213 strings,
214 values,
215 };
216};
217/**
218 * Interprets a template literal as an HTML template that can efficiently
219 * render to and update a container.
220 *
221 * ```ts
222 * const header = (title: string) => html`<h1>${title}</h1>`;
223 * ```
224 *
225 * The `html` tag returns a description of the DOM to render as a value. It is
226 * lazy, meaning no work is done until the template is rendered. When rendering,
227 * if a template comes from the same expression as a previously rendered result,
228 * it's efficiently updated instead of replaced.
229 */
230export const html = tag(HTML_RESULT);
231/**
232 * Interprets a template literal as an SVG fragment that can efficiently
233 * render to and update a container.
234 *
235 * ```ts
236 * const rect = svg`<rect width="10" height="10"></rect>`;
237 *
238 * const myImage = html`
239 * <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg">
240 * ${rect}
241 * </svg>`;
242 * ```
243 *
244 * The `svg` *tag function* should only be used for SVG fragments, or elements
245 * that would be contained **inside** an `<svg>` HTML element. A common error is
246 * placing an `<svg>` *element* in a template tagged with the `svg` tag
247 * function. The `<svg>` element is an HTML element and should be used within a
248 * template tagged with the {@linkcode html} tag function.
249 *
250 * In LitElement usage, it's invalid to return an SVG fragment from the
251 * `render()` method, as the SVG fragment will be contained within the element's
252 * shadow root and thus cannot be used within an `<svg>` HTML element.
253 */
254export const svg = tag(SVG_RESULT);
255/**
256 * A sentinel value that signals that a value was handled by a directive and
257 * should not be written to the DOM.
258 */
259export const noChange = Symbol.for('lit-noChange');
260/**
261 * A sentinel value that signals a ChildPart to fully clear its content.
262 *
263 * ```ts
264 * const button = html`${
265 * user.isAdmin
266 * ? html`<button>DELETE</button>`
267 * : nothing
268 * }`;
269 * ```
270 *
271 * Prefer using `nothing` over other falsy values as it provides a consistent
272 * behavior between various expression binding contexts.
273 *
274 * In child expressions, `undefined`, `null`, `''`, and `nothing` all behave the
275 * same and render no nodes. In attribute expressions, `nothing` _removes_ the
276 * attribute, while `undefined` and `null` will render an empty string. In
277 * property expressions `nothing` becomes `undefined`.
278 */
279export const nothing = Symbol.for('lit-nothing');
280/**
281 * The cache of prepared templates, keyed by the tagged TemplateStringsArray
282 * and _not_ accounting for the specific template tag used. This means that
283 * template tags cannot be dynamic - the must statically be one of html, svg,
284 * or attr. This restriction simplifies the cache lookup, which is on the hot
285 * path for rendering.
286 */
287const templateCache = new WeakMap();
288const walker = d.createTreeWalker(d, 129 /* NodeFilter.SHOW_{ELEMENT|COMMENT} */);
289let sanitizerFactoryInternal = noopSanitizer;
290function trustFromTemplateString(tsa, stringFromTSA) {
291 // A security check to prevent spoofing of Lit template results.
292 // In the future, we may be able to replace this with Array.isTemplateObject,
293 // though we might need to make that check inside of the html and svg
294 // functions, because precompiled templates don't come in as
295 // TemplateStringArray objects.
296 if (!Array.isArray(tsa) || !tsa.hasOwnProperty('raw')) {
297 let message = 'invalid template strings array';
298 if (DEV_MODE) {
299 message = `
300 Internal Error: expected template strings to be an array
301 with a 'raw' field. Faking a template strings array by
302 calling html or svg like an ordinary function is effectively
303 the same as calling unsafeHtml and can lead to major security
304 issues, e.g. opening your code up to XSS attacks.
305 If you're using the html or svg tagged template functions normally
306 and still seeing this error, please file a bug at
307 https://github.com/lit/lit/issues/new?template=bug_report.md
308 and include information about your build tooling, if any.
309 `
310 .trim()
311 .replace(/\n */g, '\n');
312 }
313 throw new Error(message);
314 }
315 return policy !== undefined
316 ? policy.createHTML(stringFromTSA)
317 : stringFromTSA;
318}
319/**
320 * Returns an HTML string for the given TemplateStringsArray and result type
321 * (HTML or SVG), along with the case-sensitive bound attribute names in
322 * template order. The HTML contains comment markers denoting the `ChildPart`s
323 * and suffixes on bound attributes denoting the `AttributeParts`.
324 *
325 * @param strings template strings array
326 * @param type HTML or SVG
327 * @return Array containing `[html, attrNames]` (array returned for terseness,
328 * to avoid object fields since this code is shared with non-minified SSR
329 * code)
330 */
331const getTemplateHtml = (strings, type) => {
332 // Insert makers into the template HTML to represent the position of
333 // bindings. The following code scans the template strings to determine the
334 // syntactic position of the bindings. They can be in text position, where
335 // we insert an HTML comment, attribute value position, where we insert a
336 // sentinel string and re-write the attribute name, or inside a tag where
337 // we insert the sentinel string.
338 const l = strings.length - 1;
339 // Stores the case-sensitive bound attribute names in the order of their
340 // parts. ElementParts are also reflected in this array as undefined
341 // rather than a string, to disambiguate from attribute bindings.
342 const attrNames = [];
343 let html = type === SVG_RESULT ? '<svg>' : '';
344 // When we're inside a raw text tag (not it's text content), the regex
345 // will still be tagRegex so we can find attributes, but will switch to
346 // this regex when the tag ends.
347 let rawTextEndRegex;
348 // The current parsing state, represented as a reference to one of the
349 // regexes
350 let regex = textEndRegex;
351 for (let i = 0; i < l; i++) {
352 const s = strings[i];
353 // The index of the end of the last attribute name. When this is
354 // positive at end of a string, it means we're in an attribute value
355 // position and need to rewrite the attribute name.
356 // We also use a special value of -2 to indicate that we encountered
357 // the end of a string in attribute name position.
358 let attrNameEndIndex = -1;
359 let attrName;
360 let lastIndex = 0;
361 let match;
362 // The conditions in this loop handle the current parse state, and the
363 // assignments to the `regex` variable are the state transitions.
364 while (lastIndex < s.length) {
365 // Make sure we start searching from where we previously left off
366 regex.lastIndex = lastIndex;
367 match = regex.exec(s);
368 if (match === null) {
369 break;
370 }
371 lastIndex = regex.lastIndex;
372 if (regex === textEndRegex) {
373 if (match[COMMENT_START] === '!--') {
374 regex = commentEndRegex;
375 }
376 else if (match[COMMENT_START] !== undefined) {
377 // We started a weird comment, like </{
378 regex = comment2EndRegex;
379 }
380 else if (match[TAG_NAME] !== undefined) {
381 if (rawTextElement.test(match[TAG_NAME])) {
382 // Record if we encounter a raw-text element. We'll switch to
383 // this regex at the end of the tag.
384 rawTextEndRegex = new RegExp(`</${match[TAG_NAME]}`, 'g');
385 }
386 regex = tagEndRegex;
387 }
388 else if (match[DYNAMIC_TAG_NAME] !== undefined) {
389 if (DEV_MODE) {
390 throw new Error('Bindings in tag names are not supported. Please use static templates instead. ' +
391 'See https://lit.dev/docs/templates/expressions/#static-expressions');
392 }
393 regex = tagEndRegex;
394 }
395 }
396 else if (regex === tagEndRegex) {
397 if (match[ENTIRE_MATCH] === '>') {
398 // End of a tag. If we had started a raw-text element, use that
399 // regex
400 regex = rawTextEndRegex ?? textEndRegex;
401 // We may be ending an unquoted attribute value, so make sure we
402 // clear any pending attrNameEndIndex
403 attrNameEndIndex = -1;
404 }
405 else if (match[ATTRIBUTE_NAME] === undefined) {
406 // Attribute name position
407 attrNameEndIndex = -2;
408 }
409 else {
410 attrNameEndIndex = regex.lastIndex - match[SPACES_AND_EQUALS].length;
411 attrName = match[ATTRIBUTE_NAME];
412 regex =
413 match[QUOTE_CHAR] === undefined
414 ? tagEndRegex
415 : match[QUOTE_CHAR] === '"'
416 ? doubleQuoteAttrEndRegex
417 : singleQuoteAttrEndRegex;
418 }
419 }
420 else if (regex === doubleQuoteAttrEndRegex ||
421 regex === singleQuoteAttrEndRegex) {
422 regex = tagEndRegex;
423 }
424 else if (regex === commentEndRegex || regex === comment2EndRegex) {
425 regex = textEndRegex;
426 }
427 else {
428 // Not one of the five state regexes, so it must be the dynamically
429 // created raw text regex and we're at the close of that element.
430 regex = tagEndRegex;
431 rawTextEndRegex = undefined;
432 }
433 }
434 if (DEV_MODE) {
435 // If we have a attrNameEndIndex, which indicates that we should
436 // rewrite the attribute name, assert that we're in a valid attribute
437 // position - either in a tag, or a quoted attribute value.
438 console.assert(attrNameEndIndex === -1 ||
439 regex === tagEndRegex ||
440 regex === singleQuoteAttrEndRegex ||
441 regex === doubleQuoteAttrEndRegex, 'unexpected parse state B');
442 }
443 // We have four cases:
444 // 1. We're in text position, and not in a raw text element
445 // (regex === textEndRegex): insert a comment marker.
446 // 2. We have a non-negative attrNameEndIndex which means we need to
447 // rewrite the attribute name to add a bound attribute suffix.
448 // 3. We're at the non-first binding in a multi-binding attribute, use a
449 // plain marker.
450 // 4. We're somewhere else inside the tag. If we're in attribute name
451 // position (attrNameEndIndex === -2), add a sequential suffix to
452 // generate a unique attribute name.
453 // Detect a binding next to self-closing tag end and insert a space to
454 // separate the marker from the tag end:
455 const end = regex === tagEndRegex && strings[i + 1].startsWith('/>') ? ' ' : '';
456 html +=
457 regex === textEndRegex
458 ? s + nodeMarker
459 : attrNameEndIndex >= 0
460 ? (attrNames.push(attrName),
461 s.slice(0, attrNameEndIndex) +
462 boundAttributeSuffix +
463 s.slice(attrNameEndIndex)) +
464 marker +
465 end
466 : s + marker + (attrNameEndIndex === -2 ? i : end);
467 }
468 const htmlResult = html + (strings[l] || '<?>') + (type === SVG_RESULT ? '</svg>' : '');
469 // Returned as an array for terseness
470 return [trustFromTemplateString(strings, htmlResult), attrNames];
471};
472class Template {
473 constructor(
474 // This property needs to remain unminified.
475 { strings, ['_$litType$']: type }, options) {
476 this.parts = [];
477 let node;
478 let nodeIndex = 0;
479 let attrNameIndex = 0;
480 const partCount = strings.length - 1;
481 const parts = this.parts;
482 // Create template element
483 const [html, attrNames] = getTemplateHtml(strings, type);
484 this.el = Template.createElement(html, options);
485 walker.currentNode = this.el.content;
486 // Re-parent SVG nodes into template root
487 if (type === SVG_RESULT) {
488 const svgElement = this.el.content.firstChild;
489 svgElement.replaceWith(...svgElement.childNodes);
490 }
491 // Walk the template to find binding markers and create TemplateParts
492 while ((node = walker.nextNode()) !== null && parts.length < partCount) {
493 if (node.nodeType === 1) {
494 if (DEV_MODE) {
495 const tag = node.localName;
496 // Warn if `textarea` includes an expression and throw if `template`
497 // does since these are not supported. We do this by checking
498 // innerHTML for anything that looks like a marker. This catches
499 // cases like bindings in textarea there markers turn into text nodes.
500 if (/^(?:textarea|template)$/i.test(tag) &&
501 node.innerHTML.includes(marker)) {
502 const m = `Expressions are not supported inside \`${tag}\` ` +
503 `elements. See https://lit.dev/msg/expression-in-${tag} for more ` +
504 `information.`;
505 if (tag === 'template') {
506 throw new Error(m);
507 }
508 else
509 issueWarning('', m);
510 }
511 }
512 // TODO (justinfagnani): for attempted dynamic tag names, we don't
513 // increment the bindingIndex, and it'll be off by 1 in the element
514 // and off by two after it.
515 if (node.hasAttributes()) {
516 for (const name of node.getAttributeNames()) {
517 if (name.endsWith(boundAttributeSuffix)) {
518 const realName = attrNames[attrNameIndex++];
519 const value = node.getAttribute(name);
520 const statics = value.split(marker);
521 const m = /([.?@])?(.*)/.exec(realName);
522 parts.push({
523 type: ATTRIBUTE_PART,
524 index: nodeIndex,
525 name: m[2],
526 strings: statics,
527 ctor: m[1] === '.'
528 ? PropertyPart
529 : m[1] === '?'
530 ? BooleanAttributePart
531 : m[1] === '@'
532 ? EventPart
533 : AttributePart,
534 });
535 node.removeAttribute(name);
536 }
537 else if (name.startsWith(marker)) {
538 parts.push({
539 type: ELEMENT_PART,
540 index: nodeIndex,
541 });
542 node.removeAttribute(name);
543 }
544 }
545 }
546 // TODO (justinfagnani): benchmark the regex against testing for each
547 // of the 3 raw text element names.
548 if (rawTextElement.test(node.tagName)) {
549 // For raw text elements we need to split the text content on
550 // markers, create a Text node for each segment, and create
551 // a TemplatePart for each marker.
552 const strings = node.textContent.split(marker);
553 const lastIndex = strings.length - 1;
554 if (lastIndex > 0) {
555 node.textContent = trustedTypes
556 ? trustedTypes.emptyScript
557 : '';
558 // Generate a new text node for each literal section
559 // These nodes are also used as the markers for node parts
560 // We can't use empty text nodes as markers because they're
561 // normalized when cloning in IE (could simplify when
562 // IE is no longer supported)
563 for (let i = 0; i < lastIndex; i++) {
564 node.append(strings[i], createMarker());
565 // Walk past the marker node we just added
566 walker.nextNode();
567 parts.push({ type: CHILD_PART, index: ++nodeIndex });
568 }
569 // Note because this marker is added after the walker's current
570 // node, it will be walked to in the outer loop (and ignored), so
571 // we don't need to adjust nodeIndex here
572 node.append(strings[lastIndex], createMarker());
573 }
574 }
575 }
576 else if (node.nodeType === 8) {
577 const data = node.data;
578 if (data === markerMatch) {
579 parts.push({ type: CHILD_PART, index: nodeIndex });
580 }
581 else {
582 let i = -1;
583 while ((i = node.data.indexOf(marker, i + 1)) !== -1) {
584 // Comment node has a binding marker inside, make an inactive part
585 // The binding won't work, but subsequent bindings will
586 parts.push({ type: COMMENT_PART, index: nodeIndex });
587 // Move to the end of the match
588 i += marker.length - 1;
589 }
590 }
591 }
592 nodeIndex++;
593 }
594 if (DEV_MODE) {
595 // If there was a duplicate attribute on a tag, then when the tag is
596 // parsed into an element the attribute gets de-duplicated. We can detect
597 // this mismatch if we haven't precisely consumed every attribute name
598 // when preparing the template. This works because `attrNames` is built
599 // from the template string and `attrNameIndex` comes from processing the
600 // resulting DOM.
601 if (attrNames.length !== attrNameIndex) {
602 throw new Error(`Detected duplicate attribute bindings. This occurs if your template ` +
603 `has duplicate attributes on an element tag. For example ` +
604 `"<input ?disabled=\${true} ?disabled=\${false}>" contains a ` +
605 `duplicate "disabled" attribute. The error was detected in ` +
606 `the following template: \n` +
607 '`' +
608 strings.join('${...}') +
609 '`');
610 }
611 }
612 // We could set walker.currentNode to another node here to prevent a memory
613 // leak, but every time we prepare a template, we immediately render it
614 // and re-use the walker in new TemplateInstance._clone().
615 debugLogEvent &&
616 debugLogEvent({
617 kind: 'template prep',
618 template: this,
619 clonableTemplate: this.el,
620 parts: this.parts,
621 strings,
622 });
623 }
624 // Overridden via `litHtmlPolyfillSupport` to provide platform support.
625 /** @nocollapse */
626 static createElement(html, _options) {
627 const el = d.createElement('template');
628 el.innerHTML = html;
629 return el;
630 }
631}
632function resolveDirective(part, value, parent = part, attributeIndex) {
633 // Bail early if the value is explicitly noChange. Note, this means any
634 // nested directive is still attached and is not run.
635 if (value === noChange) {
636 return value;
637 }
638 let currentDirective = attributeIndex !== undefined
639 ? parent.__directives?.[attributeIndex]
640 : parent.__directive;
641 const nextDirectiveConstructor = isPrimitive(value)
642 ? undefined
643 : // This property needs to remain unminified.
644 value['_$litDirective$'];
645 if (currentDirective?.constructor !== nextDirectiveConstructor) {
646 // This property needs to remain unminified.
647 currentDirective?.['_$notifyDirectiveConnectionChanged']?.(false);
648 if (nextDirectiveConstructor === undefined) {
649 currentDirective = undefined;
650 }
651 else {
652 currentDirective = new nextDirectiveConstructor(part);
653 currentDirective._$initialize(part, parent, attributeIndex);
654 }
655 if (attributeIndex !== undefined) {
656 (parent.__directives ??= [])[attributeIndex] =
657 currentDirective;
658 }
659 else {
660 parent.__directive = currentDirective;
661 }
662 }
663 if (currentDirective !== undefined) {
664 value = resolveDirective(part, currentDirective._$resolve(part, value.values), currentDirective, attributeIndex);
665 }
666 return value;
667}
668/**
669 * An updateable instance of a Template. Holds references to the Parts used to
670 * update the template instance.
671 */
672class TemplateInstance {
673 constructor(template, parent) {
674 this._$parts = [];
675 /** @internal */
676 this._$disconnectableChildren = undefined;
677 this._$template = template;
678 this._$parent = parent;
679 }
680 // Called by ChildPart parentNode getter
681 get parentNode() {
682 return this._$parent.parentNode;
683 }
684 // See comment in Disconnectable interface for why this is a getter
685 get _$isConnected() {
686 return this._$parent._$isConnected;
687 }
688 // This method is separate from the constructor because we need to return a
689 // DocumentFragment and we don't want to hold onto it with an instance field.
690 _clone(options) {
691 const { el: { content }, parts: parts, } = this._$template;
692 const fragment = (options?.creationScope ?? d).importNode(content, true);
693 walker.currentNode = fragment;
694 let node = walker.nextNode();
695 let nodeIndex = 0;
696 let partIndex = 0;
697 let templatePart = parts[0];
698 while (templatePart !== undefined) {
699 if (nodeIndex === templatePart.index) {
700 let part;
701 if (templatePart.type === CHILD_PART) {
702 part = new ChildPart(node, node.nextSibling, this, options);
703 }
704 else if (templatePart.type === ATTRIBUTE_PART) {
705 part = new templatePart.ctor(node, templatePart.name, templatePart.strings, this, options);
706 }
707 else if (templatePart.type === ELEMENT_PART) {
708 part = new ElementPart(node, this, options);
709 }
710 this._$parts.push(part);
711 templatePart = parts[++partIndex];
712 }
713 if (nodeIndex !== templatePart?.index) {
714 node = walker.nextNode();
715 nodeIndex++;
716 }
717 }
718 // We need to set the currentNode away from the cloned tree so that we
719 // don't hold onto the tree even if the tree is detached and should be
720 // freed.
721 walker.currentNode = d;
722 return fragment;
723 }
724 _update(values) {
725 let i = 0;
726 for (const part of this._$parts) {
727 if (part !== undefined) {
728 debugLogEvent &&
729 debugLogEvent({
730 kind: 'set part',
731 part,
732 value: values[i],
733 valueIndex: i,
734 values,
735 templateInstance: this,
736 });
737 if (part.strings !== undefined) {
738 part._$setValue(values, part, i);
739 // The number of values the part consumes is part.strings.length - 1
740 // since values are in between template spans. We increment i by 1
741 // later in the loop, so increment it by part.strings.length - 2 here
742 i += part.strings.length - 2;
743 }
744 else {
745 part._$setValue(values[i]);
746 }
747 }
748 i++;
749 }
750 }
751}
752class ChildPart {
753 // See comment in Disconnectable interface for why this is a getter
754 get _$isConnected() {
755 // ChildParts that are not at the root should always be created with a
756 // parent; only RootChildNode's won't, so they return the local isConnected
757 // state
758 return this._$parent?._$isConnected ?? this.__isConnected;
759 }
760 constructor(startNode, endNode, parent, options) {
761 this.type = CHILD_PART;
762 this._$committedValue = nothing;
763 // The following fields will be patched onto ChildParts when required by
764 // AsyncDirective
765 /** @internal */
766 this._$disconnectableChildren = undefined;
767 this._$startNode = startNode;
768 this._$endNode = endNode;
769 this._$parent = parent;
770 this.options = options;
771 // Note __isConnected is only ever accessed on RootParts (i.e. when there is
772 // no _$parent); the value on a non-root-part is "don't care", but checking
773 // for parent would be more code
774 this.__isConnected = options?.isConnected ?? true;
775 if (ENABLE_EXTRA_SECURITY_HOOKS) {
776 // Explicitly initialize for consistent class shape.
777 this._textSanitizer = undefined;
778 }
779 }
780 /**
781 * The parent node into which the part renders its content.
782 *
783 * A ChildPart's content consists of a range of adjacent child nodes of
784 * `.parentNode`, possibly bordered by 'marker nodes' (`.startNode` and
785 * `.endNode`).
786 *
787 * - If both `.startNode` and `.endNode` are non-null, then the part's content
788 * consists of all siblings between `.startNode` and `.endNode`, exclusively.
789 *
790 * - If `.startNode` is non-null but `.endNode` is null, then the part's
791 * content consists of all siblings following `.startNode`, up to and
792 * including the last child of `.parentNode`. If `.endNode` is non-null, then
793 * `.startNode` will always be non-null.
794 *
795 * - If both `.endNode` and `.startNode` are null, then the part's content
796 * consists of all child nodes of `.parentNode`.
797 */
798 get parentNode() {
799 let parentNode = wrap(this._$startNode).parentNode;
800 const parent = this._$parent;
801 if (parent !== undefined &&
802 parentNode?.nodeType === 11 /* Node.DOCUMENT_FRAGMENT */) {
803 // If the parentNode is a DocumentFragment, it may be because the DOM is
804 // still in the cloned fragment during initial render; if so, get the real
805 // parentNode the part will be committed into by asking the parent.
806 parentNode = parent.parentNode;
807 }
808 return parentNode;
809 }
810 /**
811 * The part's leading marker node, if any. See `.parentNode` for more
812 * information.
813 */
814 get startNode() {
815 return this._$startNode;
816 }
817 /**
818 * The part's trailing marker node, if any. See `.parentNode` for more
819 * information.
820 */
821 get endNode() {
822 return this._$endNode;
823 }
824 _$setValue(value, directiveParent = this) {
825 if (DEV_MODE && this.parentNode === null) {
826 throw new Error(`This \`ChildPart\` has no \`parentNode\` and therefore cannot accept a value. This likely means the element containing the part was manipulated in an unsupported way outside of Lit's control such that the part's marker nodes were ejected from DOM. For example, setting the element's \`innerHTML\` or \`textContent\` can do this.`);
827 }
828 value = resolveDirective(this, value, directiveParent);
829 if (isPrimitive(value)) {
830 // Non-rendering child values. It's important that these do not render
831 // empty text nodes to avoid issues with preventing default <slot>
832 // fallback content.
833 if (value === nothing || value == null || value === '') {
834 if (this._$committedValue !== nothing) {
835 debugLogEvent &&
836 debugLogEvent({
837 kind: 'commit nothing to child',
838 start: this._$startNode,
839 end: this._$endNode,
840 parent: this._$parent,
841 options: this.options,
842 });
843 this._$clear();
844 }
845 this._$committedValue = nothing;
846 }
847 else if (value !== this._$committedValue && value !== noChange) {
848 this._commitText(value);
849 }
850 // This property needs to remain unminified.
851 }
852 else if (value['_$litType$'] !== undefined) {
853 this._commitTemplateResult(value);
854 }
855 else if (value.nodeType !== undefined) {
856 if (DEV_MODE && this.options?.host === value) {
857 this._commitText(`[probable mistake: rendered a template's host in itself ` +
858 `(commonly caused by writing \${this} in a template]`);
859 console.warn(`Attempted to render the template host`, value, `inside itself. This is almost always a mistake, and in dev mode `, `we render some warning text. In production however, we'll `, `render it, which will usually result in an error, and sometimes `, `in the element disappearing from the DOM.`);
860 return;
861 }
862 this._commitNode(value);
863 }
864 else if (isIterable(value)) {
865 this._commitIterable(value);
866 }
867 else {
868 // Fallback, will render the string representation
869 this._commitText(value);
870 }
871 }
872 _insert(node) {
873 return wrap(wrap(this._$startNode).parentNode).insertBefore(node, this._$endNode);
874 }
875 _commitNode(value) {
876 if (this._$committedValue !== value) {
877 this._$clear();
878 if (ENABLE_EXTRA_SECURITY_HOOKS &&
879 sanitizerFactoryInternal !== noopSanitizer) {
880 const parentNodeName = this._$startNode.parentNode?.nodeName;
881 if (parentNodeName === 'STYLE' || parentNodeName === 'SCRIPT') {
882 let message = 'Forbidden';
883 if (DEV_MODE) {
884 if (parentNodeName === 'STYLE') {
885 message =
886 `Lit does not support binding inside style nodes. ` +
887 `This is a security risk, as style injection attacks can ` +
888 `exfiltrate data and spoof UIs. ` +
889 `Consider instead using css\`...\` literals ` +
890 `to compose styles, and make do dynamic styling with ` +
891 `css custom properties, ::parts, <slot>s, ` +
892 `and by mutating the DOM rather than stylesheets.`;
893 }
894 else {
895 message =
896 `Lit does not support binding inside script nodes. ` +
897 `This is a security risk, as it could allow arbitrary ` +
898 `code execution.`;
899 }
900 }
901 throw new Error(message);
902 }
903 }
904 debugLogEvent &&
905 debugLogEvent({
906 kind: 'commit node',
907 start: this._$startNode,
908 parent: this._$parent,
909 value: value,
910 options: this.options,
911 });
912 this._$committedValue = this._insert(value);
913 }
914 }
915 _commitText(value) {
916 // If the committed value is a primitive it means we called _commitText on
917 // the previous render, and we know that this._$startNode.nextSibling is a
918 // Text node. We can now just replace the text content (.data) of the node.
919 if (this._$committedValue !== nothing &&
920 isPrimitive(this._$committedValue)) {
921 const node = wrap(this._$startNode).nextSibling;
922 if (ENABLE_EXTRA_SECURITY_HOOKS) {
923 if (this._textSanitizer === undefined) {
924 this._textSanitizer = createSanitizer(node, 'data', 'property');
925 }
926 value = this._textSanitizer(value);
927 }
928 debugLogEvent &&
929 debugLogEvent({
930 kind: 'commit text',
931 node,
932 value,
933 options: this.options,
934 });
935 node.data = value;
936 }
937 else {
938 if (ENABLE_EXTRA_SECURITY_HOOKS) {
939 const textNode = d.createTextNode('');
940 this._commitNode(textNode);
941 // When setting text content, for security purposes it matters a lot
942 // what the parent is. For example, <style> and <script> need to be
943 // handled with care, while <span> does not. So first we need to put a
944 // text node into the document, then we can sanitize its content.
945 if (this._textSanitizer === undefined) {
946 this._textSanitizer = createSanitizer(textNode, 'data', 'property');
947 }
948 value = this._textSanitizer(value);
949 debugLogEvent &&
950 debugLogEvent({
951 kind: 'commit text',
952 node: textNode,
953 value,
954 options: this.options,
955 });
956 textNode.data = value;
957 }
958 else {
959 this._commitNode(d.createTextNode(value));
960 debugLogEvent &&
961 debugLogEvent({
962 kind: 'commit text',
963 node: wrap(this._$startNode).nextSibling,
964 value,
965 options: this.options,
966 });
967 }
968 }
969 this._$committedValue = value;
970 }
971 _commitTemplateResult(result) {
972 // This property needs to remain unminified.
973 const { values, ['_$litType$']: type } = result;
974 // If $litType$ is a number, result is a plain TemplateResult and we get
975 // the template from the template cache. If not, result is a
976 // CompiledTemplateResult and _$litType$ is a CompiledTemplate and we need
977 // to create the <template> element the first time we see it.
978 const template = typeof type === 'number'
979 ? this._$getTemplate(result)
980 : (type.el === undefined &&
981 (type.el = Template.createElement(trustFromTemplateString(type.h, type.h[0]), this.options)),
982 type);
983 if (this._$committedValue?._$template === template) {
984 debugLogEvent &&
985 debugLogEvent({
986 kind: 'template updating',
987 template,
988 instance: this._$committedValue,
989 parts: this._$committedValue._$parts,
990 options: this.options,
991 values,
992 });
993 this._$committedValue._update(values);
994 }
995 else {
996 const instance = new TemplateInstance(template, this);
997 const fragment = instance._clone(this.options);
998 debugLogEvent &&
999 debugLogEvent({
1000 kind: 'template instantiated',
1001 template,
1002 instance,
1003 parts: instance._$parts,
1004 options: this.options,
1005 fragment,
1006 values,
1007 });
1008 instance._update(values);
1009 debugLogEvent &&
1010 debugLogEvent({
1011 kind: 'template instantiated and updated',
1012 template,
1013 instance,
1014 parts: instance._$parts,
1015 options: this.options,
1016 fragment,
1017 values,
1018 });
1019 this._commitNode(fragment);
1020 this._$committedValue = instance;
1021 }
1022 }
1023 // Overridden via `litHtmlPolyfillSupport` to provide platform support.
1024 /** @internal */
1025 _$getTemplate(result) {
1026 let template = templateCache.get(result.strings);
1027 if (template === undefined) {
1028 templateCache.set(result.strings, (template = new Template(result)));
1029 }
1030 return template;
1031 }
1032 _commitIterable(value) {
1033 // For an Iterable, we create a new InstancePart per item, then set its
1034 // value to the item. This is a little bit of overhead for every item in
1035 // an Iterable, but it lets us recurse easily and efficiently update Arrays
1036 // of TemplateResults that will be commonly returned from expressions like:
1037 // array.map((i) => html`${i}`), by reusing existing TemplateInstances.
1038 // If value is an array, then the previous render was of an
1039 // iterable and value will contain the ChildParts from the previous
1040 // render. If value is not an array, clear this part and make a new
1041 // array for ChildParts.
1042 if (!isArray(this._$committedValue)) {
1043 this._$committedValue = [];
1044 this._$clear();
1045 }
1046 // Lets us keep track of how many items we stamped so we can clear leftover
1047 // items from a previous render
1048 const itemParts = this._$committedValue;
1049 let partIndex = 0;
1050 let itemPart;
1051 for (const item of value) {
1052 if (partIndex === itemParts.length) {
1053 // If no existing part, create a new one
1054 // TODO (justinfagnani): test perf impact of always creating two parts
1055 // instead of sharing parts between nodes
1056 // https://github.com/lit/lit/issues/1266
1057 itemParts.push((itemPart = new ChildPart(this._insert(createMarker()), this._insert(createMarker()), this, this.options)));
1058 }
1059 else {
1060 // Reuse an existing part
1061 itemPart = itemParts[partIndex];
1062 }
1063 itemPart._$setValue(item);
1064 partIndex++;
1065 }
1066 if (partIndex < itemParts.length) {
1067 // itemParts always have end nodes
1068 this._$clear(itemPart && wrap(itemPart._$endNode).nextSibling, partIndex);
1069 // Truncate the parts array so _value reflects the current state
1070 itemParts.length = partIndex;
1071 }
1072 }
1073 /**
1074 * Removes the nodes contained within this Part from the DOM.
1075 *
1076 * @param start Start node to clear from, for clearing a subset of the part's
1077 * DOM (used when truncating iterables)
1078 * @param from When `start` is specified, the index within the iterable from
1079 * which ChildParts are being removed, used for disconnecting directives in
1080 * those Parts.
1081 *
1082 * @internal
1083 */
1084 _$clear(start = wrap(this._$startNode).nextSibling, from) {
1085 this._$notifyConnectionChanged?.(false, true, from);
1086 while (start && start !== this._$endNode) {
1087 const n = wrap(start).nextSibling;
1088 wrap(start).remove();
1089 start = n;
1090 }
1091 }
1092 /**
1093 * Implementation of RootPart's `isConnected`. Note that this metod
1094 * should only be called on `RootPart`s (the `ChildPart` returned from a
1095 * top-level `render()` call). It has no effect on non-root ChildParts.
1096 * @param isConnected Whether to set
1097 * @internal
1098 */
1099 setConnected(isConnected) {
1100 if (this._$parent === undefined) {
1101 this.__isConnected = isConnected;
1102 this._$notifyConnectionChanged?.(isConnected);
1103 }
1104 else if (DEV_MODE) {
1105 throw new Error('part.setConnected() may only be called on a ' +
1106 'RootPart returned from render().');
1107 }
1108 }
1109}
1110class AttributePart {
1111 get tagName() {
1112 return this.element.tagName;
1113 }
1114 // See comment in Disconnectable interface for why this is a getter
1115 get _$isConnected() {
1116 return this._$parent._$isConnected;
1117 }
1118 constructor(element, name, strings, parent, options) {
1119 this.type = ATTRIBUTE_PART;
1120 /** @internal */
1121 this._$committedValue = nothing;
1122 /** @internal */
1123 this._$disconnectableChildren = undefined;
1124 this.element = element;
1125 this.name = name;
1126 this._$parent = parent;
1127 this.options = options;
1128 if (strings.length > 2 || strings[0] !== '' || strings[1] !== '') {
1129 this._$committedValue = new Array(strings.length - 1).fill(new String());
1130 this.strings = strings;
1131 }
1132 else {
1133 this._$committedValue = nothing;
1134 }
1135 if (ENABLE_EXTRA_SECURITY_HOOKS) {
1136 this._sanitizer = undefined;
1137 }
1138 }
1139 /**
1140 * Sets the value of this part by resolving the value from possibly multiple
1141 * values and static strings and committing it to the DOM.
1142 * If this part is single-valued, `this._strings` will be undefined, and the
1143 * method will be called with a single value argument. If this part is
1144 * multi-value, `this._strings` will be defined, and the method is called
1145 * with the value array of the part's owning TemplateInstance, and an offset
1146 * into the value array from which the values should be read.
1147 * This method is overloaded this way to eliminate short-lived array slices
1148 * of the template instance values, and allow a fast-path for single-valued
1149 * parts.
1150 *
1151 * @param value The part value, or an array of values for multi-valued parts
1152 * @param valueIndex the index to start reading values from. `undefined` for
1153 * single-valued parts
1154 * @param noCommit causes the part to not commit its value to the DOM. Used
1155 * in hydration to prime attribute parts with their first-rendered value,
1156 * but not set the attribute, and in SSR to no-op the DOM operation and
1157 * capture the value for serialization.
1158 *
1159 * @internal
1160 */
1161 _$setValue(value, directiveParent = this, valueIndex, noCommit) {
1162 const strings = this.strings;
1163 // Whether any of the values has changed, for dirty-checking
1164 let change = false;
1165 if (strings === undefined) {
1166 // Single-value binding case
1167 value = resolveDirective(this, value, directiveParent, 0);
1168 change =
1169 !isPrimitive(value) ||
1170 (value !== this._$committedValue && value !== noChange);
1171 if (change) {
1172 this._$committedValue = value;
1173 }
1174 }
1175 else {
1176 // Interpolation case
1177 const values = value;
1178 value = strings[0];
1179 let i, v;
1180 for (i = 0; i < strings.length - 1; i++) {
1181 v = resolveDirective(this, values[valueIndex + i], directiveParent, i);
1182 if (v === noChange) {
1183 // If the user-provided value is `noChange`, use the previous value
1184 v = this._$committedValue[i];
1185 }
1186 change ||=
1187 !isPrimitive(v) || v !== this._$committedValue[i];
1188 if (v === nothing) {
1189 value = nothing;
1190 }
1191 else if (value !== nothing) {
1192 value += (v ?? '') + strings[i + 1];
1193 }
1194 // We always record each value, even if one is `nothing`, for future
1195 // change detection.
1196 this._$committedValue[i] = v;
1197 }
1198 }
1199 if (change && !noCommit) {
1200 this._commitValue(value);
1201 }
1202 }
1203 /** @internal */
1204 _commitValue(value) {
1205 if (value === nothing) {
1206 wrap(this.element).removeAttribute(this.name);
1207 }
1208 else {
1209 if (ENABLE_EXTRA_SECURITY_HOOKS) {
1210 if (this._sanitizer === undefined) {
1211 this._sanitizer = sanitizerFactoryInternal(this.element, this.name, 'attribute');
1212 }
1213 value = this._sanitizer(value ?? '');
1214 }
1215 debugLogEvent &&
1216 debugLogEvent({
1217 kind: 'commit attribute',
1218 element: this.element,
1219 name: this.name,
1220 value,
1221 options: this.options,
1222 });
1223 wrap(this.element).setAttribute(this.name, (value ?? ''));
1224 }
1225 }
1226}
1227class PropertyPart extends AttributePart {
1228 constructor() {
1229 super(...arguments);
1230 this.type = PROPERTY_PART;
1231 }
1232 /** @internal */
1233 _commitValue(value) {
1234 if (ENABLE_EXTRA_SECURITY_HOOKS) {
1235 if (this._sanitizer === undefined) {
1236 this._sanitizer = sanitizerFactoryInternal(this.element, this.name, 'property');
1237 }
1238 value = this._sanitizer(value);
1239 }
1240 debugLogEvent &&
1241 debugLogEvent({
1242 kind: 'commit property',
1243 element: this.element,
1244 name: this.name,
1245 value,
1246 options: this.options,
1247 });
1248 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1249 this.element[this.name] = value === nothing ? undefined : value;
1250 }
1251}
1252class BooleanAttributePart extends AttributePart {
1253 constructor() {
1254 super(...arguments);
1255 this.type = BOOLEAN_ATTRIBUTE_PART;
1256 }
1257 /** @internal */
1258 _commitValue(value) {
1259 debugLogEvent &&
1260 debugLogEvent({
1261 kind: 'commit boolean attribute',
1262 element: this.element,
1263 name: this.name,
1264 value: !!(value && value !== nothing),
1265 options: this.options,
1266 });
1267 wrap(this.element).toggleAttribute(this.name, !!value && value !== nothing);
1268 }
1269}
1270class EventPart extends AttributePart {
1271 constructor(element, name, strings, parent, options) {
1272 super(element, name, strings, parent, options);
1273 this.type = EVENT_PART;
1274 if (DEV_MODE && this.strings !== undefined) {
1275 throw new Error(`A \`<${element.localName}>\` has a \`@${name}=...\` listener with ` +
1276 'invalid content. Event listeners in templates must have exactly ' +
1277 'one expression and no surrounding text.');
1278 }
1279 }
1280 // EventPart does not use the base _$setValue/_resolveValue implementation
1281 // since the dirty checking is more complex
1282 /** @internal */
1283 _$setValue(newListener, directiveParent = this) {
1284 newListener =
1285 resolveDirective(this, newListener, directiveParent, 0) ?? nothing;
1286 if (newListener === noChange) {
1287 return;
1288 }
1289 const oldListener = this._$committedValue;
1290 // If the new value is nothing or any options change we have to remove the
1291 // part as a listener.
1292 const shouldRemoveListener = (newListener === nothing && oldListener !== nothing) ||
1293 newListener.capture !==
1294 oldListener.capture ||
1295 newListener.once !==
1296 oldListener.once ||
1297 newListener.passive !==
1298 oldListener.passive;
1299 // If the new value is not nothing and we removed the listener, we have
1300 // to add the part as a listener.
1301 const shouldAddListener = newListener !== nothing &&
1302 (oldListener === nothing || shouldRemoveListener);
1303 debugLogEvent &&
1304 debugLogEvent({
1305 kind: 'commit event listener',
1306 element: this.element,
1307 name: this.name,
1308 value: newListener,
1309 options: this.options,
1310 removeListener: shouldRemoveListener,
1311 addListener: shouldAddListener,
1312 oldListener,
1313 });
1314 if (shouldRemoveListener) {
1315 this.element.removeEventListener(this.name, this, oldListener);
1316 }
1317 if (shouldAddListener) {
1318 // Beware: IE11 and Chrome 41 don't like using the listener as the
1319 // options object. Figure out how to deal w/ this in IE11 - maybe
1320 // patch addEventListener?
1321 this.element.addEventListener(this.name, this, newListener);
1322 }
1323 this._$committedValue = newListener;
1324 }
1325 handleEvent(event) {
1326 if (typeof this._$committedValue === 'function') {
1327 this._$committedValue.call(this.options?.host ?? this.element, event);
1328 }
1329 else {
1330 this._$committedValue.handleEvent(event);
1331 }
1332 }
1333}
1334class ElementPart {
1335 constructor(element, parent, options) {
1336 this.element = element;
1337 this.type = ELEMENT_PART;
1338 /** @internal */
1339 this._$disconnectableChildren = undefined;
1340 this._$parent = parent;
1341 this.options = options;
1342 }
1343 // See comment in Disconnectable interface for why this is a getter
1344 get _$isConnected() {
1345 return this._$parent._$isConnected;
1346 }
1347 _$setValue(value) {
1348 debugLogEvent &&
1349 debugLogEvent({
1350 kind: 'commit to element binding',
1351 element: this.element,
1352 value,
1353 options: this.options,
1354 });
1355 resolveDirective(this, value);
1356 }
1357}
1358/**
1359 * END USERS SHOULD NOT RELY ON THIS OBJECT.
1360 *
1361 * Private exports for use by other Lit packages, not intended for use by
1362 * external users.
1363 *
1364 * We currently do not make a mangled rollup build of the lit-ssr code. In order
1365 * to keep a number of (otherwise private) top-level exports mangled in the
1366 * client side code, we export a _$LH object containing those members (or
1367 * helper methods for accessing private fields of those members), and then
1368 * re-export them for use in lit-ssr. This keeps lit-ssr agnostic to whether the
1369 * client-side code is being used in `dev` mode or `prod` mode.
1370 *
1371 * This has a unique name, to disambiguate it from private exports in
1372 * lit-element, which re-exports all of lit-html.
1373 *
1374 * @private
1375 */
1376export const _$LH = {
1377 // Used in lit-ssr
1378 _boundAttributeSuffix: boundAttributeSuffix,
1379 _marker: marker,
1380 _markerMatch: markerMatch,
1381 _HTML_RESULT: HTML_RESULT,
1382 _getTemplateHtml: getTemplateHtml,
1383 // Used in tests and private-ssr-support
1384 _TemplateInstance: TemplateInstance,
1385 _isIterable: isIterable,
1386 _resolveDirective: resolveDirective,
1387 _ChildPart: ChildPart,
1388 _AttributePart: AttributePart,
1389 _BooleanAttributePart: BooleanAttributePart,
1390 _EventPart: EventPart,
1391 _PropertyPart: PropertyPart,
1392 _ElementPart: ElementPart,
1393};
1394// Apply polyfills if available
1395const polyfillSupport = DEV_MODE
1396 ? global.litHtmlPolyfillSupportDevMode
1397 : global.litHtmlPolyfillSupport;
1398polyfillSupport?.(Template, ChildPart);
1399// IMPORTANT: do not change the property name or the assignment expression.
1400// This line will be used in regexes to search for lit-html usage.
1401(global.litHtmlVersions ??= []).push('3.1.3');
1402if (DEV_MODE && global.litHtmlVersions.length > 1) {
1403 issueWarning('multiple-versions', `Multiple versions of Lit loaded. ` +
1404 `Loading multiple versions is not recommended.`);
1405}
1406/**
1407 * Renders a value, usually a lit-html TemplateResult, to the container.
1408 *
1409 * This example renders the text "Hello, Zoe!" inside a paragraph tag, appending
1410 * it to the container `document.body`.
1411 *
1412 * ```js
1413 * import {html, render} from 'lit';
1414 *
1415 * const name = "Zoe";
1416 * render(html`<p>Hello, ${name}!</p>`, document.body);
1417 * ```
1418 *
1419 * @param value Any [renderable
1420 * value](https://lit.dev/docs/templates/expressions/#child-expressions),
1421 * typically a {@linkcode TemplateResult} created by evaluating a template tag
1422 * like {@linkcode html} or {@linkcode svg}.
1423 * @param container A DOM container to render to. The first render will append
1424 * the rendered value to the container, and subsequent renders will
1425 * efficiently update the rendered value if the same result type was
1426 * previously rendered there.
1427 * @param options See {@linkcode RenderOptions} for options documentation.
1428 * @see
1429 * {@link https://lit.dev/docs/libraries/standalone-templates/#rendering-lit-html-templates| Rendering Lit HTML Templates}
1430 */
1431export const render = (value, container, options) => {
1432 if (DEV_MODE && container == null) {
1433 // Give a clearer error message than
1434 // Uncaught TypeError: Cannot read properties of null (reading
1435 // '_$litPart$')
1436 // which reads like an internal Lit error.
1437 throw new TypeError(`The container to render into may not be ${container}`);
1438 }
1439 const renderId = DEV_MODE ? debugLogRenderId++ : 0;
1440 const partOwnerNode = options?.renderBefore ?? container;
1441 // This property needs to remain unminified.
1442 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1443 let part = partOwnerNode['_$litPart$'];
1444 debugLogEvent &&
1445 debugLogEvent({
1446 kind: 'begin render',
1447 id: renderId,
1448 value,
1449 container,
1450 options,
1451 part,
1452 });
1453 if (part === undefined) {
1454 const endNode = options?.renderBefore ?? null;
1455 // This property needs to remain unminified.
1456 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1457 partOwnerNode['_$litPart$'] = part = new ChildPart(container.insertBefore(createMarker(), endNode), endNode, undefined, options ?? {});
1458 }
1459 part._$setValue(value);
1460 debugLogEvent &&
1461 debugLogEvent({
1462 kind: 'end render',
1463 id: renderId,
1464 value,
1465 container,
1466 options,
1467 part,
1468 });
1469 return part;
1470};
1471if (ENABLE_EXTRA_SECURITY_HOOKS) {
1472 render.setSanitizer = setSanitizer;
1473 render.createSanitizer = createSanitizer;
1474 if (DEV_MODE) {
1475 render._testOnlyClearSanitizerFactoryDoNotCallOrElse =
1476 _testOnlyClearSanitizerFactoryDoNotCallOrElse;
1477 }
1478}
1479//# sourceMappingURL=lit-html.js.map
\No newline at end of file