feat!: Allow using Blockly in web components/shadow DOM#9611
feat!: Allow using Blockly in web components/shadow DOM#9611
Conversation
maribethb
left a comment
There was a problem hiding this comment.
I don't know enough about web components so this seems plausible enough, but a couple questions.
| if ( | ||
| typeof window === 'undefined' || | ||
| !window.CSSStyleSheet || | ||
| injectionSites.has(root) |
There was a problem hiding this comment.
will this return early now if the theme changes? i think the previous code allowed you to re-inject if the theme changed? not super familiar with this code though so could be misreading
There was a problem hiding this comment.
Great catch, yes! Updated it to be keyed by selector, so it only injects once per theme, and confirmed that fixes it.
| injected = true; | ||
| if (!hasCss) { | ||
| export function inject( | ||
| container: HTMLElement, |
There was a problem hiding this comment.
again not too familiar with this code, but is it possible to have container be optional and the default value be getParentContainer?
There was a problem hiding this comment.
We could, but this is only called from Blockly.inject(), and is explicitly a no-op if called again. At the point Blockly.inject() calls it, the workspace doesn't know its injection div yet, so getParentContainer() wouldn't return it.
The basics
The details
Resolves
Fixes #1114
Proposed Changes
This PR adds support for using Blockly inside of the shadow DOM and web components.
Largely, this is a matter of injecting CSS into the document or shadow DOM root as appropriate, rather than creating
<style>tags and injecting them into<head>. Additionally, the global floaty elements (tooltips, input fields, widget div, dropdown div) needed some adjustments to their positioning logic; they had generally all been assuming that they lived in a global div at the root of the document, but this was already not necessarily the case. We've allowed specifying a parent element for these, but if that had bounds smaller than the document things could get wonky. Now, the parent container defaults to returning the injection div unless another value is specified, so these elements are all contained within that (and the shadow DOM/web component, if Blockly is used in one), and that element's position in the page is taken into account when determining the bounds for tooltips, dropdowns, etc.I also added an additional playground/test harness (with LLM assistance) that embeds Blockly inside a
<div>as usual, but also defines a web component and uses that to embed a separate instance on the same page.Reason for Changes
This has been a long-standing request, and web components have become more widespread and popular. We also have some partner applications that could benefit from this.
Breaking Changes
getParentContainer()returns the injection div if another element has not been specified. This behavior is likely fine, but if you were using this method you should either explicitly set the container you want or verify that your use is compatible with the injection div.Blockly.Css.inject()requires that the element into which Blockly/the CSS should be injected be specified.tagNameargument has been removed fromConstantProvider.createDom()andConstantProvider.injectCss_(), since CSS is no longer added via a<style>tag. If you have a custom renderer orConstantProvideror call these methods, ensure that thetagNameargument is removed from your implementation/calls.