From 0707ddaf3c9254fc87a52e8ffcea5d3da83e3fbd Mon Sep 17 00:00:00 2001 From: Jochen Kuehner Date: Fri, 15 Dec 2023 10:21:20 +0100 Subject: [PATCH 1/2] feat: add a sample using a webcomponent --- tests/webcomponent.html | 213 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 tests/webcomponent.html diff --git a/tests/webcomponent.html b/tests/webcomponent.html new file mode 100644 index 00000000000..7e282ef7ef8 --- /dev/null +++ b/tests/webcomponent.html @@ -0,0 +1,213 @@ + + + + + + Blockly Playground Webcomponent + + + + + + + + + + + + \ No newline at end of file From 8c0e08b05268392e93f255fc561835228bd10b31 Mon Sep 17 00:00:00 2001 From: jogibear9988 Date: Fri, 15 Dec 2023 11:41:26 +0100 Subject: [PATCH 2/2] feat: use constructible stylesheets for shadowdom --- core/css.ts | 56 ++++++++++++++++++++---------- core/inject.ts | 2 +- core/renderers/common/constants.ts | 9 +++-- tests/webcomponent.html | 13 ++----- 4 files changed, 48 insertions(+), 32 deletions(-) diff --git a/core/css.ts b/core/css.ts index 07e9c98a485..4e71bda7b72 100644 --- a/core/css.ts +++ b/core/css.ts @@ -8,6 +8,7 @@ /** Has CSS already been injected? */ let injected = false; +let styleSheet: CSSStyleSheet; /** * Add some CSS to the blob that will be injected later. Allows optional @@ -33,27 +34,46 @@ export function register(cssContent: string) { * document's responsibility). * @param pathToMedia Path from page to the Blockly media directory. */ -export function inject(hasCss: boolean, pathToMedia: string) { - // Only inject the CSS once. - if (injected) { - return; - } - injected = true; +export function inject(hasCss: boolean, pathToMedia: string, container: Element) { if (!hasCss) { + injected = true; return; } - // Strip off any trailing slash (either Unix or Windows). - const mediaPath = pathToMedia.replace(/[\\/]$/, ''); - const cssContent = content.replace(/<<>>/g, mediaPath); - // Cleanup the collected css content after injecting it to the DOM. - content = ''; - - // Inject CSS tag at start of head. - const cssNode = document.createElement('style'); - cssNode.id = 'blockly-common-style'; - const cssTextNode = document.createTextNode(cssContent); - cssNode.appendChild(cssTextNode); - document.head.insertBefore(cssNode, document.head.firstChild); + + const root = container?.getRootNode() ?? document; + if (root === document) { + // Only inject the CSS once. + if (injected) { + return; + } + injected = true; + + // Strip off any trailing slash (either Unix or Windows). + const mediaPath = pathToMedia.replace(/[\\/]$/, ''); + const cssContent = content.replace(/<<>>/g, mediaPath); + // Cleanup the collected css content after injecting it to the DOM. + content = ''; + + // Inject CSS tag at start of head. + const cssNode = document.createElement('style'); + cssNode.id = 'blockly-common-style'; + const cssTextNode = document.createTextNode(cssContent); + cssNode.appendChild(cssTextNode); + + document.head.insertBefore(cssNode, document.head.firstChild); + } else { + if (!styleSheet) { + // Strip off any trailing slash (either Unix or Windows). + const mediaPath = pathToMedia.replace(/[\\/]$/, ''); + const cssContent = content.replace(/<<>>/g, mediaPath); + // Cleanup the collected css content after injecting it to the DOM. + content = ''; + + styleSheet = new CSSStyleSheet(); + styleSheet.replaceSync(cssContent); + } + ( root).adoptedStyleSheets = [...( root).adoptedStyleSheets, styleSheet]; + } } /** diff --git a/core/inject.ts b/core/inject.ts index b938abaa4e0..72bff381175 100644 --- a/core/inject.ts +++ b/core/inject.ts @@ -91,7 +91,7 @@ function createDom(container: Element, options: Options): SVGElement { container.setAttribute('dir', 'LTR'); // Load CSS. - Css.inject(options.hasCss, options.pathToMedia); + Css.inject(options.hasCss, options.pathToMedia, container); // Build the SVG DOM. /* diff --git a/core/renderers/common/constants.ts b/core/renderers/common/constants.ts index f0acc39aa02..d37e82e4343 100644 --- a/core/renderers/common/constants.ts +++ b/core/renderers/common/constants.ts @@ -1077,7 +1077,9 @@ export class ConstantProvider { * @param tagName The name of the style tag to use. * @param selector The CSS selector to use. */ - protected injectCSS_(tagName: string, selector: string) { + protected injectCSS_(tagName: string, selector: string, svg: SVGElement) { + const root = svg?.getRootNode() ?? document; + const cssArray = this.getCSS_(selector); const cssNodeId = 'blockly-renderer-style-' + tagName; this.cssNode = document.getElementById(cssNodeId) as HTMLStyleElement; @@ -1092,7 +1094,10 @@ export class ConstantProvider { cssNode.id = cssNodeId; const cssTextNode = document.createTextNode(text); cssNode.appendChild(cssTextNode); - document.head.insertBefore(cssNode, document.head.firstChild); + if (root === document) + document.head.insertBefore(cssNode, document.head.firstChild); + else + root.insertBefore(cssNode, root.firstChild); this.cssNode = cssNode; } diff --git a/tests/webcomponent.html b/tests/webcomponent.html index 7e282ef7ef8..53a7205c346 100644 --- a/tests/webcomponent.html +++ b/tests/webcomponent.html @@ -97,6 +97,7 @@ } createBlockly() { + //Blockly.setParentContainer(this.shadowRoot); this.workspace = Blockly.inject(this.blocklyDiv, { toolbox: toolbox, renderer: 'zelos', @@ -119,22 +120,12 @@ wheel: true } }); - - if (!BlocklyComponent.blocklyStyle1) { - BlocklyComponent.blocklyStyle1 = document.getElementById('blockly-renderer-style-zelos-classic'); - this.shadowRoot.appendChild(BlocklyComponent.blocklyStyle1); - BlocklyComponent.blocklyStyle2 = document.getElementById('blockly-common-style'); - this.shadowRoot.appendChild(BlocklyComponent.blocklyStyle2); - } else { - this.shadowRoot.appendChild(BlocklyComponent.blocklyStyle1.cloneNode(true)); - this.shadowRoot.appendChild(BlocklyComponent.blocklyStyle2.cloneNode(true)); - } } connectedCallback() { Blockly.svgResize(this.workspace); - if (!resizeObserver) { + if (!this.resizeObserver) { this.resizeObserver = new ResizeObserver((entries) => { Blockly.svgResize(this.workspace); });