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

ViewEncapsulation.ShadowDom breaks components #9471

Closed
irekBudzowski opened this issue Oct 29, 2020 · 34 comments
Closed

ViewEncapsulation.ShadowDom breaks components #9471

irekBudzowski opened this issue Oct 29, 2020 · 34 comments
Labels
Status: Pending Review Issue or pull request is being reviewed by Core Team

Comments

@irekBudzowski
Copy link

I'm submitting a ... (check one with "x")

[x] bug report => Search github for a similar issue or PR before submitting
[ ] feature request => Please check if request is not on the roadmap already https://github.com/primefaces/primeng/wiki/Roadmap
[ ] support request => Please do not submit support request here, instead see http://forum.primefaces.org/viewforum.php?f=35

Plunkr Case (Bug Reports)
https://stackblitz.com/edit/primeng-autocomplete-demo-aha9nr

Current behavior
Enabling ViewEncapsulation.ShadowDom in Angular's component, embedded prime's component doesn't work.

Expected behavior

ViewEncapsulation.ShadowDom in Angular's component should not break embedded prime component.

Minimal reproduction of the problem with instructions

Use ViewEncapsulation.ShadowDom in components with embedded prime's component.

What is the motivation / use case for changing the behavior?

Possibility to use the library in microfrontends, where using ShadowDom is crucial.

  • Angular version: 5.X

Angular version 10.X

  • PrimeNG version: 5.X

PrimeNg version 10.X

@AKoempel
Copy link

AKoempel commented Nov 10, 2020

Same problem here. We want to use PrimeNG components inside of web components and the ViewEncapsulation.ShadowDom indeed breaks most components. I just had a deeper look into the calendar. Two functions of the calendar are problematic:

  • Calendar::bindDocumentClickListener: Clicks caught outside of the shadow dom always have the shadow root as event target to hide implementation details -> All clicks are considered to be outside of the calendar.
  • Calendar::bindScrollListener: DomHandler::getScrollableParents fails executing getComputedStyle on the shadow root.

The only solution for me right now would be to fork PrimeNG and fix it or create a directive that overrides those functions of the component.

@irekBudzowski
Copy link
Author

Thank you @AKoempel. I will try to solve that with directive, as you proposed. Also with some components, I will migrate to material, they updated js recently and it works like a harm! Unless, of course, PrimeFaces will upgrade js in the library too.

@yigitfindikli yigitfindikli added the Status: Pending Review Issue or pull request is being reviewed by Core Team label Nov 25, 2020
@TiagoM-Leite
Copy link

We're also using ViewEncapsulation.ShadowDom and, in our case, it was the behavior of any overlay panel. It appeared and disapeared right away when the user clicked on the component to show (e.g. a user clicks in a multiselect, options appear and disapear very quickly). As @AKoempel said, we ended up overidding "isOutsideClicked(event)" function in primeng-multiselect.js and primeng-dropdown.js

@TiagoM-Leite
Copy link

TiagoM-Leite commented Feb 22, 2021

Also, since v. 11.2.1, it's breaking with the following error:
"Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'"

The root cause seems to be this commit:
297beb0

For now, we're leaving on v. 11.2.0 (on which we had to create directives to override problematic functions in the component).

We're also planning to leave primeng in the near future if these issues persist, since we have to use shaddow dom and clearly primeng isn't compatible with it.

@raymatos
Copy link

Is this still an issue?

@maitrungduc1410
Copy link

maitrungduc1410 commented Jul 22, 2021

@raymatos yes, it's still an issue, I'm having the issue for all components has something "dropdown": Dropdown, Calendar, tooltip,...

@maitrungduc1410
Copy link

maitrungduc1410 commented Jul 23, 2021

Here's what I found (with solution below): the issue caused by this line:

https://github.com/primefaces/primeng/blob/master/src/app/components/dom/domhandler.ts#L175

PrimeNg uses window['getComputedStyle'](node, null), and in the case of ShadowDOM it'll throw error because node (which is passed to overflowCheck) is not normal HTML Element

