[v6/v5]: Is it possible to support shadow the use of the Bootstrap Javascript plugins within the context of the shadow DOM?
Prerequisites
- [X] I have searched for duplicate or closed feature requests
- [X] I have read the contributing guidelines
Proposal
OK I've had a look at the issue register and there have been requests to add shadow DOM support in different capacities, ways, shapes and forms if you would like to call it, for example:
- https://github.com/twbs/bootstrap/issues/14200
- https://github.com/twbs/bootstrap/issues/24918
- https://github.com/twbs/bootstrap/issues/28131
Yes all of them have been closed but with each of those examples above, they have been closed at least 2 years ago and now with support for various technologies such as web components and shadow DOM improving and considering not even sure if v6 is in an early enough stage of development (or if some little tweaks are possible in the context of version 5), I would like to request a feature that allows us to at least have the Javascript plugins supported in the shadow DOM from the onset.
This can be either writing web components that support shadow DOM, or using what Bootstrap has been using, but with shadow DOM in mind or even something we can pass in the config to allow us to change the context of DOM selection to allow us to use the shadowRoot selectors.
Motivation and context will be provided below to give a better understanding, which hopefully forms part of the justification.
Motivation and context
Currently I'm on a project that is using a different UI framework for an app. As part of a UI rebuild we are doing for that app, we are looking to progressively migrating our old UI components/elements to use a new framework that is based heavily on bootstrap as a dependency.
In order for our components to co-exist with the old framework while the migration is going on, we had to resort to using shadow DOM to fence off areas using the old UI vs areas using the new UI.
We sort of are in our initial stages, in this process and just trying out a few things to see how far we can go and see what problems we can encounter.
So far the CSS side has now been resolved and we can use Bootstrap's CSS with the shadow DOM and the REM font sizes will not get affected by the root element using the old framework.
On the Javascript side however, it doesn't seem as straight forward to use the current v5 bootstrap JS plugins.
It seems like many of them still seem to rely on us to add additional event handling ourselves (which may end up not being like for like), and since most of the DOM selection seem to be based on the document scope. Furthermore because of the document scope, some of that code would be kinda dead in the shadow DOM, called to do essentially nothing.
I have a branch of a repo that I used to create the simple initialisation of the Collapse, using javascript to instantiate the collapse instance. (Clone https://github.com/classicmike/stencil-bs-collapse-modules-bug.git and checkout shadowdom-experiment, run npm install then npm run start:dev.
As you can see, none of the simple things such as clicking to toggle collapse works and even the DOM selection methods such as the triggers return empty arrays. Hopefully I haven't caused any bugs.
Then there's also issues like this where the modal hard codes, where the modal seems to have been hard-coded to use the document scope which makes launching modals without any CSS issues impossible. This link should be enough to demonstrate issue: https://github.com/twbs/bootstrap/issues/36918.
Furthermore, there are hints where methods or referenced issues and have been use to support a shadowRoot on a case-by-case basis such as:
-
findShadowRoot()method for https://github.com/twbs/bootstrap/issues/24918
So there seems to be some precedent already for Shadow DOM's usage.
Now it's been around 5 years since the last reply on issues related to supporting web components: https://github.com/twbs/bootstrap/issues/28131 and even then feedback seems quite strong and the support for using it is getting better, it might be a good time to re explore this issue and anything related.
Even if the use of web components may be perceived as a 'step too far' here for v6 or v5 for that matter, I would like to see at the minimum, consideration for the shadow DOM context in the javascript plugins so we can use them in use cases like I have explained above.
Hopefully this request is a reasonable one and would be interested to hear what the position would be going forward.
Thanks.
One is what was mentioned by the author of the issue, which is good coverage, other is existing issue, that does impact certain areas.
Variables defined currently are being applied only to :root selector and two themes light/dark. The exception is that dark in most of the case if not all overrides only colors, because it is expecting to fallback onto :root selector. Issue is that inside ShadowDOM there is no :root selector and thus majority of variables once one does switch theme to dark become undefined and it is braking design.
Some applications where there is more than one team working at it absolutely must use ShadowDOM to isolate one part from another to avoid style collisions and unexpected behavior. But if bootstrap is used in such application, then most likely it will be imported according to need and that would mean, that it will be under ShadowDOM, thus :root selector will not be available and dark theme will be broken, unless locally there will be a workaround introduced (how I solve it currently).
Would be nice though if it was solved on Bootstrap level. IMO it can easily be solved by defining variables for all main identifiers and overriding for specific ones.
So in majority cases there are 3 main identifiers for defining variables :root, [data-bs-theme="light"] and [data-bs-theme="dark"] selectors.
What I would do is define variables under all of them and in case of a dark theme override if dark theme was enabled. IMO this is the most basic solution.
To solve that :root variable problem. If you re-inject a stylesheet with :root definitions in the shadow DOM you must replace :root with :host. And then the variables get picked up correctly.
cssContent.replace(/:root([^\w\-_])/g, ':host$1')
I have this piece of code where I re-inject all my <style> and <link rel="stylesheet" href="..."> into a shadow while replacing :root with :host
const shadowAdoptCss = (shadow, cssText) => {
const sheet = new CSSStyleSheet()
sheet.replaceSync(cssText.replace(/:root([^\w\-_])/g, ':host$1'))
shadow.adoptedStyleSheets.push(sheet)
}
const shadow = element.attachShadow({mode: 'open'})
document.querySelectorAll('style').forEach(styleEl => {
shadowAdoptCss(shadow, styleEl.textContent)
})
document.querySelectorAll('link[rel="stylesheet"]').forEach(linkStyle => {
fetch(linkStyle.href)
.then(response => response.text())
.then(cssContent => {
shadowAdoptCss(shadow, cssContent)
})
})
Actually in my application I render Bootstrap 5 elements with a different design and different colors in a shadow on an older page which is still Bootstrap 4. And the <style> and <link> elements I am copying don't come from the parent document but from an ajax call to a new page with a new design, but this is just an example to show it's not that difficult to make css :root work in a shadow DOM
But that does not solve the javascript issues offcourse. In the end I stopped trying because I had no tooltips and other js related functionality in my shadow DOM's because Bootstrap's js queries don't go beyond the shadow boundaries and don't find the elements inside