element-vir
Version:
Heroic. Reactive. Declarative. Type safe. Web components without compromise.
97 lines (96 loc) • 4.29 kB
JavaScript
/* eslint-disable @typescript-eslint/no-deprecated */
import { check } from '@augment-vir/assert';
import { collapseWhiteSpace, getOrSet, safeMatch } from '@augment-vir/common';
import { assign } from '../../declarative-element/directives/assign.directive.js';
import { declarativeElementRequired } from '../../require-declarative-element.js';
import { hasTagName, isMinimalDefinitionWithInputs, } from '../minimal-element-definition.js';
import { transformTemplate } from '../transform-template.js';
import { tagNameKeys } from './tag-name-keys.js';
export function mapHtmlValues(inputTemplateStrings, inputValues) {
return inputValues.map((currentValue, currentValueIndex) => {
const lastString = inputTemplateStrings[currentValueIndex];
const nextString = inputTemplateStrings[currentValueIndex + 1];
if (lastString && nextString) {
const { shouldHaveTagNameHere } = classifyValue(lastString, nextString);
if (shouldHaveTagNameHere && check.isString(currentValue)) {
const replacement = {
tagName: currentValue,
tagInterpolationKey: getOrSet(tagNameKeys, currentValue, () => {
return { tagName: currentValue };
}),
};
return replacement;
}
}
return currentValue;
});
}
function classifyValue(lastNewString, currentTemplateString) {
const isOpeningTag = lastNewString.trim().endsWith('<') && !!currentTemplateString.match(/^[\s>]/);
const isClosingTag = lastNewString.trim().endsWith('</') && currentTemplateString.trim().startsWith('>');
const shouldHaveTagNameHere = isOpeningTag || isClosingTag;
return {
isOpeningTag,
shouldHaveTagNameHere,
};
}
function transformHtml(...[lastNewString, currentTemplateString, rawCurrentValue,]) {
const currentValue = isMinimalDefinitionWithInputs(rawCurrentValue)
? rawCurrentValue.definition
: rawCurrentValue;
const { isOpeningTag, shouldHaveTagNameHere } = classifyValue(lastNewString, currentTemplateString);
const isTagNameWrapper = hasTagName(currentValue);
if (isTagNameWrapper && shouldHaveTagNameHere && currentValue.tagInterpolationKey) {
return {
replacement: currentValue.tagName,
getExtraValues: undefined,
};
}
if (shouldHaveTagNameHere && !isTagNameWrapper) {
console.error({
lastNewString,
currentTemplateString,
currentValue,
});
throw new Error(`Got interpolated tag name but found no tag name on the given value: '${currentValue?.tagName ||
currentValue?.prototype?.constructor?.name ||
currentValue?.constructor?.name}'`);
}
if (!shouldHaveTagNameHere || !isTagNameWrapper) {
return undefined;
}
const replacement = currentValue.tagName;
return {
replacement,
getExtraValues(extraValueCurrentValue) {
const assignedInputs = isMinimalDefinitionWithInputs(extraValueCurrentValue)
? extraValueCurrentValue.inputs
: undefined;
return [
isOpeningTag && assignedInputs ? assign(assignedInputs) : undefined,
].filter(check.isTruthy);
},
};
}
function extractCustomElementTags(input) {
const tagNameMatches = safeMatch(input, /<\/\s*[^\s><]+\s*>/g);
return tagNameMatches.reduce((accum, match) => {
const tagName = collapseWhiteSpace(match.replace(/\n/g, ' ')).replace(/<\/|>/g, '');
// custom elements always have a dash in them
if (tagName.includes('-')) {
return accum.concat(tagName);
}
return accum;
}, []);
}
function stringValidator(input) {
if (declarativeElementRequired) {
const customElementTagNames = extractCustomElementTags(input);
if (customElementTagNames.length) {
console.error(`Custom element tags must be interpolated from declarative elements: ${customElementTagNames.join(', ')}`);
}
}
}
export function transformHtmlTemplate(litTemplate) {
return transformTemplate(litTemplate.strings, litTemplate.values, transformHtml, stringValidator);
}