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

Issue with missing styles when using copyStyles with @emotion #78

Open
karibertils opened this issue Mar 21, 2021 · 5 comments
Open

Issue with missing styles when using copyStyles with @emotion #78

karibertils opened this issue Mar 21, 2021 · 5 comments
Assignees

Comments

@karibertils
Copy link

karibertils commented Mar 21, 2021

When using emotion css lib the copyStyles={true} does not work correctly.

Emotion injects css styles into DOM head at the time the components are mounted. When a new popup using copyStyles={true} has opened, it copies all the existing styles but misses the styles that will be injected.

This is further complicated when there is delay until a component inside the popup are mounted. For example when components need to wait for data, or won't show until state changes happen.

One solution might be to have an interval every 50-100ms monitoring when new styles get added to the parent, and then adding them to the popups

@rmariuzzo
Copy link
Owner

rmariuzzo commented Mar 22, 2021 via email

@Wassap124
Copy link

Wassap124 commented Apr 29, 2021

@karibertils Any chance you have an example of how to add styles to the pop-up after X ms? I tried delaying the mount of the component but it won't work.

@rmariuzzo
I would very much appreciate it if you'd be able to handle some of the issues / PRs that had piled up, the relatively low activity on this repo is stopping the package from being a lot more popular

@rmariuzzo
Copy link
Owner

rmariuzzo commented Jul 5, 2021 via email

@epeterson320
Copy link

epeterson320 commented Feb 28, 2022

I had this need too, since I was using MUI. I was able to get it to work using the below wrapper. @rmariuzzo I'd be happy to open a PR if you think it's appropriate. Best wishes fighting the cancer. I prayed for strength and healing for you.

MuiCompatNewWindow.tsx

import { FC, useCallback, useRef } from "react";
import NewWindow, { INewWindowProps } from "react-new-window";

/**
 * Wrapper for react-new-window that works with MUI (and Emotion).
 *
 * Same interface as react-new-window.
 */
const MUICompatNewWindow: FC<INewWindowProps> = ({
  onOpen,
  onUnload,
  ...props
}) => {
  const mutationObserverRef = useRef<MutationObserver | null>(null);

  const compatOnOpen = useCallback(
    (childWindow: Window) => {
      const childHead = childWindow.document.head;
      const mutationObserver = new MutationObserver((mutationList) => {
        mutationList
          .flatMap((mutation) => Array.from(mutation.addedNodes))
          .filter((node) => node instanceof HTMLStyleElement)
          .forEach((styleEl) => {
            childHead.appendChild(styleEl.cloneNode(true));
          });
      });
      mutationObserver.observe(document.head, { childList: true });
      mutationObserverRef.current = mutationObserver;
      onOpen?.(childWindow);
    },
    [onOpen]
  );

  const compatOnUnload = useCallback(() => {
    mutationObserverRef.current?.disconnect();
    onUnload?.();
  }, [onUnload]);

  return (
    <NewWindow onOpen={compatOnOpen} onUnload={compatOnUnload} {...props} />
  );
};

export default MUICompatNewWindow;

@rmariuzzo rmariuzzo self-assigned this Nov 27, 2022
@cgoinglove
Copy link

The revised code offers targeted enhancements over the original => epeterson320 comment

  1. Efficient Mutation Observing: In the original implementation, using multiple popups would lead to an increase in mutation observers observing the parent's head. The new approach consolidates this by having a single observer for all popups, effectively reducing the overhead and preventing potential performance issues.

  2. Addition of copyStyle Function: The copyStyle function is a new addition, providing a straightforward way to replicate existing styles in new popups. This ensures that popups are consistently styled, even when they are opened after the initial style elements have been added to the document head.

import { useEffect, useId } from 'react';
import NewWindow, { INewWindowProps } from 'react-new-window';

const subscriber = new Map<string, Document['head']>();

const styles = new Set<HTMLStyleElement>();

const addStyle = (style: HTMLStyleElement) => {
  styles.add(style);
  Array.from(subscriber.values()).forEach(head => {
    head.appendChild(style.cloneNode(true));
  });
};

/**
 *  Use this if a popup mounts and there are un-copied styles present
 *  onOpen={window => {
 *      subscriber.set(id, window.document.head);
 *      copyStyle(window.document.head);
 *      //...
 *  }
 *
 * This function is kept as a utility to handle situations where previously stored styles
 * haven't been copied to the popup.
 * For now, in my project, it's not being used since the existing styles are properly loaded into the popup.
 */

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const copyStyle = (target: Document['head']) => {
  styles.forEach(style => target.appendChild(style.cloneNode(true)));
};

const mutationObserver = new MutationObserver(mutationList => {
  mutationList.forEach(({ addedNodes, removedNodes }) => {
    [...addedNodes].forEach(node => {
      if (!(node instanceof HTMLStyleElement)) return;
      addStyle(node);
    });
    [...removedNodes].forEach(node => {
      if (!(node instanceof HTMLStyleElement)) return;
      styles.has(node) && styles.delete(node);
    });
  });
});

mutationObserver.observe(document.head, { childList: true });

export default function NewWindowPopup({ onOpen, ...rest }: INewWindowProps) {
  const id = useId();

  useEffect(() => {
    return () => {
      subscriber.delete(id);
    };
  }, []);

  return (
    <NewWindow
      onOpen={window => {
        if (!subscriber.has(id)) subscriber.set(id, window.document.head);
        onOpen?.(window);
      }}
      {...rest}
    />
  );
}

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

5 participants