-
Notifications
You must be signed in to change notification settings - Fork 600
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* expose dom shim from package root * ensure DOM updates are made synchronously * upgrade lit to use latest dom shim * fleshing out dom-shim with foundation-element requirements * fix root <template> behavior to be more durable to formatting * implement classList binding for native elements * enables style retrevial for a custom element through SSR style-strategy * clean up exports * add tagName to elements created by ElementRenderer to be used by componentPresentation * prettier Co-authored-by: nicholasrice <[email protected]>
- Loading branch information
1 parent
260f816
commit 6a0c833
Showing
10 changed files
with
260 additions
and
39 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
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,24 +1,151 @@ | ||
import { installWindowOnGlobal } from "@lit-labs/ssr/lib/dom-shim.js"; | ||
|
||
installWindowOnGlobal(); | ||
|
||
// Implement shadowRoot getter on HTMLElement. | ||
// Can be removed if https://github.com/lit/lit/issues/2652 | ||
// is integrated. | ||
class PatchedHTMLElement extends HTMLElement { | ||
#shadowRoot: ShadowRoot | null = null; | ||
get shadowRoot() { | ||
class DOMTokenList { | ||
#tokens = new Set<string>(); | ||
|
||
public add(value: string) { | ||
this.#tokens.add(value); | ||
} | ||
|
||
public remove(value: string) { | ||
this.#tokens.delete(value); | ||
} | ||
|
||
public contains(value: string) { | ||
return this.#tokens.has(value); | ||
} | ||
|
||
public toggle(value: string, force?: boolean) { | ||
const add = force === undefined ? !this.contains(value) : force; | ||
|
||
if (add) { | ||
this.add(value); | ||
return true; | ||
} else { | ||
this.remove(value); | ||
return false; | ||
} | ||
} | ||
|
||
public toString() { | ||
return Array.from(this.#tokens).join(" "); | ||
} | ||
|
||
*[Symbol.iterator]() { | ||
yield* this.#tokens.values(); | ||
} | ||
} | ||
class Node { | ||
appendChild() {} | ||
removeChild() {} | ||
} | ||
|
||
class Element extends Node {} | ||
|
||
abstract class HTMLElement extends Element { | ||
#attributes = new Map<string, string | DOMTokenList>(); | ||
#shadowRoot: null | ShadowRoot = null; | ||
|
||
public readonly classList = new DOMTokenList(); | ||
|
||
public get attributes(): { name: string; value: string }[] { | ||
return Array.from(this.#attributes).map(([name, value]) => { | ||
if (typeof value === "string") { | ||
return { name, value }; | ||
} else { | ||
return { name, value: value.toString() }; | ||
} | ||
}); | ||
} | ||
|
||
public get shadowRoot() { | ||
return this.#shadowRoot; | ||
} | ||
attachShadow(init: ShadowRootInit) { | ||
const root = super.attachShadow(init); | ||
|
||
if (init.mode === "open") { | ||
this.#shadowRoot = root; | ||
public abstract attributeChangedCallback?( | ||
name: string, | ||
old: string | null, | ||
value: string | null | ||
): void; | ||
|
||
public setAttribute(name: string, value: string) { | ||
let _value: string | DOMTokenList = value; | ||
if (name === "class") { | ||
_value = new DOMTokenList(); | ||
value.split(" ").forEach(className => { | ||
(_value as DOMTokenList).add(className); | ||
}); | ||
} | ||
this.#attributes.set(name, _value); | ||
} | ||
|
||
public removeAttribute(name: string) { | ||
this.#attributes.delete(name); | ||
} | ||
|
||
public hasAttribute(name: string) { | ||
return this.#attributes.has(name); | ||
} | ||
|
||
public attachShadow(init: ShadowRootInit) { | ||
const shadowRoot = ({ host: this } as unknown) as ShadowRoot; | ||
if (init && init.mode === "open") { | ||
this.#shadowRoot = shadowRoot; | ||
} | ||
return shadowRoot; | ||
} | ||
|
||
public getAttribute(name: string) { | ||
const value = this.#attributes.get(name); | ||
return value === undefined | ||
? null | ||
: value instanceof DOMTokenList | ||
? value.toString() | ||
: value; | ||
} | ||
} | ||
|
||
return root; | ||
class Document { | ||
head = new Node(); | ||
adoptedStyleSheets = []; | ||
createTreeWalker() { | ||
return {}; | ||
} | ||
createTextNode() { | ||
return {}; | ||
} | ||
createElement(tagName: string) { | ||
return { tagName }; | ||
} | ||
querySelector() { | ||
return undefined; | ||
} | ||
addEventListener() {} | ||
} | ||
|
||
class CSSStyleDeclaration { | ||
setProperty() {} | ||
} | ||
|
||
window.HTMLElement = PatchedHTMLElement; | ||
class CSSStyleSheet { | ||
get cssRules() { | ||
return [{ style: new CSSStyleDeclaration() }]; | ||
} | ||
replace() {} | ||
insertRule() { | ||
return 0; | ||
} | ||
} | ||
|
||
class MediaQueryList { | ||
addListener() {} | ||
matches = false; | ||
} | ||
installWindowOnGlobal({ | ||
matchMedia: () => new MediaQueryList(), | ||
HTMLElement, | ||
Document, | ||
document: new Document(), | ||
Node, | ||
CSSStyleSheet, | ||
}); |
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
36 changes: 34 additions & 2 deletions
36
packages/web-components/fast-ssr/src/element-renderer/style-strategy.ts
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,5 +1,37 @@ | ||
import { StyleStrategy } from "@microsoft/fast-element"; | ||
import { StyleStrategy, StyleTarget } from "@microsoft/fast-element"; | ||
|
||
const sheetsForElement = new WeakMap<Element, Set<string | CSSStyleSheet>>(); | ||
|
||
function getOrCreateFor(target: Element): Set<string | CSSStyleSheet> { | ||
let set = sheetsForElement.get(target); | ||
if (set) { | ||
return set; | ||
} | ||
|
||
set = new Set<string | CSSStyleSheet>(); | ||
sheetsForElement.set(target, set); | ||
|
||
return set; | ||
} | ||
|
||
function isShadowRoot(target: any): target is ShadowRoot { | ||
return !!target.host; | ||
} | ||
|
||
export class FASTSSRStyleStrategy implements StyleStrategy { | ||
addStylesTo() {} | ||
addStylesTo(target: StyleTarget) { | ||
if (isShadowRoot(target)) { | ||
const cache = getOrCreateFor(target.host); | ||
|
||
this.styles.forEach(style => cache?.add(style)); | ||
} | ||
} | ||
|
||
removeStylesFrom() {} | ||
|
||
constructor(private styles: (string | CSSStyleSheet)[]) {} | ||
|
||
public static getStylesFor(target: Element): Set<string | CSSStyleSheet> | null { | ||
return sheetsForElement.get(target) || null; | ||
} | ||
} |
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
Oops, something went wrong.