将 Lit 元素移动到新文档时未保留样式
Styles not preserved when moving Lit element to new document
问题
所以我有一个 Lit 元素,我正试图移动到一个新文档。我可以让它移动并且所有功能仍然存在,以及它与原始文档之间的通信。但是它缺少所有样式。
例如,如果我有这样一个简单的 Lit 元素:
@customElement("my-el")
export class customEl extends LitElement {
popout () {
const newWindow = window.open('', '_blank', 'title=Search Controls,height=1447,width=405,top=0,…o,status=no,menubar=no,scrollbars=no,resizable=no');
newWindow.document.body.appendChild( newWindow.document.adoptNode( this ) );
}
static styles = css`
.custom-el {
background-color: #666;
color: #fff;
}
.custom-el .custom-el_text {
padding: 15px;
border: 1px solid red;
font-family: "Comic Sans", "Arial", "Helvetica", sans-serif;
font-size: 44px;
font-weight: 900;
}
`;
render() {
return html`
<div class="custom-el">
<p class="custom-el_text">Hello World</p>
</div>
`;
}
}
如果调用 popout
方法,元素将移动到新的 window 中,除了样式之外一切都完好无损。
我试过的
深入研究 Lit NPM 包,我尝试追踪设置 adoptedStyleSheets
属性 的位置。我没有在 window
、document
、renderRoot
或 shadowRoot
上找到它。我希望我能以某种方式将采用的样式表从一个文档迁移到另一个文档。
使用 importNode
而不是 adoptNode
。这导致重新设置元素的状态,这对我的用例来说是非常不希望的,但也会给出错误 Failed to set the 'adoptedStyleSheets' property on 'ShadowRoot': Sharing constructed stylesheets in multiple documents is not allowed
。我认为这可能是样式未被移动的根本原因,但我不确定该怎么做。
我需要什么
需要一种方法将元素从一个文档移动到另一个文档,同时保留状态、功能和样式。当前方法覆盖状态和功能,但不保留样式。在新 window(弹出窗口)中打开元素时寻找一种方法来维护所有这三个元素。
工作示例
您可以在此处找到此问题的工作示例:https://codepen.io/Arrbjorn/pen/bGaPMBa
问题的根源
所以这里的问题是构造样式表的规范不支持跨文档共享构造样式表,因此出现错误 Failed to set the 'adoptedStyleSheets' property on 'ShadowRoot': Sharing constructed stylesheets in multiple documents is not allowed
。
有关做出此决定的原因的更多信息,您可以在此处阅读原始讨论:https://github.com/WICG/construct-stylesheets/issues/23
解决方案
但是,对于需要在新文档中打开的 Web 组件,无论是 iframe
、新的 window
还是其他文档,都有一个解决方法。我们可以在 Web 组件的 adoptedCallback
函数中解决这个期望的行为。这是一个可能看起来像的示例:
import { CSSResultGroup, supportsAdoptingStyleSheets } from "lit";
adoptedCallback(){
// If the browser supports adopting stylesheets
if (supportsAdoptingStyleSheets) {
// If the styles is an array of CSSResultGroup Objects
// This happens when styles is passed an array i.e. => static styles = [css`${styles1}`, css`${styles2}`] in the component
if ( ((this.constructor as typeof LitElement).styles as CSSResultGroup[]).length ) {
// Define the sheets array by mapping the array of CSSResultGroup objects
const sheets = ((this.constructor as typeof LitElement).styles as CSSResultGroup[]).map( s => {
// Create a new stylesheet in the context of the owner document's window
// We have to cast defaultView as any due to typescript definition not allowing us to call CSSStyleSheet in this conext
// We have to cast CSSStyleSheet as <any> due to typescript definition not containing replaceSync for CSSStyleSheet
const sheet = (new (this.ownerDocument.defaultView as any).CSSStyleSheet() as any);
// Update the new sheet with the old styles
sheet.replaceSync(s);
// Return the sheet
return sheet;
});
// Set adoptedStyleSheets with the new styles (must be an array)
(this.shadowRoot as any).adoptedStyleSheets = sheets;
} else {
// Create a new stylesheet in the context of the owner document's window
// We have to cast defaultView as any due to typescript definition not allowing us to call CSSStyleSheet in this conext
// We have to cast CSSStyleSheet as <any> due to typescript definition not containing replaceSync for CSSStyleSheet
const sheet = (new (this.ownerDocument.defaultView as any).CSSStyleSheet() as any);
// Update the new sheet with the old styles
sheet.replaceSync( (this.constructor as typeof LitElement).styles );
// Set adoptedStyleSheets with the new styles (must be an array)
(this.shadowRoot as any).adoptedStyleSheets = [ sheet ];
}
}
}
这是在做什么?
此代码块引用自定义元素的原始样式,并在新文档的上下文中创建新的 CSSStyleSheet
(或 CssStyleSheet[]
)并将它们应用于该元素。这样我们就不会将原始样式共享到新文档,而是使用 BY 新文档创建的新样式表。
正在优化
如果您像我一样在许多组件中使用它,这些组件可能会移动到另一个文档,您可以将其抽象为一个实用函数,该函数可以导入任何组件并在 adoptedCallback
期间调用。
函数
import { CSSResultGroup, supportsAdoptingStyleSheets } from "lit";
// This function migrates styles from a custom element's constructe stylesheet to a new document.
export function adoptStyles ( shadowRoot: ShadowRoot, styles: CSSResultGroup | CSSResultGroup[], defaultView: Window) {
// If the browser supports adopting stylesheets
if (supportsAdoptingStyleSheets) {
// If the styles is an array of CSSResultGroup Objects
// This happens when styles is passed an array i.e. => static styles = [css`${styles1}`, css`${styles2}`] in the component
if ( (styles as CSSResultGroup[]).length ) {
// Define the sheets array by mapping the array of CSSResultGroup objects
const sheets = (styles as CSSResultGroup[]).map( s => {
// Create a new stylesheet in the context of the owner document's window
// We have to cast defaultView as any due to typescript definition not allowing us to call CSSStyleSheet in this conext
// We have to cast CSSStyleSheet as <any> due to typescript definition not containing replaceSync for CSSStyleSheet
const sheet = (new (defaultView as any).CSSStyleSheet() as any);
// Update the new sheet with the old styles
sheet.replaceSync(s);
// Return the sheet
return sheet;
});
// Set adoptedStyleSheets with the new styles (must be an array)
(shadowRoot as any).adoptedStyleSheets = sheets;
} else {
// Create a new stylesheet in the context of the owner document's window
// We have to cast defaultView as any due to typescript definition not allowing us to call CSSStyleSheet in this conext
// We have to cast CSSStyleSheet as <any> due to typescript definition not containing replaceSync for CSSStyleSheet
const sheet = (new (defaultView as any).CSSStyleSheet() as any);
// Update the new sheet with the old styles
sheet.replaceSync(styles);
// Set adoptedStyleSheets with the new styles (must be an array)
(shadowRoot as any).adoptedStyleSheets = [ sheet ];
}
}
}
从组件调用函数
adoptedCallback() {
// Adopt the old styles into the new document
adoptStyles( this.shadowRoot!, (this.constructor as typeof LitElement).styles!, this.ownerDocument.defaultView! )
}
问题
所以我有一个 Lit 元素,我正试图移动到一个新文档。我可以让它移动并且所有功能仍然存在,以及它与原始文档之间的通信。但是它缺少所有样式。
例如,如果我有这样一个简单的 Lit 元素:
@customElement("my-el")
export class customEl extends LitElement {
popout () {
const newWindow = window.open('', '_blank', 'title=Search Controls,height=1447,width=405,top=0,…o,status=no,menubar=no,scrollbars=no,resizable=no');
newWindow.document.body.appendChild( newWindow.document.adoptNode( this ) );
}
static styles = css`
.custom-el {
background-color: #666;
color: #fff;
}
.custom-el .custom-el_text {
padding: 15px;
border: 1px solid red;
font-family: "Comic Sans", "Arial", "Helvetica", sans-serif;
font-size: 44px;
font-weight: 900;
}
`;
render() {
return html`
<div class="custom-el">
<p class="custom-el_text">Hello World</p>
</div>
`;
}
}
如果调用 popout
方法,元素将移动到新的 window 中,除了样式之外一切都完好无损。
我试过的
深入研究 Lit NPM 包,我尝试追踪设置 adoptedStyleSheets
属性 的位置。我没有在 window
、document
、renderRoot
或 shadowRoot
上找到它。我希望我能以某种方式将采用的样式表从一个文档迁移到另一个文档。
使用 importNode
而不是 adoptNode
。这导致重新设置元素的状态,这对我的用例来说是非常不希望的,但也会给出错误 Failed to set the 'adoptedStyleSheets' property on 'ShadowRoot': Sharing constructed stylesheets in multiple documents is not allowed
。我认为这可能是样式未被移动的根本原因,但我不确定该怎么做。
我需要什么
需要一种方法将元素从一个文档移动到另一个文档,同时保留状态、功能和样式。当前方法覆盖状态和功能,但不保留样式。在新 window(弹出窗口)中打开元素时寻找一种方法来维护所有这三个元素。
工作示例
您可以在此处找到此问题的工作示例:https://codepen.io/Arrbjorn/pen/bGaPMBa
问题的根源
所以这里的问题是构造样式表的规范不支持跨文档共享构造样式表,因此出现错误 Failed to set the 'adoptedStyleSheets' property on 'ShadowRoot': Sharing constructed stylesheets in multiple documents is not allowed
。
有关做出此决定的原因的更多信息,您可以在此处阅读原始讨论:https://github.com/WICG/construct-stylesheets/issues/23
解决方案
但是,对于需要在新文档中打开的 Web 组件,无论是 iframe
、新的 window
还是其他文档,都有一个解决方法。我们可以在 Web 组件的 adoptedCallback
函数中解决这个期望的行为。这是一个可能看起来像的示例:
import { CSSResultGroup, supportsAdoptingStyleSheets } from "lit";
adoptedCallback(){
// If the browser supports adopting stylesheets
if (supportsAdoptingStyleSheets) {
// If the styles is an array of CSSResultGroup Objects
// This happens when styles is passed an array i.e. => static styles = [css`${styles1}`, css`${styles2}`] in the component
if ( ((this.constructor as typeof LitElement).styles as CSSResultGroup[]).length ) {
// Define the sheets array by mapping the array of CSSResultGroup objects
const sheets = ((this.constructor as typeof LitElement).styles as CSSResultGroup[]).map( s => {
// Create a new stylesheet in the context of the owner document's window
// We have to cast defaultView as any due to typescript definition not allowing us to call CSSStyleSheet in this conext
// We have to cast CSSStyleSheet as <any> due to typescript definition not containing replaceSync for CSSStyleSheet
const sheet = (new (this.ownerDocument.defaultView as any).CSSStyleSheet() as any);
// Update the new sheet with the old styles
sheet.replaceSync(s);
// Return the sheet
return sheet;
});
// Set adoptedStyleSheets with the new styles (must be an array)
(this.shadowRoot as any).adoptedStyleSheets = sheets;
} else {
// Create a new stylesheet in the context of the owner document's window
// We have to cast defaultView as any due to typescript definition not allowing us to call CSSStyleSheet in this conext
// We have to cast CSSStyleSheet as <any> due to typescript definition not containing replaceSync for CSSStyleSheet
const sheet = (new (this.ownerDocument.defaultView as any).CSSStyleSheet() as any);
// Update the new sheet with the old styles
sheet.replaceSync( (this.constructor as typeof LitElement).styles );
// Set adoptedStyleSheets with the new styles (must be an array)
(this.shadowRoot as any).adoptedStyleSheets = [ sheet ];
}
}
}
这是在做什么?
此代码块引用自定义元素的原始样式,并在新文档的上下文中创建新的 CSSStyleSheet
(或 CssStyleSheet[]
)并将它们应用于该元素。这样我们就不会将原始样式共享到新文档,而是使用 BY 新文档创建的新样式表。
正在优化
如果您像我一样在许多组件中使用它,这些组件可能会移动到另一个文档,您可以将其抽象为一个实用函数,该函数可以导入任何组件并在 adoptedCallback
期间调用。
函数
import { CSSResultGroup, supportsAdoptingStyleSheets } from "lit";
// This function migrates styles from a custom element's constructe stylesheet to a new document.
export function adoptStyles ( shadowRoot: ShadowRoot, styles: CSSResultGroup | CSSResultGroup[], defaultView: Window) {
// If the browser supports adopting stylesheets
if (supportsAdoptingStyleSheets) {
// If the styles is an array of CSSResultGroup Objects
// This happens when styles is passed an array i.e. => static styles = [css`${styles1}`, css`${styles2}`] in the component
if ( (styles as CSSResultGroup[]).length ) {
// Define the sheets array by mapping the array of CSSResultGroup objects
const sheets = (styles as CSSResultGroup[]).map( s => {
// Create a new stylesheet in the context of the owner document's window
// We have to cast defaultView as any due to typescript definition not allowing us to call CSSStyleSheet in this conext
// We have to cast CSSStyleSheet as <any> due to typescript definition not containing replaceSync for CSSStyleSheet
const sheet = (new (defaultView as any).CSSStyleSheet() as any);
// Update the new sheet with the old styles
sheet.replaceSync(s);
// Return the sheet
return sheet;
});
// Set adoptedStyleSheets with the new styles (must be an array)
(shadowRoot as any).adoptedStyleSheets = sheets;
} else {
// Create a new stylesheet in the context of the owner document's window
// We have to cast defaultView as any due to typescript definition not allowing us to call CSSStyleSheet in this conext
// We have to cast CSSStyleSheet as <any> due to typescript definition not containing replaceSync for CSSStyleSheet
const sheet = (new (defaultView as any).CSSStyleSheet() as any);
// Update the new sheet with the old styles
sheet.replaceSync(styles);
// Set adoptedStyleSheets with the new styles (must be an array)
(shadowRoot as any).adoptedStyleSheets = [ sheet ];
}
}
}
从组件调用函数
adoptedCallback() {
// Adopt the old styles into the new document
adoptStyles( this.shadowRoot!, (this.constructor as typeof LitElement).styles!, this.ownerDocument.defaultView! )
}