javascript - 将 Lit 元素移动到新文档时未保留样式

标签 javascript web-component shadow-dom lit constructable-stylesheet

问题


所以我有一个 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调用方法后,元素将移动到新窗口,除了样式外,所有内容都保持不变。

我尝试过的


深入研究 Lit NPM 包,我尝试追踪它设置 adoptedStyleSheets 的位置。属性(property)。我在window上没有找到它, document , renderRoot ,或shadowRoot 。我希望能够以某种方式将采用的样式表从一个文档迁移到另一个文档。

使用importNode而不是adoptNode 。这会导致重新设置元素的状态,这对于我的用例来说是非常不希望的,但也会给出错误 Failed to set the 'adoptedStyleSheets' property on 'ShadowRoot': Sharing constructed stylesheets in multiple documents is not allowed 。我认为这可能是样式没有移动的根源,但我不知道该怎么办。

我需要什么


需要一种方法将元素从一个文档移动到另一个文档,同时保留状态、功能和样式。当前方法涵盖了状态和功能,但不保留样式。寻找一种在新窗口(弹出窗口)中打开元素时维护所有这三个元素的方法。

工作示例


您可以在这里找到此问题的有效示例:https://codepen.io/Arrbjorn/pen/bGaPMBa

最佳答案

问题的根源


因此,这里的问题是构造样式表的规范不支持跨文档共享构造样式表,因此错误Failed to set the 'adoptedStyleSheets' property on 'ShadowRoot':Sharingbuilt stylesheets in multiple Documents 不是允许

有关为何做出此决定的更多信息,您可以在此处阅读原始讨论:https://github.com/WICG/construct-stylesheets/issues/23

解决方案


但是,对于需要在新文档中打开的 Web 组件,无论是 iframe、新 窗口 还是其他任何内容,都有一种解决方法。我们可以在 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[])并应用它们到元素。这样,我们就不会将原始样式共享到新文档,而是使用新文档创建的新样式表。创建。

优化


如果您像我一样在许多可能被移动到另一个文档的组件中使用它,您可以将其抽象为一个实用函数,该函数可以导入到任何组件中并在 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! )
}

关于javascript - 将 Lit 元素移动到新文档时未保留样式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72016726/

相关文章:

无法识别 JavaScript 函数

javascript - WebComponents 将元素添加到侧边栏

javascript - 如何将 js 对象和函数传递给 Web 组件

javascript - 有没有办法在不使用 Shadow DOM 的情况下将类似 <slot> 的元素添加到 <template> 中?

javascript - 我应该在 angular2 中使用 <page type ="list"> 或 <listpage> 吗?

javascript - 如何让 Coverity 分析第二语言

javascript - 存储 session 详细信息以供将来数据上传到 opencpu 后重用

javascript - 检查 Dom 元素是否为原生/无自定义元素

javascript - 确定用户是否在 shadow dom 外部单击

reactjs - React 是否扼杀了 Shadow-DOM 的概念