diff options
author | 2023-02-06 17:01:21 -0800 | |
---|---|---|
committer | 2023-02-06 20:01:21 -0500 | |
commit | ec38a8921f02a275949abcababe1b8afdf8184a2 (patch) | |
tree | d1634f74690af1e87d2bcaa1b8296b3f0642c151 /packages/integrations/lit/src | |
parent | 327a76c3dbd6945e1a98b0262d7f20edaf1914b1 (diff) | |
download | astro-ec38a8921f02a275949abcababe1b8afdf8184a2.tar.gz astro-ec38a8921f02a275949abcababe1b8afdf8184a2.tar.zst astro-ec38a8921f02a275949abcababe1b8afdf8184a2.zip |
[Lit] add `client:only` functionality to Lit integration (#6111)
* [Lit] add `client:only` functionality to Lit integration
* add changeset
* update lit changeset to minor
Diffstat (limited to 'packages/integrations/lit/src')
-rw-r--r-- | packages/integrations/lit/src/client.ts | 89 |
1 files changed, 70 insertions, 19 deletions
diff --git a/packages/integrations/lit/src/client.ts b/packages/integrations/lit/src/client.ts index fb92ac3df..b63147182 100644 --- a/packages/integrations/lit/src/client.ts +++ b/packages/integrations/lit/src/client.ts @@ -1,21 +1,72 @@ -export default (element: HTMLElement) => async (Component: any, props: Record<string, any>) => { - // Get the LitElement element instance (may or may not be upgraded). - const component = element.children[0] as HTMLElement; - - // If there is no deferral of hydration, then all reactive properties are - // already serialzied as reflected attributes, or no reactive props were set - if (!component || !component.hasAttribute('defer-hydration')) { - return; - } - - // Set properties on the LitElement instance for resuming hydration. - for (let [name, value] of Object.entries(props)) { - // Check if reactive property or class property. - if (name in Component.prototype) { - (component as any)[name] = value; +/** + * Adds the appropriate slot attribute to each top-level node in the given HTML + * string. + * + * @example + * addSlotAttrsToHtmlString('foo', '<div>bar</div><div>baz</div>'); + * // '<div slot="foo">bar</div><div slot="foo">baz</div>' + * + * @param slotName Name of slot to apply to HTML string. + * @param html Stringified HTML that should be projected into the given slotname. + * @returns A stringified HTML string with the slot attribute applied to each top-level node. + */ +const addSlotAttrsToHtmlString = (slotName: string, html: string) => { + const templ = document.createElement('template'); + templ.innerHTML = html; + Array.from(templ.content.children).forEach((node) => { + node.setAttribute('slot', slotName); + }); + return templ.innerHTML; +}; + +export default (element: HTMLElement) => + async ( + Component: any, + props: Record<string, any>, + { default: defaultChildren, ...slotted }: { default: string; [slotName: string]: string } + ) => { + // Get the LitElement element instance. + let component = element.children[0]; + // Check if hydration model is client:only + const isClientOnly = element.getAttribute('client') === 'only'; + + // We need to attach the element and it's children to the DOM since it's not + // SSR'd. + if (isClientOnly) { + component = new Component(); + + const otherSlottedChildren = Object.entries(slotted) + .map(([slotName, htmlStr]) => addSlotAttrsToHtmlString(slotName, htmlStr)) + .join(''); + + // defaultChildren can actually be undefined, but TS will complain if we + // type it as so, make sure we don't render undefined. + component.innerHTML = `${defaultChildren ?? ''}${otherSlottedChildren}`; + element.appendChild(component); + + // Set props bound to non-reactive properties as attributes. + for (let [name, value] of Object.entries(props)) { + if (!(name in Component.prototype)) { + component.setAttribute(name, value); + } + } } - } - // Tell LitElement to resume hydration. - component.removeAttribute('defer-hydration'); -}; + // If there is no deferral of hydration, then all reactive properties are + // already serialzied as reflected attributes, or no reactive props were set + // Alternatively, if hydration is client:only proceed to set props. + if (!component || !(component.hasAttribute('defer-hydration') || isClientOnly)) { + return; + } + + // Set properties on the LitElement instance for resuming hydration. + for (let [name, value] of Object.entries(props)) { + // Check if reactive property or class property. + if (name in Component.prototype) { + (component as any)[name] = value; + } + } + + // Tell LitElement to resume hydration. + component.removeAttribute('defer-hydration'); + }; |