lukeed / navaid

A navigation aid (aka, router) for the browser in 850 bytes~!
MIT License
781 stars 26 forks source link

Support for shadow dom #30

Open isidrok opened 4 years ago

isidrok commented 4 years ago

Intercepting clicks made on anchors inside shadow roots looking for the anchor closest to the event target doesn't work since events are re-targeted to the shadow root host element.

The anchor can be found in the event path with the following code:

function findA(e){
  var i = 0, p = e.path || e.composedPath && e.composedPath(), a = p && p[i];
  while(a && a.tagName !== 'A') a = p[++i];
  return a;
}

Note that this will only work for shadow roots configured with mode set to open since elements inside closed ones don't appear in the event path.

Can submit a PR if you agree with the proposed change

lukeed commented 4 years ago

Hmm... isn't the point of Shadow DOM is to hide it away from the parent container? I don't use WC but iirc, you have to use mode: 'open' and manually proxy/bubble events should you want them to be accessed.

I'll have to think about this for now. Will open it up for feedback from others who've hopefully had more experience. Thanks for the suggestion!

isidrok commented 4 years ago

Yes, the point is encapsulation but that doesn't mean event delegation should not work.

It is true that you need to properly configure events your components manually dispatch if you want them to exit shadow root boundaries composed:true and some native events won't cross shadow root boundaries but most of them do.

You can read more about it here https://developers.google.com/web/fundamentals/web-components/shadowdom#events

Thanks for considering.

Edit: a fallback may be needed for browsers that don't support event.path or event.composedPath (IE and old Edge that I'm aware of) the good thing is since these browsers don't support custom elements event re-targetting should not happen and event.target.closest('a') should work.

tomg7 commented 4 years ago

Please consider implementing this feature as navaid does not work with Shadow DOM (and libraries like LitElement that uses Shadow DOM by default) without it.

I added @isidrok's code to navaid's click function as var x = e.target.closest('a') || findA(e) and tested it in one of my LitElement based projects where I would like to replace Page.js with navaid. It works fine for me.

You might also want to take a look at the relevant pull request from Page.js .

I think this feature would be really useful as Shadow DOM is supported in all modern browsers and its usage is rising.

Thanks

ba55ie commented 3 years ago

Yes please! :)

It's hard to find a simple and robust router that supports Shadow DOM (Web Components) and navaid seems to tick all the boxes.

By adding a few lines of code navaid will support a current web standard and make ~a lot of~ some developers happy. ;)

zerodevx commented 2 years ago

FWIW, I'll maintain a forked version that supports shadow-dom anchor links, where the only change is from this:

https://github.com/lukeed/navaid/blob/b2b105abcf6c465621710c55e66117bf8f020b68/src/index.js#L53

to this:

var x = e.composedPath, x = e.path || x && x() || [e.target], 
  x = x[0].closest('a'), y = x && x.getAttribute('href');

Install with:

$ npm i @zerodevx/navaid