-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Lit] add
client:only
functionality to Lit integration (#6111)
* [Lit] add `client:only` functionality to Lit integration * add changeset * update lit changeset to minor
- Loading branch information
Showing
6 changed files
with
115 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
'@astrojs/lit': minor | ||
'astro': patch | ||
--- | ||
|
||
Implement client:only functionality in Lit and add lit to the client:only warning |
9 changes: 9 additions & 0 deletions
9
packages/astro/e2e/fixtures/lit-component/src/components/ClientOnlyComponent.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { LitElement, html } from 'lit'; | ||
|
||
export default class ClientOnlyComponent extends LitElement { | ||
render() { | ||
return html`<slot><div class="defaultContent"> Shadow dom default content should not be visible</div></slot><slot name="foo"></slot><slot name="bar"></slot></div>`; | ||
} | ||
} | ||
|
||
customElements.define('client-only-component', ClientOnlyComponent); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'); | ||
}; |