Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions packages/blockly/core/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
47 changes: 26 additions & 21 deletions packages/blockly/core/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

// Former goog.module ID: Blockly.Css
/** Has CSS already been injected? */
let injected = false;
const injectionSites = new WeakSet<Document | ShadowRoot>();
const registeredStyleSheets: Array<CSSStyleSheet> = [];

/**
* Add some CSS to the blob that will be injected later. Allows optional
Expand All @@ -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);
}

/**
Expand All @@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again not too familiar with this code, but is it possible to have container be optional and the default value be getParentContainer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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(/<<<PATH>>>/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,
Expand Down
20 changes: 16 additions & 4 deletions packages/blockly/core/dropdowndiv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@ export function show<T>(
manageEphemeralFocus: boolean,
opt_onHide?: () => void,
): boolean {
const parentDiv = common.getParentContainer();
parentDiv?.appendChild(div);

owner = newOwner as Field;
onHide = opt_onHide || null;
// Set direction.
Expand Down Expand Up @@ -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';
Expand Down
11 changes: 9 additions & 2 deletions packages/blockly/core/field_input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,8 +702,15 @@ export abstract class FieldInput<T extends InputTypes> 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`;
Expand Down
2 changes: 1 addition & 1 deletion packages/blockly/core/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/*
Expand Down
49 changes: 25 additions & 24 deletions packages/blockly/core/renderers/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ export function isNotch(shape: Shape): shape is Notch {
);
}

const injectionSites = new Map<string, WeakSet<Document | ShadowRoot>>();

/**
* An object that provides constants for rendering blocks.
*/
Expand Down Expand Up @@ -327,9 +329,6 @@ export class ConstantProvider {
*/
private debugFilter: SVGElement | null = null;

/** The <style> element to use for injecting renderer specific CSS. */
private cssNode: HTMLStyleElement | null = null;

/**
* Cursor colour.
*/
Expand Down Expand Up @@ -696,7 +695,6 @@ export class ConstantProvider {
if (this.debugFilter) {
dom.removeNode(this.debugFilter);
}
this.cssNode = null;
}

/**
Expand Down Expand Up @@ -924,7 +922,6 @@ export class ConstantProvider {
* Create any DOM elements that this renderer needs (filters, patterns, etc).
*
* @param svg The root of the workspace's SVG.
* @param tagName The name to use for the CSS style tag.
* @param selector The CSS selector to use.
* @param injectionDivIfIsParent The div containing the parent workspace and
* all related workspaces and block containers, if this renderer is for the
Expand All @@ -934,11 +931,15 @@ export class ConstantProvider {
*/
createDom(
svg: SVGElement,
tagName: string,
selector: string,
injectionDivIfIsParent?: HTMLElement,
) {
this.injectCSS_(tagName, selector);
if (injectionDivIfIsParent) {
const root = injectionDivIfIsParent.getRootNode() as
| Document
| ShadowRoot;
this.injectCSS_(root, selector);
}

/*
<defs>
Expand Down Expand Up @@ -1121,26 +1122,26 @@ export class ConstantProvider {
/**
* Inject renderer specific CSS into the page.
*
* @param tagName The name of the style tag to use.
* @param selector The CSS selector to use.
* @param root The document root to inject the CSS into.
* @param selector The CSS selector to interpolate into the stylesheet.
*/
protected injectCSS_(tagName: string, selector: string) {
const cssArray = this.getCSS_(selector);
const cssNodeId = 'blockly-renderer-style-' + tagName;
this.cssNode = document.getElementById(cssNodeId) as HTMLStyleElement;
const text = cssArray.join('\n');
if (this.cssNode) {
// Already injected, update if the theme changed.
this.cssNode.firstChild!.textContent = text;
protected injectCSS_(root: Document | ShadowRoot, selector: string) {
if (
typeof window === 'undefined' ||
!window.CSSStyleSheet ||
injectionSites.get(selector)?.has(root)
) {
return;
}
// Inject CSS tag at start of head.
const cssNode = document.createElement('style');
cssNode.id = cssNodeId;
const cssTextNode = document.createTextNode(text);
cssNode.appendChild(cssTextNode);
document.head.insertBefore(cssNode, document.head.firstChild);
this.cssNode = cssNode;

const sheet = new CSSStyleSheet();
sheet.replace(this.getCSS_(selector).join('\n'));
root.adoptedStyleSheets.push(sheet);

const sitesForSelector =
injectionSites.get(selector) ?? new WeakSet<Document | ShadowRoot>();
sitesForSelector.add(root);
injectionSites.set(selector, sitesForSelector);
}

/**
Expand Down
1 change: 0 additions & 1 deletion packages/blockly/core/renderers/common/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ export class Renderer implements IRegistrable {
) {
this.constants_.createDom(
svg,
this.name + '-' + theme.name,
'.' + this.getClassName() + '.' + theme.getClassName(),
injectionDivIfIsParent,
);
Expand Down
3 changes: 1 addition & 2 deletions packages/blockly/core/renderers/zelos/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -647,11 +647,10 @@ export class ConstantProvider extends BaseConstantProvider {

override createDom(
svg: SVGElement,
tagName: string,
selector: string,
injectionDivIfIsParent?: HTMLElement,
) {
super.createDom(svg, tagName, selector, injectionDivIfIsParent);
super.createDom(svg, selector, injectionDivIfIsParent);
/*
<defs>
... filters go here ...
Expand Down
35 changes: 31 additions & 4 deletions packages/blockly/core/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import * as browserEvents from './browser_events.js';
import * as common from './common.js';
import * as blocklyString from './utils/string.js';
import type {WorkspaceSvg} from './workspace_svg.js';

/**
* A type which can define a tooltip.
Expand Down Expand Up @@ -287,7 +288,7 @@ function onMouseOut(_e: PointerEvent) {
*
* @param e Mouse event.
*/
function onMouseMove(e: Event) {
function onMouseMove(this: any, e: Event) {
if (!element || !(element as AnyDuringMigration).tooltip) {
// No tooltip here to show.
return;
Expand Down Expand Up @@ -318,7 +319,20 @@ function onMouseMove(e: Event) {
// AnyDuringMigration because: Property 'pageY' does not exist on type
// 'Event'.
lastY = (e as AnyDuringMigration).pageY;
showPid = setTimeout(show, HOVER_MS);
showPid = setTimeout(() => {
let workspace: WorkspaceSvg | undefined;
if (this instanceof Element) {
for (const ws of common.getAllWorkspaces()) {
if (!ws.rendered) continue;
if ((ws as WorkspaceSvg).getInjectionDiv()?.contains(this)) {
workspace = ws as WorkspaceSvg;
break;
}
}
}

show(workspace);
}, HOVER_MS);
}
}

Expand Down Expand Up @@ -416,7 +430,16 @@ function getPosition(rtl: boolean): {x: number; y: number} {
}

let anchorY = lastY + OFFSET_Y;
if (anchorY + containerDiv!.offsetHeight > windowHeight + window.scrollY) {

const parentElement = containerDiv?.parentElement;
if (parentElement) {
const parentBounds = parentElement.getBoundingClientRect();
anchorX -= parentBounds.left + window.scrollX;
anchorY -= parentBounds.top + window.scrollY;
}

const tooltipBottom = anchorY + containerDiv!.offsetHeight;
if (tooltipBottom > windowHeight + window.scrollY) {
// Falling off the bottom of the screen; shift the tooltip up.
anchorY -= containerDiv!.offsetHeight + 2 * OFFSET_Y;
}
Expand All @@ -439,7 +462,7 @@ function getPosition(rtl: boolean): {x: number; y: number} {
}

/** Create the tooltip and show it. */
function show() {
function show(workspace?: WorkspaceSvg) {
if (blocked) {
// Someone doesn't want us to show tooltips.
return;
Expand All @@ -448,6 +471,10 @@ function show() {
if (!containerDiv) {
return;
}

const parentDiv = common.getParentContainer(workspace);
parentDiv?.appendChild(containerDiv);

// Erase all existing text.
containerDiv.textContent = '';

Expand Down
19 changes: 16 additions & 3 deletions packages/blockly/core/widgetdiv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ export function show(
dispose = newDispose;
const div = containerDiv;
if (!div) return;

const parentDiv = common.getParentContainer();
parentDiv?.appendChild(div);

div.style.direction = rtl ? 'rtl' : 'ltr';
div.style.display = 'block';
if (!workspace && newOwner instanceof Field) {
Expand Down Expand Up @@ -225,9 +229,18 @@ export function hideIfOwnerIsInWorkspace(workspace: WorkspaceSvg) {
* @param height The height of the widget div (pixels).
*/
function positionInternal(x: number, y: number, height: number) {
containerDiv!.style.left = x + 'px';
containerDiv!.style.top = y + 'px';
containerDiv!.style.height = height + 'px';
if (!containerDiv) return;

const parentElement = containerDiv.parentElement;
if (parentElement) {
const bounds = parentElement.getBoundingClientRect();
x -= bounds.left + window.scrollX;
y -= bounds.top + window.scrollY;
}

containerDiv.style.left = x + 'px';
containerDiv.style.top = y + 'px';
containerDiv.style.height = height + 'px';
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/blockly/tests/mocha/contextmenu_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ suite('Context Menu', function () {

const menu = Blockly.ContextMenu.getMenu();
assert.instanceOf(menu, Blockly.Menu, 'getMenu() should return a Menu');
assert.include(menu.getElement().innerText, 'Test option');
assert.include(menu.getElement().textContent, 'Test option');

Blockly.ContextMenu.hide();
assert.isNull(Blockly.ContextMenu.getMenu());
Expand Down
Loading
Loading