Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Widget styles override consuming app styles #144

Open
jusa3 opened this issue Oct 10, 2024 · 11 comments
Open

Widget styles override consuming app styles #144

jusa3 opened this issue Oct 10, 2024 · 11 comments

Comments

@jusa3
Copy link
Collaborator

jusa3 commented Oct 10, 2024

Problem: The style issue stems from the global styles being injected by the EuiProvider from @elastic/eui and the Emotion CSS-in-JS library that it uses for theming and styling.

Temporary workaround: After the widget is rendered, remove the injected global styles. Specifically, the <style data-emotion="css-global" data-s=""></style> tag is dynamically generated by Emotion when global styles are applied, and it seems to override the app's existing styles, causing unintended changes. This happens because EuiProvider applies its own global styles which may conflict with or override the app’s styles. When we remove the style tag from the DOM, those global styles no longer apply.

@jusa3
Copy link
Collaborator Author

jusa3 commented Oct 10, 2024

Solution for React: To avoid using global styles in the widgets, we need to prevent or limit the application of the global CSS rules that EuiProvider and Emotion's CSS-in-JS solution inject into the document. We can do this by using a CacheProvider as described here: emotion-js/emotion#1078

const styleElementId = "eui-local-style";
  
  const getOrCreateStyleElement = () => {
    let styleElement = document.querySelector(`#${styleElementId}`);
    if (!styleElement) {
      styleElement = document.createElement("style");
      styleElement.setAttribute("id", styleElementId);
      document.head.appendChild(styleElement);
    }
    return styleElement;
  };

  const createEmotionCache = () => {
    const styleElement = getOrCreateStyleElement();
    return createCache({
      key: "custom",
      container: styleElement
    });
  };

  const cache = createEmotionCache();


  return (
    <CacheProvider value={cache}>
      <EuiProvider colorMode="light">
        <EuiComboBox
        ...

This works well with React but not with the plainJS environment so the issue is still open.

@jusa3
Copy link
Collaborator Author

jusa3 commented Oct 14, 2024

More hints:

  • Another option could be using the EuiThemeProvider.
  • Prevent using global style option: <EuiProvider colorMode="light" globalStyles={false}>
  • Try with local build (npm run build:plainJS) and linking in this project: https://github.com/ts4nfdi/Workshop-FDM-Campus

@VincentKneip
Copy link
Collaborator

Styling can be inspected via the tab "Stilbearbeitung". The styles overriding the global styles occur inside a website embedded style document. Normally, you can see which styles are applied by each style document, but for embedded style documents this does not seem to work.

Disabling this style document via the eye button on the left removes the unwanted styles, but also the styles for the imported widget:

embedded style document enabled embedded style document disabled
grafik grafik

If we could figure out a way to see which specific styles are applied by the styling document, we could manually include them in a proper styles file and discard the use of the EUI styles.

@jusa3
Copy link
Collaborator Author

jusa3 commented Oct 25, 2024

Disabling the global styles of the EuiProvider with the globalStyles={false} option is the solution to prevent global style injection, but results in a different look:

Before:
image

After:
image

@johannes-darms
Copy link
Contributor

@jusa3 Are these issues still present in the latest version of elasticUI? The migration to emotion seems to be complete and since v96 https://github.com/elastic/eui/releases/v96.0.0 all css files are removed. Combined with the https://emotion.sh/docs/@emotion/cache#options and https://eui.elastic.co/v97.3.1/#/utilities/provider#theming-and-global-styles I would assume that the styling of elasticui does not interfere with other style definitions. If it does, we should open an issue with elasticui and try to resolve the problem.

If you plan to migrate away from elasticUI, please discuss this change with any downstream users of the widgets, as there are some implications.

@johannes-darms
Copy link
Contributor

Regardless of the UI kit used, this problem will occur. Since each application has its own design language, it will most likely at some point conflict with the one used in a reused component. Therefore, as a user of a library, you can either accept the difference, if it is not so big that it affects the usability, or you have to adapt it to your liking. The latter requires a library that allows this kind of customisation and thus is bit more complex to create.

jusa3 added a commit that referenced this issue Dec 4, 2024
Widget styles override consuming app styles. Remove the EuiProvider that injects the problematic global styles and introduce a css object to the EUI components with custom styles.

Related to #144
jusa3 added a commit that referenced this issue Dec 4, 2024
Add custom css objects to the DataContentWidget to get the original style.

Related to #144
@jusa3
Copy link
Collaborator Author

jusa3 commented Dec 4, 2024

Two more approaches:

  1. In this branch we tried using a style wrapper that wraps the Eui reset styles. Instead of a wrapper, it's possible to just use a <div id="tss-styles"> element that wraps the eui component/widget. The reset styles can then be prefixed with the css descendant combinator. a style injection is also needed <style>{tss-styles}</styles>. But this only allows resetting the styles. We would need to inject the eui core styles, like the EuiProvider does. We just need to find those styles.

https://github.com/ts4nfdi/terminology-service-suite/tree/fix/prevent%23-global-style-injection

  1. In this branch we tried the approach to remove the EuiProvider and insert custom css objects into the Eui components. It solves the issue, but it's work-intensive, messy, and will cause problems using the widgets in a React app that uses the Eui library.

https://github.com/ts4nfdi/terminology-service-suite/tree/eui-style-issue

Code example:

<EuiCard
          css={{
            ...fontReset,
            color: euiTheme.colors.text,
            padding: "16px",
            ".euiCard__title": {
              marginTop: "4px",
              marginBottom: "-0.4em",
            },
        }}

@jusa3
Copy link
Collaborator Author

jusa3 commented Dec 4, 2024

Next approach (WIP): address the problem separately for React and html widgets. Using the custom cache (as described above) for React and a Shadow DOM for html:

https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM

@jusa3
Copy link
Collaborator Author

jusa3 commented Dec 5, 2024

Using a custom cache alone does not solve the issue. The styles are still applied globally to all matching DOM elements in the app (the Eui global styles are injected into the <style> tag in the of the document). Eui applies several global styles, including resets, to standard HTML elements like body, html, button, and more. It seems that the Emotion cache manages style injection order, but it doesn’t encapsulate styles to apply only within specific parts of the DOM.

@jusa3
Copy link
Collaborator Author

jusa3 commented Dec 5, 2024

We tested the Shadow DOM approach like this:
AutocompleteWidgetHTML.stories.tsx

<script type="text/javascript">
document.querySelector('#autocomplete_widget_container_${num}').attachShadow({ mode: "open" });

function addStylesheet(url, shadowRoot) {
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = url;
  shadowRoot.appendChild(link);
}


window['SemLookPWidgets'].createAutocomplete(
    {
        api:"${args.api}",
        parameter:"${args.parameter}",
        selectionChangedEvent:${args.selectionChangedEvent.toString().replace(/(\r\n|\n|\r)/gm, "")},
        preselected:${JSON.stringify(args.preselected).replace(/"([^"]+)":/g, '$1:')},
        placeholder:"${args.placeholder}",
        hasShortSelectedLabel:${args.hasShortSelectedLabel},
        allowCustomTerms:${args.allowCustomTerms},
        singleSelection:${args.singleSelection},
        ts4nfdiGateway:${args.ts4nfdiGateway},
        singleSuggestionRow:${args.singleSuggestionRow},
        showApiSource:${args.showApiSource},
    },
    document.querySelector('#autocomplete_widget_container_${num}').shadowRoot
)
addStylesheet('https://unpkg.com/@elastic/[email protected]/dist/eui_theme_light.css', document.querySelector('#autocomplete_widget_container_${num}').shadowRoot);
</script>

and in the consumin app:

<div id="autocomplete_widget_container_1"></div>

    <script>
        function initialize() {
            let widgetContainer = document.getElementById('autocomplete_widget_container_1');
            const shadowRoot = widgetContainer.attachShadow({ mode: "open" });
            window['SemLookPWidgets'].createAutocomplete(
                {
                    api:"https://semanticlookup.zbmed.de/ols/api/",
                    parameter:"fieldList=description,label,iri,ontology_name,type,short_form",
                    selectionChangedEvent:()=>{},
                    preselected:[],
                    placeholder:"Bitte geben Sie einen Begriff ein",
                    hasShortSelectedLabel:true,
                    allowCustomTerms:false,
                    singleSelection:true,
                    ts4nfdiGateway:false,
                    singleSuggestionRow:undefined,
                },
                shadowRoot
            )
        }
        initialize();

But even without injecting the eui theming styles, the eui component styles were missing and the global styles were still injected globally.

No DOM support: elastic/eui#7158:
"Currently, EUI uses several Emotion components which inject styles directly into the head.
This head insertion occurs even when EUI is instantiated from the shadow DOM (when styles should instead be inserted into the shadow DOM container)"

@jusa3
Copy link
Collaborator Author

jusa3 commented Dec 5, 2024

Perhaps the EUI team has an idea on how to solve this problem: elastic/eui#8203

jusa3 added a commit that referenced this issue Jan 13, 2025
add css classes to restore the autocomplete widget style when the global style option of the EuiProvider is removed. As a proof of concept that this approach might work for all widgets.

related to #144
jusa3 added a commit that referenced this issue Jan 16, 2025
This is a proposal for the style issue and custom style feature.

related to #184 #144
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants