diff --git a/packages/blockly/core/common.ts b/packages/blockly/core/common.ts index 7f23779ec93..a87e1aa129a 100644 --- a/packages/blockly/core/common.ts +++ b/packages/blockly/core/common.ts @@ -141,8 +141,15 @@ let parentContainer: Element | null; * * @returns The parent container. */ -export function getParentContainer(): Element | null { - return parentContainer; +export function getParentContainer( + workspace = getMainWorkspace(), +): Element | null { + if (parentContainer) return parentContainer; + if (workspace && workspace.rendered) { + return (workspace as WorkspaceSvg).getInjectionDiv(); + } + + return null; } /** diff --git a/packages/blockly/core/css.ts b/packages/blockly/core/css.ts index ab1d494ad23..1e796b354ff 100644 --- a/packages/blockly/core/css.ts +++ b/packages/blockly/core/css.ts @@ -6,7 +6,8 @@ // Former goog.module ID: Blockly.Css /** Has CSS already been injected? */ -let injected = false; +const injectionSites = new WeakSet(); +const registeredStyleSheets: Array = []; /** * Add some CSS to the blob that will be injected later. Allows optional @@ -15,10 +16,11 @@ let injected = false; * @param cssContent Multiline CSS string or an array of single lines of CSS. */ export function register(cssContent: string) { - if (injected) { - throw Error('CSS already injected'); - } - content += '\n' + cssContent; + if (typeof window === 'undefined' || !window.CSSStyleSheet) return; + + const sheet = new CSSStyleSheet(); + sheet.replace(cssContent); + registeredStyleSheets.push(sheet); } /** @@ -28,37 +30,40 @@ export function register(cssContent: string) { * b) It speeds up loading by not blocking on a separate HTTP transfer. * c) The CSS content may be made dynamic depending on init options. * + * @param container The div or other HTML element into which Blockly was injected. * @param hasCss If false, don't inject CSS (providing CSS becomes the * 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; - if (!hasCss) { +export function inject( + container: HTMLElement, + hasCss: boolean, + pathToMedia: string, +) { + if (!hasCss || typeof window === 'undefined' || !window.CSSStyleSheet) { return; } + + const root = container.getRootNode() as Document | ShadowRoot; + // Only inject the CSS once. + if (injectionSites.has(root)) return; + injectionSites.add(root); + // 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 sheet = new CSSStyleSheet(); + sheet.replace(cssContent); + root.adoptedStyleSheets.push(sheet); + + registeredStyleSheets.forEach((sheet) => root.adoptedStyleSheets.push(sheet)); } /** * The CSS content for Blockly. */ -let content = ` +const content = ` :is( .injectionDiv, .blocklyWidgetDiv, diff --git a/packages/blockly/core/dropdowndiv.ts b/packages/blockly/core/dropdowndiv.ts index ceab467a895..704a767e8cb 100644 --- a/packages/blockly/core/dropdowndiv.ts +++ b/packages/blockly/core/dropdowndiv.ts @@ -370,6 +370,9 @@ export function show( manageEphemeralFocus: boolean, opt_onHide?: () => void, ): boolean { + const parentDiv = common.getParentContainer(); + parentDiv?.appendChild(div); + owner = newOwner as Field; onHide = opt_onHide || null; // Set direction. @@ -738,10 +741,19 @@ function positionInternal( arrow.style.display = 'none'; } - const initialX = Math.floor(metrics.initialX); - const initialY = Math.floor(metrics.initialY); - const finalX = Math.floor(metrics.finalX); - const finalY = Math.floor(metrics.finalY); + let initialX = Math.floor(metrics.initialX); + let initialY = Math.floor(metrics.initialY); + let finalX = Math.floor(metrics.finalX); + let finalY = Math.floor(metrics.finalY); + + const parentElement = div.parentElement; + if (parentElement) { + const bounds = parentElement.getBoundingClientRect(); + initialX -= bounds.left + window.scrollX; + finalX -= bounds.left + window.scrollX; + initialY -= bounds.top + window.scrollY; + finalY -= bounds.top + window.scrollY; + } // First apply initial translation. div.style.left = initialX + 'px'; diff --git a/packages/blockly/core/field_input.ts b/packages/blockly/core/field_input.ts index 55383a4c1d2..a8377ae050f 100644 --- a/packages/blockly/core/field_input.ts +++ b/packages/blockly/core/field_input.ts @@ -702,8 +702,15 @@ export abstract class FieldInput extends Field< // In RTL mode block fields and LTR input fields the left edge moves, // whereas the right edge is fixed. Reposition the editor. - const x = block.RTL ? bBox.right - div!.offsetWidth : bBox.left; - const y = bBox.top; + let x = block.RTL ? bBox.right - div!.offsetWidth : bBox.left; + let y = bBox.top; + + const parentElement = div?.parentElement; + if (parentElement) { + const bounds = parentElement.getBoundingClientRect(); + x -= bounds.left + window.scrollX; + y -= bounds.top + window.scrollY; + } div!.style.left = `${x}px`; div!.style.top = `${y}px`; diff --git a/packages/blockly/core/inject.ts b/packages/blockly/core/inject.ts index 1ecefa7c484..ca62eb47f29 100644 --- a/packages/blockly/core/inject.ts +++ b/packages/blockly/core/inject.ts @@ -95,7 +95,7 @@ function createDom(container: HTMLElement, options: Options): SVGElement { container.setAttribute('dir', 'LTR'); // Load CSS. - Css.inject(options.hasCss, options.pathToMedia); + Css.inject(container, options.hasCss, options.pathToMedia); // Build the SVG DOM. /* diff --git a/packages/blockly/core/renderers/common/constants.ts b/packages/blockly/core/renderers/common/constants.ts index c5a7a759c5c..764cef029c1 100644 --- a/packages/blockly/core/renderers/common/constants.ts +++ b/packages/blockly/core/renderers/common/constants.ts @@ -116,6 +116,8 @@ export function isNotch(shape: Shape): shape is Notch { ); } +const injectionSites = new Map>(); + /** * An object that provides constants for rendering blocks. */ @@ -327,9 +329,6 @@ export class ConstantProvider { */ private debugFilter: SVGElement | null = null; - /** The + + + + + + + +
+
+
Light DOM
+
+
+
+
+ +
+
+ Shadow DOM via <blockly-editor> +
+
+ +
+
+
+ +