I manage to solve this issue, not much effort:

// from this
let parents = this.getParents(element);

// to this
let parents = this.getParents(element).filter(item => !(item instanceof ShadowRoot));

Explain: we filter out the shadow root when getting parents of element, so later params which is passed to overflowCheck will always be an HTML Element

  • Step 3: create a directive to override a behaviour of PrimeNG:
@Directive({
  selector: '[bindScrollListener]',
})
export class bindScrollListenerDirective {
  constructor(
    @Host() @Self() @Optional() public hostSel: Dropdown) {

    // Nothing is changed here, I just simply use ConnectedOverlayScrollHandler from the 2 files we downloaded
    hostSel.bindScrollListener = () => {
      if (!hostSel.scrollHandler) {
        hostSel.scrollHandler = new ConnectedOverlayScrollHandler(hostSel.containerViewChild.nativeElement, (event: any) => {
          if (hostSel.overlayVisible) {
            hostSel.hide();
          }
        });
      }

      hostSel.scrollHandler.bindScrollListener();
    }

    // override PrimeNG to make it support ShadowDOM
    hostSel.isOutsideClicked = (event: any) => {
      const target = event.target.shadowRoot ? event.path[0] : event.target
      return !(this.hostSel.el.nativeElement.isSameNode(target) || this.hostSel.el.nativeElement.contains(target) || (this.hostSel.overlay && this.hostSel.overlay.contains(<Node> target)));
    }
  }
}

Finally register the directive in declarations in app.module and start using your component as usual:

<p-dropdown bindScrollListener ....>

What I actually changed:

hostSel.isOutsideClicked = (event: any) => {
  const target = event.target.shadowRoot ? event.path[0] : event.target
  return !(this.hostSel.el.nativeElement.isSameNode(target) || this.hostSel.el.nativeElement.contains(target) || (this.hostSel.overlay && this.hostSel.overlay.contains(<Node> target)));
}

P/s: for each component that has this issue you need to create a directive for it (above I use dropdown)


Hi @cagataycivici, I don't know if you aware of this issue, but if this one is fixed it'll make PrimeNg much better cuz it can run in all kind of encapsulation environments that Angular has. Also Shadow DOM is a common case when developers want to embed their Angular components into any other web app.

If my solution is valid, I'd happy to create a PR for that

@maitrungduc1410
Copy link

Hi @yigitfindikli If you ever have time please help to look at this issue. Thank you

@maitrungduc1410
Copy link

For anyone is having this issue, I've published a small lib with directives that fix this problem: https://github.com/maitrungduc1410/primeng-shadowdom-directives

@ContrRus
Copy link

For anyone is having this issue, I've published a small lib with directives that fix this problem: https://github.com/maitrungduc1410/primeng-shadowdom-directives

Hi @maitrungduc1410, thank you, I was waiting for some update here, I have a problem with filters in datatable, the <p-columnFilter "> element. In the library that you provided is there any solution for this element?

@maitrungduc1410
Copy link

For anyone is having this issue, I've published a small lib with directives that fix this problem: https://github.com/maitrungduc1410/primeng-shadowdom-directives

Hi @maitrungduc1410, thank you, I was waiting for some update here, I have a problem with filters in datatable, the <p-columnFilter "> element. In the library that you provided is there any solution for this element?

It's PrimeNg table right?

@ContrRus
Copy link

For anyone is having this issue, I've published a small lib with directives that fix this problem: https://github.com/maitrungduc1410/primeng-shadowdom-directives

Hi @maitrungduc1410, thank you, I was waiting for some update here, I have a problem with filters in datatable, the <p-columnFilter "> element. In the library that you provided is there any solution for this element?

It's PrimeNg table right?

Yeah of course, below is my question for this issue on stackoverflow
https://stackoverflow.com/questions/69117441/viewencapsulation-shadowdom-and-primeng-not-all-styles-are-presented

@maitrungduc1410
Copy link

