mirror of
https://github.com/google/blockly.git
synced 2026-01-11 19:07:08 +01:00
fix: Create CSS vars for SVG patterns. (#8671)
This commit is contained in:
@@ -106,11 +106,7 @@ export abstract class Bubble implements IBubble, ISelectable {
|
||||
);
|
||||
const embossGroup = dom.createSvgElement(
|
||||
Svg.G,
|
||||
{
|
||||
'filter': `url(#${
|
||||
this.workspace.getRenderer().getConstants().embossFilterId
|
||||
})`,
|
||||
},
|
||||
{'class': 'blocklyEmboss'},
|
||||
this.svgRoot,
|
||||
);
|
||||
this.tail = dom.createSvgElement(
|
||||
|
||||
@@ -85,6 +85,10 @@ let content = `
|
||||
transition: transform .5s;
|
||||
}
|
||||
|
||||
.blocklyEmboss {
|
||||
filter: var(--blocklyEmbossFilter);
|
||||
}
|
||||
|
||||
.blocklyTooltipDiv {
|
||||
background-color: #ffffc7;
|
||||
border: 1px solid #ddc;
|
||||
@@ -138,6 +142,10 @@ let content = `
|
||||
border-color: inherit;
|
||||
}
|
||||
|
||||
.blocklyHighlighted>.blocklyPath {
|
||||
filter: var(--blocklyEmbossFilter);
|
||||
}
|
||||
|
||||
.blocklyHighlightedConnectionPath {
|
||||
fill: none;
|
||||
stroke: #fc3;
|
||||
@@ -189,6 +197,7 @@ let content = `
|
||||
}
|
||||
|
||||
.blocklyDisabled>.blocklyPath {
|
||||
fill: var(--blocklyDisabledPattern);
|
||||
fill-opacity: .5;
|
||||
stroke-opacity: .5;
|
||||
}
|
||||
|
||||
15
core/grid.ts
15
core/grid.ts
@@ -210,6 +210,9 @@ export class Grid {
|
||||
* @param rnd A random ID to append to the pattern's ID.
|
||||
* @param gridOptions The object containing grid configuration.
|
||||
* @param defs The root SVG element for this workspace's defs.
|
||||
* @param injectionDiv The div containing the parent workspace and all related
|
||||
* workspaces and block containers. CSS variables representing SVG patterns
|
||||
* will be scoped to this container.
|
||||
* @returns The SVG element for the grid pattern.
|
||||
* @internal
|
||||
*/
|
||||
@@ -217,6 +220,7 @@ export class Grid {
|
||||
rnd: string,
|
||||
gridOptions: GridOptions,
|
||||
defs: SVGElement,
|
||||
injectionDiv?: HTMLElement,
|
||||
): SVGElement {
|
||||
/*
|
||||
<pattern id="blocklyGridPattern837493" patternUnits="userSpaceOnUse">
|
||||
@@ -247,6 +251,17 @@ export class Grid {
|
||||
// Edge 16 doesn't handle empty patterns
|
||||
dom.createSvgElement(Svg.LINE, {}, gridPattern);
|
||||
}
|
||||
|
||||
if (injectionDiv) {
|
||||
// Add CSS variables scoped to the injection div referencing the created
|
||||
// patterns so that CSS can apply the patterns to any element in the
|
||||
// injection div.
|
||||
injectionDiv.style.setProperty(
|
||||
'--blocklyGridPattern',
|
||||
`url(#${gridPattern.id})`,
|
||||
);
|
||||
}
|
||||
|
||||
return gridPattern;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ export function inject(
|
||||
* @param options Dictionary of options.
|
||||
* @returns Newly created SVG image.
|
||||
*/
|
||||
function createDom(container: Element, options: Options): SVGElement {
|
||||
function createDom(container: HTMLElement, options: Options): SVGElement {
|
||||
// Sadly browsers (Chrome vs Firefox) are currently inconsistent in laying
|
||||
// out content in RTL mode. Therefore Blockly forces the use of LTR,
|
||||
// then manually positions content in RTL as needed.
|
||||
@@ -132,7 +132,12 @@ function createDom(container: Element, options: Options): SVGElement {
|
||||
// https://neil.fraser.name/news/2015/11/01/
|
||||
const rnd = String(Math.random()).substring(2);
|
||||
|
||||
options.gridPattern = Grid.createDom(rnd, options.gridOptions, defs);
|
||||
options.gridPattern = Grid.createDom(
|
||||
rnd,
|
||||
options.gridOptions,
|
||||
defs,
|
||||
container,
|
||||
);
|
||||
return svg;
|
||||
}
|
||||
|
||||
@@ -144,7 +149,7 @@ function createDom(container: Element, options: Options): SVGElement {
|
||||
* @returns Newly created main workspace.
|
||||
*/
|
||||
function createMainWorkspace(
|
||||
injectionDiv: Element,
|
||||
injectionDiv: HTMLElement,
|
||||
svg: SVGElement,
|
||||
options: Options,
|
||||
): WorkspaceSvg {
|
||||
|
||||
@@ -926,8 +926,18 @@ export class ConstantProvider {
|
||||
* @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
|
||||
* parent workspace. CSS variables representing SVG patterns will be scoped
|
||||
* to this container. Child workspaces should not override the CSS variables
|
||||
* created by the parent and thus do not need access to the injection div.
|
||||
*/
|
||||
createDom(svg: SVGElement, tagName: string, selector: string) {
|
||||
createDom(
|
||||
svg: SVGElement,
|
||||
tagName: string,
|
||||
selector: string,
|
||||
injectionDivIfIsParent?: HTMLElement,
|
||||
) {
|
||||
this.injectCSS_(tagName, selector);
|
||||
|
||||
/*
|
||||
@@ -1034,6 +1044,24 @@ export class ConstantProvider {
|
||||
this.disabledPattern = disabledPattern;
|
||||
|
||||
this.createDebugFilter();
|
||||
|
||||
if (injectionDivIfIsParent) {
|
||||
// If this renderer is for the parent workspace, add CSS variables scoped
|
||||
// to the injection div referencing the created patterns so that CSS can
|
||||
// apply the patterns to any element in the injection div.
|
||||
injectionDivIfIsParent.style.setProperty(
|
||||
'--blocklyEmbossFilter',
|
||||
`url(#${this.embossFilterId})`,
|
||||
);
|
||||
injectionDivIfIsParent.style.setProperty(
|
||||
'--blocklyDisabledPattern',
|
||||
`url(#${this.disabledPatternId})`,
|
||||
);
|
||||
injectionDivIfIsParent.style.setProperty(
|
||||
'--blocklyDebugFilter',
|
||||
`url(#${this.debugFilterId})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -173,13 +173,8 @@ export class PathObject implements IPathObject {
|
||||
|
||||
updateHighlighted(enable: boolean) {
|
||||
if (enable) {
|
||||
this.svgPath.setAttribute(
|
||||
'filter',
|
||||
'url(#' + this.constants.embossFilterId + ')',
|
||||
);
|
||||
this.setClass_('blocklyHighlighted', true);
|
||||
} else {
|
||||
this.svgPath.setAttribute('filter', 'none');
|
||||
this.setClass_('blocklyHighlighted', false);
|
||||
}
|
||||
}
|
||||
@@ -206,12 +201,6 @@ export class PathObject implements IPathObject {
|
||||
*/
|
||||
protected updateDisabled_(disabled: boolean) {
|
||||
this.setClass_('blocklyDisabled', disabled);
|
||||
if (disabled) {
|
||||
this.svgPath.setAttribute(
|
||||
'fill',
|
||||
'url(#' + this.constants.disabledPatternId + ')',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -78,13 +78,23 @@ export class Renderer implements IRegistrable {
|
||||
*
|
||||
* @param svg The root of the workspace's SVG.
|
||||
* @param theme The workspace theme object.
|
||||
* @param injectionDivIfIsParent The div containing the parent workspace and
|
||||
* all related workspaces and block containers, if this renderer is for the
|
||||
* parent workspace. CSS variables representing SVG patterns will be scoped
|
||||
* to this container. Child workspaces should not override the CSS variables
|
||||
* created by the parent and thus do not need access to the injection div.
|
||||
* @internal
|
||||
*/
|
||||
createDom(svg: SVGElement, theme: Theme) {
|
||||
createDom(
|
||||
svg: SVGElement,
|
||||
theme: Theme,
|
||||
injectionDivIfIsParent?: HTMLElement,
|
||||
) {
|
||||
this.constants_.createDom(
|
||||
svg,
|
||||
this.name + '-' + theme.name,
|
||||
'.' + this.getClassName() + '.' + theme.getClassName(),
|
||||
injectionDivIfIsParent,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,8 +103,17 @@ export class Renderer implements IRegistrable {
|
||||
*
|
||||
* @param svg The root of the workspace's SVG.
|
||||
* @param theme The workspace theme object.
|
||||
* @param injectionDivIfIsParent The div containing the parent workspace and
|
||||
* all related workspaces and block containers, if this renderer is for the
|
||||
* parent workspace. CSS variables representing SVG patterns will be scoped
|
||||
* to this container. Child workspaces should not override the CSS variables
|
||||
* created by the parent and thus do not need access to the injection div.
|
||||
*/
|
||||
refreshDom(svg: SVGElement, theme: Theme) {
|
||||
refreshDom(
|
||||
svg: SVGElement,
|
||||
theme: Theme,
|
||||
injectionDivIfIsParent?: HTMLElement,
|
||||
) {
|
||||
const previousConstants = this.getConstants();
|
||||
previousConstants.dispose();
|
||||
this.constants_ = this.makeConstants_();
|
||||
@@ -105,7 +124,7 @@ export class Renderer implements IRegistrable {
|
||||
this.constants_.randomIdentifier = previousConstants.randomIdentifier;
|
||||
this.constants_.setTheme(theme);
|
||||
this.constants_.init();
|
||||
this.createDom(svg, theme);
|
||||
this.createDom(svg, theme, injectionDivIfIsParent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,8 +50,12 @@ export class Renderer extends BaseRenderer {
|
||||
this.highlightConstants.init();
|
||||
}
|
||||
|
||||
override refreshDom(svg: SVGElement, theme: Theme) {
|
||||
super.refreshDom(svg, theme);
|
||||
override refreshDom(
|
||||
svg: SVGElement,
|
||||
theme: Theme,
|
||||
injectionDiv: HTMLElement,
|
||||
) {
|
||||
super.refreshDom(svg, theme, injectionDiv);
|
||||
this.getHighlightConstants().init();
|
||||
}
|
||||
|
||||
|
||||
@@ -675,8 +675,13 @@ export class ConstantProvider extends BaseConstantProvider {
|
||||
return utilsColour.blend('#000', colour, 0.25) || colour;
|
||||
}
|
||||
|
||||
override createDom(svg: SVGElement, tagName: string, selector: string) {
|
||||
super.createDom(svg, tagName, selector);
|
||||
override createDom(
|
||||
svg: SVGElement,
|
||||
tagName: string,
|
||||
selector: string,
|
||||
injectionDivIfIsParent?: HTMLElement,
|
||||
) {
|
||||
super.createDom(svg, tagName, selector, injectionDivIfIsParent);
|
||||
/*
|
||||
<defs>
|
||||
... filters go here ...
|
||||
@@ -795,6 +800,20 @@ export class ConstantProvider extends BaseConstantProvider {
|
||||
);
|
||||
this.replacementGlowFilterId = replacementGlowFilter.id;
|
||||
this.replacementGlowFilter = replacementGlowFilter;
|
||||
|
||||
if (injectionDivIfIsParent) {
|
||||
// If this renderer is for the parent workspace, add CSS variables scoped
|
||||
// to the injection div referencing the created patterns so that CSS can
|
||||
// apply the patterns to any element in the injection div.
|
||||
injectionDivIfIsParent.style.setProperty(
|
||||
'--blocklySelectedGlowFilter',
|
||||
`url(#${this.selectedGlowFilterId})`,
|
||||
);
|
||||
injectionDivIfIsParent.style.setProperty(
|
||||
'--blocklyReplacementGlowFilter',
|
||||
`url(#${this.replacementGlowFilterId})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
override getCSS_(selector: string) {
|
||||
@@ -873,7 +892,7 @@ export class ConstantProvider extends BaseConstantProvider {
|
||||
|
||||
// Disabled outline paths.
|
||||
`${selector} .blocklyDisabled > .blocklyOutlinePath {`,
|
||||
`fill: url(#blocklyDisabledPattern${this.randomIdentifier})`,
|
||||
`fill: var(--blocklyDisabledPattern)`,
|
||||
`}`,
|
||||
|
||||
// Insertion marker.
|
||||
@@ -881,6 +900,15 @@ export class ConstantProvider extends BaseConstantProvider {
|
||||
`fill-opacity: ${this.INSERTION_MARKER_OPACITY};`,
|
||||
`stroke: none;`,
|
||||
`}`,
|
||||
|
||||
`${selector} .blocklySelected>.blocklyPath.blocklyPathSelected {`,
|
||||
`fill: none;`,
|
||||
`filter: var(--blocklySelectedGlowFilter);`,
|
||||
`}`,
|
||||
|
||||
`${selector} .blocklyReplaceable>.blocklyPath {`,
|
||||
`filter: var(--blocklyReplacementGlowFilter);`,
|
||||
`}`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,11 +91,7 @@ export class PathObject extends BasePathObject {
|
||||
if (enable) {
|
||||
if (!this.svgPathSelected) {
|
||||
this.svgPathSelected = this.svgPath.cloneNode(true) as SVGElement;
|
||||
this.svgPathSelected.setAttribute('fill', 'none');
|
||||
this.svgPathSelected.setAttribute(
|
||||
'filter',
|
||||
'url(#' + this.constants.selectedGlowFilterId + ')',
|
||||
);
|
||||
this.svgPathSelected.classList.add('blocklyPathSelected');
|
||||
this.svgRoot.appendChild(this.svgPathSelected);
|
||||
}
|
||||
} else {
|
||||
@@ -108,14 +104,6 @@ export class PathObject extends BasePathObject {
|
||||
|
||||
override updateReplacementFade(enable: boolean) {
|
||||
this.setClass_('blocklyReplaceable', enable);
|
||||
if (enable) {
|
||||
this.svgPath.setAttribute(
|
||||
'filter',
|
||||
'url(#' + this.constants.replacementGlowFilterId + ')',
|
||||
);
|
||||
} else {
|
||||
this.svgPath.removeAttribute('filter');
|
||||
}
|
||||
}
|
||||
|
||||
override updateShapeForInputHighlight(conn: Connection, enable: boolean) {
|
||||
|
||||
@@ -225,7 +225,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
* The first parent div with 'injectionDiv' in the name, or null if not set.
|
||||
* Access this with getInjectionDiv.
|
||||
*/
|
||||
private injectionDiv: Element | null = null;
|
||||
private injectionDiv: HTMLElement | null = null;
|
||||
|
||||
/**
|
||||
* Last known position of the page scroll.
|
||||
@@ -539,7 +539,12 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
*/
|
||||
refreshTheme() {
|
||||
if (this.svgGroup_) {
|
||||
this.renderer.refreshDom(this.svgGroup_, this.getTheme());
|
||||
const isParentWorkspace = this.options.parentWorkspace === null;
|
||||
this.renderer.refreshDom(
|
||||
this.svgGroup_,
|
||||
this.getTheme(),
|
||||
isParentWorkspace ? this.getInjectionDiv() : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
// Update all blocks in workspace that have a style name.
|
||||
@@ -636,20 +641,24 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
// Before the SVG canvas, scale the coordinates.
|
||||
scale = this.scale;
|
||||
}
|
||||
let ancestor: Element = element;
|
||||
do {
|
||||
// Loop through this block and every parent.
|
||||
const xy = svgMath.getRelativeXY(element);
|
||||
if (element === this.getCanvas() || element === this.getBubbleCanvas()) {
|
||||
const xy = svgMath.getRelativeXY(ancestor);
|
||||
if (
|
||||
ancestor === this.getCanvas() ||
|
||||
ancestor === this.getBubbleCanvas()
|
||||
) {
|
||||
// After the SVG canvas, don't scale the coordinates.
|
||||
scale = 1;
|
||||
}
|
||||
x += xy.x * scale;
|
||||
y += xy.y * scale;
|
||||
element = element.parentNode as SVGElement;
|
||||
ancestor = ancestor.parentNode as Element;
|
||||
} while (
|
||||
element &&
|
||||
element !== this.getParentSvg() &&
|
||||
element !== this.getInjectionDiv()
|
||||
ancestor &&
|
||||
ancestor !== this.getParentSvg() &&
|
||||
ancestor !== this.getInjectionDiv()
|
||||
);
|
||||
return new Coordinate(x, y);
|
||||
}
|
||||
@@ -687,7 +696,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
* @returns The first parent div with 'injectionDiv' in the name.
|
||||
* @internal
|
||||
*/
|
||||
getInjectionDiv(): Element {
|
||||
getInjectionDiv(): HTMLElement {
|
||||
// NB: it would be better to pass this in at createDom, but is more likely
|
||||
// to break existing uses of Blockly.
|
||||
if (!this.injectionDiv) {
|
||||
@@ -695,7 +704,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
while (element) {
|
||||
const classes = element.getAttribute('class') || '';
|
||||
if ((' ' + classes + ' ').includes(' injectionDiv ')) {
|
||||
this.injectionDiv = element;
|
||||
this.injectionDiv = element as HTMLElement;
|
||||
break;
|
||||
}
|
||||
element = element.parentNode as Element;
|
||||
@@ -739,7 +748,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
* 'blocklyMutatorBackground'.
|
||||
* @returns The workspace's SVG group.
|
||||
*/
|
||||
createDom(opt_backgroundClass?: string, injectionDiv?: Element): Element {
|
||||
createDom(opt_backgroundClass?: string, injectionDiv?: HTMLElement): Element {
|
||||
if (!this.injectionDiv) {
|
||||
this.injectionDiv = injectionDiv ?? null;
|
||||
}
|
||||
@@ -765,8 +774,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
);
|
||||
|
||||
if (opt_backgroundClass === 'blocklyMainBackground' && this.grid) {
|
||||
this.svgBackground_.style.fill =
|
||||
'url(#' + this.grid.getPatternId() + ')';
|
||||
this.svgBackground_.style.fill = 'var(--blocklyGridPattern)';
|
||||
} else {
|
||||
this.themeManager_.subscribe(
|
||||
this.svgBackground_,
|
||||
@@ -823,7 +831,12 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
|
||||
CursorClass && this.markerManager.setCursor(new CursorClass());
|
||||
|
||||
this.renderer.createDom(this.svgGroup_, this.getTheme());
|
||||
const isParentWorkspace = this.options.parentWorkspace === null;
|
||||
this.renderer.createDom(
|
||||
this.svgGroup_,
|
||||
this.getTheme(),
|
||||
isParentWorkspace ? this.getInjectionDiv() : undefined,
|
||||
);
|
||||
return this.svgGroup_;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user