Support for using Widgets inside Shadow DOM encapsulated web components.

Are there any plans to add support for using the widgets inside Shadow DOM encapsulated web components?

I have done some experiments with trying to used these widgets inside a framework like Stencil.js. If none of the parent elements use Shadow DOM, everything works well. However, some of the styling of elements is off if you move the style block inside of the shadow root.

Here is the short list of what I would think would help with building modular progressive web applications:

  1. More modular ES2015 or ES2017 EcmaScript that can be tree shaken using Rollup.js or WebPack.
  2. More modular styling broken down by widget. Ideally, there would also be Sass or Less versions of this in the deployment.

The idea is to only load the JS and CSS we need for the components we use and only when we use them for the first time. This would allow us to build true progressive Web Applications (PWA) with minimum payload sizes.

It looks like in recent versions, you are moving in this direction, but the deployment seems to only contain the monolithic JS and CSS at this point. I am still evaluating your library and may be wrong about this though. Please forgive me if I am missing something obvious.

If you like, I can build a simple Web component with Shadow DOM to demonstrate some of the styling and modularity problems.

In the long run, it would be great to provide web component versions of these widgets as well, but I would be happy with more modularity for now.

I have added a couple of crude code snippets that demonstrates the problem at

switch: https://snippet.webix.com/n5f3qxj5

datatable: https://snippet.webix.com/3e5jbd17

OK, I have a little additional information this morning. It seems the styling problems on the datatable are related to the injection of styles into the head section of the document. For example:

<style type="text/css" media="screen,print">
   #datatable1540471118828 .webix_cell { height:36px; line-height:36px; }
   #datatable1540471118828 .webix_hcell { height:42px; line-height:42px;}
   #datatable1540471118971 .webix_cell { height:36px; line-height:36px; }
   #datatable1540471118971 .webix_hcell { height:42px; line-height:42px;}
   #datatable1540471119014 .webix_cell { height:36px; line-height:36px; }
   #datatable1540471119014 .webix_hcell { height:42px; line-height:42px;}</style>

Of course, this would all be ignored by an element in the shadow DOM. Is there a function we can monkey patch to inject these styles into a style block within the shadow root?

As a proof of concept, I modified the datatable snippet to specify these styles within the Shadow DOM.
https://snippet.webix.com/g2lmoxx8

The function that finds the document root currently could use the following code to get the DocumentOrShadowRoot.

const root = container.getRootNode();

See https://developer.mozilla.org/en-US/docs/Web/API/Node/getRootNode

This would sometimes return the document and sometimes return a shadow root. In the event it returns the document, you could look for the style block in the document.head. In the event it returns the shadow root, you could look for or add the style block directly under it.

Here is a proof of concept that will log the document root and last style block for both Shadow DOM encapsulated elements and elements without shadow DOM: https://snippet.webix.com/0a2r4klk

There are still problems with some widget behavior (switch and checkbox can’t toggle, etc.), but at least this should help on the styling issues.

Chrome, Safari, Firefox 63+ and Opera all support custom elements and Shadow DOM at this point. MS Edge is working on it. And, of course, there are polyfills for the majority of this functionality. It would be great to be able to use Webix widgets in this way.

Thanks,
Neal

More modular ES2015 or ES2017 EcmaScript that can be tree shaken using Rollup.js or WebPack.

Webix 6.0 delivers this feature.

More modular styling broken down by widget.

Not here yet. And will not appear till version 7.0
To be clear, there is no guarantee that it will be included in 7.0. We are moving to that direction, still, the feature has a low priority.

Current toolchain allows to manually exclude some css files from the final package, though. Check

to build true progressive Web Applications (PWA) with minimum payload sizes.

As far as I can see, current codebase do not prevent building PWA in any way.
Starting from 6.0 you can exclude the unwanted widgets from the bundle, decreasing the size of the js file.

It seems the styling problems on the datatable are related to the injection of styles

Yep, datatable and spreadsheet generate their custom styles. They are using webix.html.addStyle API. If you are building lib from source, you can change the dependency and provide custom version of addStyle API, which will generate styles inside of the Shadow DOM root.

The main reason why we are not providing a solution for custom elements is the sizing.

Webix UI calculates sizes of elements on its own. It allows to size elements to data and uses flexible layouts in older browsers, and in the same time doesn’t allow to use custom elements effectively. When a widget is wrapped inside of custom element it is not aware of its surrounding and cant detect when a size of a custom element is changed. Which means the inner content will not resize on custom element change.

Also, it makes all layout components ineffective, as they are not aware of child views and can’t provide the correct sizes for child components.

As a long-term goal, we plan to introduce the second sizing strategy for all component. So they will be able to size by sizes defined in JS code ( as they do now ) and, alternatively, they will be able to size self-based on CSS rules and HTML flaw as normal HTML content does. When such sizing mode will work for all components, it is rather easy to wrap widgets in the custom element ( or React/Vue/Angular views )

About the broken click handlers inside of shadow DOM, this is caused by a global mouse handler. Currently, all inputs have a single click handler, which attached to document.body. Of course, it will not work with custom elements inside of shadow DOM.

It is rather easy to change the code and use handlers directly on a DOM node, so this issue is not a stopper, still, while the sizing problem is not resolved, the custom elements with webix widgets inside will be painful to use.

Hi Maksim,

You sent me the proper webix.d.ts file and I can now consume the library from our Rollup configuration and get the tree shaking. Works great, thanks!

Thanks for the thorough write-up and consideration of what would need to change to support shadow DOM encapsulation of the Webix widgets. As you have said, we can get more granular widget styling from the Less files in the source. Stencil.js uses Rollup.js internally and there is a plugin for less that we can use in this case.

For resize tracking, we are using the ResizeObserver that is supported in some browsers and add the resize-observer-pollyfill at https://www.npmjs.com/package/resize-observer-polyfill for the browsers that don’t. This gives us native platform resize tracking in Chrome and support based on MutationObserver in other browsers. Even IE11 supports mutation observer. The polyfill will be needed less and less as the other browsers provide support. See https://www.caniuse.com/#search=resizeobserver. The ResizeObserver can track the side of the element itself as well as the size of child elements inside the shadow root if needed.

As you have stated, the Webix widgets work well inside custom elements as long as we avoid the shadow DOM. However, if some other container component uses the shadow DOM further up the tree, the components will break for reasons already discussed.

This page describes composed events and events bubbling past the shadow root https://developer.mozilla.org/en-US/docs/Web/API/Event/composed. As you can see, “click” and other mouse events should be composed and pass through the Shadow DOM boundary. However, the element that raised the event (target) will be the custom element and not the inner element that was clicked. However, since we are using an “open” shadow root, the document level event handler could potentially use the e.composedPath() method to get the element that raised the event, even within the shadow root.

Regards,
Neal