element-vir
Version:
Heroic. Reactive. Declarative. Type safe. Web components without compromise.
124 lines (123 loc) • 4.27 kB
JavaScript
import { check } from '@augment-vir/assert';
import { filterMap, getOrSet } from '@augment-vir/common';
import { canHoldProperties, hasTagName, isMinimalDefinitionWithInputs, } from './minimal-element-definition.js';
import { tagNameKeys } from './vir-html/tag-name-keys.js';
/**
* DOM nodes have a `tagName` but never contribute to a template transform, so keying the cache on
* them would create an unbounded entry per node instance.
*/
function isDomNode(value) {
return canHoldProperties(value) && check.isNumber(value.nodeType);
}
function extractElementKeys(values) {
return filterMap(values, (value) => {
if (isMinimalDefinitionWithInputs(value)) {
return value.definition;
}
else if (!hasTagName(value) || isDomNode(value)) {
return undefined;
}
else if (value.tagInterpolationKey) {
return value.tagInterpolationKey;
}
else if (check.isFunction(value)) {
return value;
}
else {
/**
* Any other object that merely carries a tag name gets a key shared by tag name so
* that the cache is not defeated by a new object on every render.
*/
return getOrSet(tagNameKeys, value.tagName, () => {
return {
tagName: value.tagName,
};
});
}
}, check.isTruthy);
}
/**
* The transformed templates are written to a map so that we can preserve reference equality between
* calls. Without maintaining reference equality between html`` calls, lit-element reconstructs all
* of its children on every render.
*
* This is a WeakMap because we only care about the transformed array value as long as the original
* template array key exists.
*/
const transformedTemplateStrings = new WeakMap();
export function getAlreadyMappedTemplate(templateStringsKey, values) {
/**
* This runs on every render, so the maps are walked directly instead of through the wrapper
* objects that {@link setNestedValues} needs for its failure reasons.
*/
return extractElementKeys(values).reduce((parent, elementKey) => {
return parent?.nested?.get(elementKey);
}, transformedTemplateStrings.get(templateStringsKey))?.template;
}
export function setMappedTemplate(templateStringsKey, values, valueToSet) {
const elementKeys = extractElementKeys(values);
return setNestedValues({
map: transformedTemplateStrings,
keys: [
templateStringsKey,
...elementKeys,
],
valueToSet,
});
}
function getCurrentKeyAndValue(map, keys, index) {
const currentKey = keys[index];
if (currentKey == undefined) {
return {
currentKey: undefined,
currentTemplateAndNested: undefined,
reason: `key at index ${index} not found`,
};
}
const currentTemplateAndNested = map.get(currentKey);
if (!currentTemplateAndNested) {
return {
currentKey,
currentTemplateAndNested: undefined,
reason: `key at index ${index} was not in the map`,
};
}
return {
currentKey,
currentTemplateAndNested,
reason: 'key and value exists',
};
}
function setNestedValues({ map, keys, valueToSet, index = 0, }) {
const { currentTemplateAndNested, currentKey, reason } = getCurrentKeyAndValue(map, keys, index);
if (!currentKey) {
return {
result: false,
reason,
};
}
const nestedAndTemplate = currentTemplateAndNested ?? {
nested: undefined,
template: undefined,
};
if (!currentTemplateAndNested) {
map.set(currentKey, nestedAndTemplate);
}
if (index === keys.length - 1) {
nestedAndTemplate.template = valueToSet;
return {
result: true,
reason: 'set value at end of keys array',
};
}
const nestedWeakMap = nestedAndTemplate.nested ?? new WeakMap();
if (!nestedAndTemplate.nested) {
nestedAndTemplate.nested = nestedWeakMap;
}
return setNestedValues({
map: nestedWeakMap,
keys,
valueToSet,
index: index + 1,
});
}