Как смонтировать стили внутри теневого корня, используя 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);