Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Create CSS vars for SVG patterns. #8671

Merged
merged 1 commit into from
Dec 2, 2024
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
6 changes: 1 addition & 5 deletions core/bubbles/bubble.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
9 changes: 9 additions & 0 deletions core/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ let content = `
transition: transform .5s;
}

.blocklyEmboss {
filter: var(--blocklyEmbossFilter);
}

.blocklyTooltipDiv {
background-color: #ffffc7;
border: 1px solid #ddc;
Expand Down Expand Up @@ -138,6 +142,10 @@ let content = `
border-color: inherit;
}

.blocklyHighlighted>.blocklyPath {
filter: var(--blocklyEmbossFilter);
}

.blocklyHighlightedConnectionPath {
fill: none;
stroke: #fc3;
Expand Down Expand Up @@ -189,6 +197,7 @@ let content = `
}

.blocklyDisabled>.blocklyPath {
fill: var(--blocklyDisabledPattern);
fill-opacity: .5;
stroke-opacity: .5;
}
Expand Down
15 changes: 15 additions & 0 deletions core/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,13 +210,17 @@ 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
*/
static createDom(
rnd: string,
gridOptions: GridOptions,
defs: SVGElement,
injectionDiv?: HTMLElement,
): SVGElement {
/*
<pattern id="blocklyGridPattern837493" patternUnits="userSpaceOnUse">
Expand Down Expand Up @@ -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;
}
}
11 changes: 8 additions & 3 deletions core/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}

Expand All @@ -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 {
Expand Down
30 changes: 29 additions & 1 deletion core/renderers/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/*
Expand Down Expand Up @@ -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})`,
);
}
}

/**
Expand Down
11 changes: 0 additions & 11 deletions core/renderers/common/path_object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -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 + ')',
);
}
}

/**
Expand Down
27 changes: 23 additions & 4 deletions core/renderers/common/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
}

Expand All @@ -93,8 +103,17 @@ export class Renderer implements IRegistrable {
*
* @param svg The root of the workspace's SVG.
* @param theme The workspace theme object.
*/
refreshDom(svg: SVGElement, theme: Theme) {
* @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,
injectionDivIfIsParent?: HTMLElement,
) {
const previousConstants = this.getConstants();
previousConstants.dispose();
this.constants_ = this.makeConstants_();
Expand All @@ -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);
}

/**
Expand Down
8 changes: 6 additions & 2 deletions core/renderers/geras/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
34 changes: 31 additions & 3 deletions core/renderers/zelos/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ...
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -873,14 +892,23 @@ export class ConstantProvider extends BaseConstantProvider {

// Disabled outline paths.
`${selector} .blocklyDisabled > .blocklyOutlinePath {`,
`fill: url(#blocklyDisabledPattern${this.randomIdentifier})`,
`fill: var(--blocklyDisabledPattern)`,
`}`,

// Insertion marker.
`${selector} .blocklyInsertionMarker>.blocklyPath {`,
`fill-opacity: ${this.INSERTION_MARKER_OPACITY};`,
`stroke: none;`,
`}`,

`${selector} .blocklySelected>.blocklyPath.blocklyPathSelected {`,
`fill: none;`,
`filter: var(--blocklySelectedGlowFilter);`,
`}`,

`${selector} .blocklyReplaceable>.blocklyPath {`,
`filter: var(--blocklyReplacementGlowFilter);`,
`}`,
];
}
}
Expand Down
14 changes: 1 addition & 13 deletions core/renderers/zelos/path_object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down
Loading
Loading