Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ui-library, storybook): genericBlrComponentRenderer #976

Merged
merged 1 commit into from
Mar 4, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 64 additions & 48 deletions packages/ui-library/src/utils/typesafe-generic-component-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,80 +6,96 @@ export const genericBlrComponentRenderer = <ComponentType extends { [s: string]:
children?: TemplateResult<1>,
htmlAttributes?: { [s: string]: string | boolean | number }
): TemplateResult<1> => {
const templateFragments: string[] = [];
let templateFragments: string[] = [];
const values: unknown[] = [];
const propEntries = Object.entries(props);
const attrEntries = Object.entries(htmlAttributes || {});

// we will get rid of the dots later on by defining bindings within the component property decorator
propEntries.forEach(([key, value]) => {
const needsOpenTag = templateFragments.length === 0;
// 1. Render HTML open tag.
{
// 1.0. Add HTML tag name fragment
templateFragments.push(`<${tagName}`);

if (typeof value === 'function') {
if (needsOpenTag) {
templateFragments.push(`<${tagName} @${key}=`);
} else {
// 1.1. Collect html attribute name fragments and values from props.
// we will get rid of the dots later on by defining bindings within the component property decorator
propEntries.forEach(([key, value]) => {
if (typeof value === 'function') {
templateFragments.push(` @${key}=`);
}
} else if (key === 'classMap') {
if (needsOpenTag) {
templateFragments.push(`<${tagName} class=`);
} else {
} else if (key === 'classMap') {
templateFragments.push(` class=`);
}
} else if (typeof value === 'boolean') {
if (value === true) {
if (needsOpenTag) {
templateFragments.push(`<${tagName} ${key}=`);
} else {
} else if (typeof value === 'boolean') {
if (value === true) {
templateFragments.push(` ${key}=`);
}
} else {
if (needsOpenTag) {
templateFragments.push(`<${tagName} .${key}=`);
} else {
templateFragments.push(` .${key}=`);
}
}
} else if (typeof value === 'object') {
if (needsOpenTag) {
templateFragments.push(`<${tagName} .${key}=`);
} else {
} else if (typeof value === 'object') {
templateFragments.push(` .${key}=`);
}
} else {
if (needsOpenTag) {
templateFragments.push(`<${tagName} ${key}=`);
} else {
templateFragments.push(` ${key}=`);
}
}
values.push(value);
});

attrEntries.forEach(([key, value], index) => {
if (typeof value !== 'boolean' || value === true) {
if (index === 0 && propEntries.length === 0) {
templateFragments.push(`<${tagName} ${key}=`);
} else {
templateFragments.push(` ${key}=`);
values.push(value);
});

// 1.2. Collect html attribute name fragments and values from htmlAttributes
attrEntries.forEach(([key, value], index) => {
if (typeof value !== 'boolean' || value === true) {
if (index > 0) {
templateFragments.push(` ${key}=`);
}

values.push(value);
}
});

values.push(value);
}
});
// 1.3. Close the open tag after all props and attributes are processed.
templateFragments.push('>');
}

// 2. Insert child elements if any
if (children) {
templateFragments.push(`>`);
values.push(children);
templateFragments.push(`</${tagName}>`);
} else {
templateFragments.push(`></${tagName}>`);
values.push('');
}

// 3. Render the HTML close tag
templateFragments.push(`</${tagName}>`);

/**
* For some reason the HTML open tag can not be followed by an empty value and thus needs to be joined with either the HTML close tag or the first HTML attribute.
* Lit will render incorrectly or throw an error otherwise.
* To clarify:
*
* This does not work:
*
* ```ts
* const templateFragments = ['<custom-button', '></custom-button']
* const values = ['']
*
* html(templateFragments, ...values) // Render Failure
* ```
*
* Whereas this is fine:
*
* ```ts
* const templateFragments = ['<custom-button/></custom-button>']
* const values = []
*
* html(templateFragments, ...values)
* ```
*
* For this reason we de -& re-structure the templateFragments array and join the first fragment
* (which should always be the HTML open tag) with whatever fragment comes next (either a parameter or the close tag).
* Then we include the remaining fragments if any.
*/
const [openTagFragment, firstParameterOrClosingFragment, ...remainingFragments] = templateFragments;
templateFragments = [openTagFragment + firstParameterOrClosingFragment, ...remainingFragments];

// eslint-disable-next-line
// @ts-ignore
templateFragments.raw = templateFragments;
templateFragments.raw = [...templateFragments];

return html(templateFragments as unknown as TemplateStringsArray, ...values);
};
Loading