Как смонтировать стили внутри теневого корня, используя cssinjs/jss

Я пытаюсь использовать https://material-ui.com/ компоненты внутри shadow dom, и мне нужен способ внедрить эти стили внутри shadow dom. по умолчанию material-ui, который использует jss под капотом вводит стили в заголовок страницы.

Это вообще возможно? Кто-нибудь может привести пример?

4 ответа

Так выглядит мой веб-компонент. Это веб-компонент, который отображает приложение React, содержащее стили material-ui.

import * as React from 'react';
import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '@material-ui/styles';
import { create } from 'jss';

import { App } from '@myApp/core';

class MyWebComponent extends HTMLElement {
  connectedCallback() {
    const shadowRoot = this.attachShadow({ mode: 'open' });
    const mountPoint = document.createElement('span');
    const reactRoot = shadowRoot.appendChild(mountPoint);
    const jss = create({
      ...jssPreset(),
      insertionPoint: reactRoot
    });

    render(
      <StylesProvider jss={jss}>
        <App />
      </StylesProvider>,
      mountPoint
    );
  }
}
customElements.define('my-web-commponent', MyWebComponent);

Установка insertionPoint на jss к фактическому корню реакции внутри теневого корня скажет jss вставить эти стили в этот теневой корень.

Используя https://github.com/Wildhoney/ReactShadow для создания теневого пространства (вы также можете сделать это вручную, как показано в предыдущем ответе), я создал небольшой WrapperComponent, который инкапсулирует логику.

import root from 'react-shadow';
import {jssPreset, StylesProvider} from "@material-ui/styles";
import {create} from 'jss';
import React, {useState} from "react"

const WrappedJssComponent = ({children}) => {
  const [jss, setJss] = useState(null);
  
  function setRefAndCreateJss(headRef) {
    if (headRef && !jss) {
      const createdJssWithRef = create({...jssPreset(), insertionPoint: headRef})
      setJss(createdJssWithRef)
    }
  }
  
  return (
    <root.div>
      <head>
        <style ref={setRefAndCreateJss}></style>
      </head>
      {jss &&
      <StylesProvider jss={jss}>
        {children}
      </StylesProvider>
      }
    
    </root.div>
  )
}

export default WrappedJssComponent

Затем вам просто нужно обернуть ваше приложение или ту часть вашего приложения, которую вы хотите затенять внутри <WrappedJssComponenent><YourComponent></YourComponent></WrappedJssComponenent>.

Будьте осторожны, некоторые компоненты UI-материала не будут работать как обычно (у меня были проблемы с

  • ClickAwayListener, возможно, потому, что он использует родительский дом, не исследовал больше, если честно.
  • Popper, и все, кто попытается использовать document.body в качестве контейнера, не будет иметь доступа к jss, определенному в теневом узле. Вы должны указать элемент внутри теневого дома в качестве контейнера.

Теперь в документации есть целая страница (MaterialUI 5), на которой рассказывается, как интегрировать MUI с теневым домом. Вам также может потребоваться установить значения по умолчанию Portal, чтобы не нацеливаться на dom. https://mui.com/material-ui/guides/shadow-dom/

Когда используешь @material-ui/core/CssBaselineс MUI также используются стили. Для поддержки обоих устаревших jssа также emotionвы можете расширить принятый ответ выше с помощью CacheProviderкак это:

      import ReactDOM from 'react-dom/client'
import App from './App'
import createCache from '@emotion/cache'
import { CacheProvider } from '@emotion/react';
import { StylesProvider, jssPreset } from '@material-ui/styles';
import { create } from 'jss';

class ReportComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    const mountPoint = document.createElement('div');
    const emotionPoint = this.shadowRoot!.appendChild(document.createElement('div'));
    const emotionCache = createCache({
      key: 'report-component',
      container: emotionPoint
    });

    const reactRoot = this.shadowRoot!.appendChild(mountPoint);
    const root = ReactDOM.createRoot(reactRoot);
    const jss = create({
      ...jssPreset(),
      insertionPoint: reactRoot
    });

    root.render(
      <StylesProvider jss={jss}>
        <CacheProvider value={emotionCache}>
          <App />
        </CacheProvider>
      </StylesProvider>
    );
  }
}
customElements.define('report-component', ReportComponent);
Другие вопросы по тегам