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

make bodyContains() handle possibly nested shadow root's #2532

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from

Conversation

spez
Copy link

@spez spez commented May 5, 2024

Description

make bodyContains() handle the case where elt might be inside nested shadow dom's

Testing

Tested manually with and without nested shadow dom's

Checklist

  • I have read the contribution guidelines
  • I have targeted this PR against the correct branch (master for website changes, dev for
    source changes)
  • This is either a bugfix, a documentation update, or a new feature that has been explicitly
    approved via an issue
  • I ran the test suite locally (npm run test) and verified that it succeeded

@1cg
Copy link
Contributor

1cg commented May 15, 2024

no opinion on this @kgscialdone do you have one?

Definitely needs tests if it is to be accepted though.

@kgscialdone
Copy link
Contributor

This is... acceptable. Recursive checks like this are gross but there isn't really a better way.

@innovation-stack
Copy link

innovation-stack commented Jun 4, 2024

@kgscialdone , @1cg - I propose to solve this via htmx extension. That way its up to the user to iterate and fetch the host node.

function getRootNode(elt) {
    // custom logic to iterate nodes and return host
    const node = /* ... */
    return node;
}

htmx.defineExtension("body-contains-elem", {
    bodyContains: function (elt) {
       const node = getRootNode(elt);
       return document.body.contains(node);
    }
})

The revised bodyContains would be:

function bodyContains(elt) {
    let exists = undefined;
    
    // process extension
    withExtensions(asElement(elt), function (extension) {
        exists = extension.bodyContains(elt);
    })

    if (exists === true || exists === false) {
        return exists;
    }

    // original implementation
    const rootNode = elt.getRootNode && elt.getRootNode();
    if (rootNode && rootNode instanceof ShadowRoot) {
        return getDocument().body.contains(rootNode.host);
    } else {
        return getDocument().body.contains(elt);
    }  
}

Let me know if this solution is acceptable, I can raise PR for it.

@kgscialdone
Copy link
Contributor

@innovation-stack I'm unclear why solving this with an extension would be preferable to simply solving it, even if the solution is a little gross. Could you elaborate on why?

@innovation-stack
Copy link

innovation-stack commented Jun 5, 2024

@kgscialdone - For performance reasons.
In my case, I am building a query-builder web component, which can be contain deeply nested components.
Here's an illustration:

deeply-nested-wcs

Few notes:

  1. Card component can be deeply nested, upto any level of hierarchy
  2. In my query-builder, any deeply nested child component can refer its root via builder property. I want to leverage this fact as part of bodyContains implementation.
  3. As per solution proposed in PR, calling bodyContains on any deeply nested child web component have to iterate over all parent web components, which will incur performance. bodyContains is being called multiple times during render and API calls.

Proposed solution:

  1. Using body-contains-elem extension, I can completely bypass all parent node traversals and simply use the builder property to refer to root component.

Let me know your thoughts on this.
Thank you.

@kgscialdone
Copy link
Contributor

kgscialdone commented Jun 5, 2024

@innovation-stack I see where you're coming from, thank you for the explanation. However, I don't think that relegating a fix for this to extensions is the right approach from a logistical point of view, as it puts the impetus on developers using HTMX to handle the problem rather than on HTMX itself.

That said, on further inspection of the use cases of bodyContains, it may be worth looking into whether it actually needs to be "does the <body> contain this element", or if all use cases would be fine also including elements in the <head>. I can't determine that offhand at the moment, so will need to inquire of @1cg for more information, but if it's the case that it doesn't matter whether it's in the <body> or the <head> in all use cases, the whole function could be replaced with the following and avoid the whole issue:

function documentContains(elt) {
  return elt.getRootNode({ composed: true }) === getDocument()
}

@innovation-stack
Copy link

@kgscialdone : Thank you for the explanation. I agree with your solution. Its elegant.
Let me know what's the final verdict on the approach.

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

Successfully merging this pull request may close these issues.

None yet

4 participants