maitrungduc1410 commented Sep 22, 2021

okay I'll update to support for that too (if it's possible :D)

@maitrungduc1410
Copy link

Hi @ContrRus , I just looked into the question you posted on StackOverflow, it seems not related to this Github issue, I posted my answer for your question

@Vishnuram1988
Copy link

Hello Team,

@yigitfindikli , Would be really great if we could get the fix for this in upcoming primeng version. I think it is quite relevant now as we use micro front end architecture and we may need to integrate the angular micro front ends to existing web application as remote modules with shadow DOM encapsulation

@shahafm-netapp
Copy link

@yigitfindikli

the issue opened at Oct 29, 2020, more than a year ago, is there any ETA ?

@dmluzhin
Copy link

Having the same problem in our team's project. Really waiting for this fix

@vanroda
Copy link

vanroda commented Nov 15, 2021

@maitrungduc1410, thanks for the directives. Is it possible to add one for ConfirmPopup ?
I tried based on psdTooltipDirective but didn't work. Thank you

@maitrungduc1410
Copy link

@vanroda okay, I'll check and let you know

@maitrungduc1410
Copy link

maitrungduc1410 commented Nov 16, 2021

Hi @vanroda, please upgrade to version 0.0.5, I added support for confirm popup component

@leopardy
Copy link

I really wish this would be fixed in the newer version of primeng. It's been over a year. I too have my components break once I switch to ViewEncapsaulation.ShadowDom.
In the meantime, thank you @maitrungduc1410 for providing us some type of workaround. Could you tell me if your lib of directives is going to get DataView?
https://www.primefaces.org/primeng/showcase/#/dataview
Our grid layout breaks and displays our items vertically. Also, the p-paginator that comes as part of the DataView also breaks, we lose the p-paginator-first, p-paginator-prev, p-paginator-next, and p-paginator-last arrows.

@maitrungduc1410
Copy link

maitrungduc1410 commented Dec 11, 2021

@leopardy this Github issue only happens to "overlay" components, in your case there's no such component usage in DataView, I did check, and it works fine inside ShadowDOM. I think you probably import css incorrectly

Remember that when working with ShadowDOM, global style defined in styles.css won't work. Also beware of using :ng-deep it doesn't work well with ShadowDOM, don't use :ng-deep in this case.

The way it works is you have to place css import in any component which is ShadowDOM encapsulated. Example:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: [
    './app.component.scss',

    // ---------------- IMPORT PRIMENG STYLE HERE
    "../../../../node_modules/primeicons/primeicons.css",
    "../../../../node_modules/primeng/resources/themes/saga-blue/theme.css",
    "../../../../node_modules/primeng/resources/primeng.min.css"
  ],
  encapsulation: ViewEncapsulation.ShadowDom
})
export class AppComponent {

@Cichy3D
Copy link

Cichy3D commented Apr 25, 2022

Answer removed.

@cmyk1
Copy link

cmyk1 commented Jun 9, 2022

Still have the same issue within the webcomponents? @cagataycivici

@drekthral
Copy link

Still not working in shadow dom. >﹏<

@cagataycivici
Copy link
Member

Sorry right now, ShadowDOM is not supported and I'm not sure how it can be right now. Any ideas are appreciated considering the current design agnostic theming approach of PrimeNG.

For web components though, we'll create PrimeElements aka PrimeUI with lit or similar which will have a styling architecture suited for WC. PrimeNG supports NONE and EMULATED for now.

@drekthral
Copy link

Sorry right now, ShadowDOM is not supported and I'm not sure how it can be right now. Any ideas are appreciated considering the current design agnostic theming approach of PrimeNG.

For web components though, we'll create PrimeElements aka PrimeUI with lit or similar which will have a styling architecture suited for WC. PrimeNG supports NONE and EMULATED for now.

Any rough estimate when PrimeElements could be created?

@papb
Copy link
Contributor

papb commented Aug 18, 2022

Any ideas are appreciated considering the current design agnostic theming approach of PrimeNG.

@cagataycivici What do you think of the approach by @maitrungduc1410 ?

If my solution is valid, I'd happy to create a PR for that

As I understand, the root problem is with "overlay" behaviors of components. Other than that, simply using styleUrls in the component that has encapsulation: ViewEncapsulation.ShadowDom seems to be enough. However, of course overlays are a big part of primeng, so that needs fixing too, and that's what @maitrungduc1410 has done.

A quick guess from me (I haven't really looked well into how @maitrungduc1410 did it): solving this is probably just a matter of being careful to create all overlay elements also inside the shadow root, rather than appending document.body directly.

Thank you all!! This package is awesome 🚀

@papb
Copy link
Contributor

papb commented Aug 18, 2022

For my case, the following patch seems to be enough for now:

import { DomHandler } from 'primeng/dom';

// Patch it to use `.parentElement` instead of `.parentNode`.
// See https://github.com/primefaces/primeng/issues/9471
// See https://github.com/primefaces/primeng/blob/ddcc166fe48d336012f2dfa9a2deb6af08f98593/src/app/components/dom/domhandler.ts#L164-L166
DomHandler.getParents = function(element, parents = []) {
  const parent = element.parentElement;
  return parent ? this.getParents(parent, [...parents, parent]) : parents;
};

Many thanks to @maitrungduc1410!

@fabiocastagnino
Copy link

the solution proposed seems good, can you apply it? can you share the ETA?

@cagataycivici
Copy link
Member

I'll discuss it with the team this week and respond. PrimeUI begins in december.

@mertsincan
Copy link
Member

Hi,

So sorry for the delayed response! Improvements have been made to many components recently, both in terms of performance and enhancement. Therefore, this improvement may have been developed in another issue ticket without realizing it. You can check this in the documentation. If there is no improvement on this, can you reopen the issue so we can include it in our roadmap?
Please don't forget to add your feedback as a comment after reopening the issue. These will be taken into account by us and will contribute to the development of this feature. Thanks a lot for your understanding!

Best Regards,

@tfuerholzer
Copy link

tfuerholzer commented Feb 3, 2023

In case anyone else lands here I found a solution specifically for using a <p-table> with <p-columnfilter> inside a shadow DOM :

ColumnFilter.prototype.onOverlayAnimationStart = patchedOnOverlayStart
...

function patchedOnOverlayStart(event){
  switch (event.toState) {
    case 'visible':
      this.overlay = event.element;

      const sdr = document.body.querySelector('my-shadow-dom-component').shadowRoot
      // get an instance of your shadowRoot
      sdr.appendChild(this.overlay);
      ZIndexUtils.set('overlay', this.overlay, this.config.zIndex.overlay);
      DomHandler.absolutePosition(this.overlay, this.icon.nativeElement);
      this.bindDocumentClickListener();
      this.bindDocumentResizeListener();
      this.bindScrollListener();

      this.overlayEventListener = (e) => {
        if (this.overlay && this.overlay.contains(e.target)) {
          this.selfClick = true;
        }
      };

      this.overlaySubscription = this.overlayService.clickObservable.subscribe(this.overlayEventListener);
      break;

    case 'void':
      this.onOverlayHide();

      if (this.overlaySubscription) {
        this.overlaySubscription.unsubscribe();
      }
      break;
  }
}

Basically I override the onOverlayAnimationStart method which adds the Filter to the DOM to use my shadow DOM root instead of the root document.

The code otherwise the same as https://github.com/primefaces/primeng/blob/master/src/app/components/table/table.ts#L4611

Also keep in mind that when you are using this inside a module federated frontend or something similar to revert the override once finished, might break stuff for others otherwise.

@aserec
Copy link

aserec commented Dec 12, 2023

The issue persists on version 17.0.0. In my case, it opens the calendar, but It cannot be interacted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Pending Review Issue or pull request is being reviewed by Core Team
Projects
None yet
Development

No branches or pull requests