Skip to content

Commit

Permalink
Merge pull request #12631 from ckeditor/ck/12597-icon-view-allow-unmo…
Browse files Browse the repository at this point in the history
…dified-icons

Fix (ui): Improvements to the `IconView` component to allow rich colorful icons. Closes #12597. Closes #12599.

Preserved presentational attributes from the icon source on the `<svg>` element (#12597). 
Made it possible to opt out from icon color inheritance (#12599). 
Excluded icon internals from CSS reset (#12599).
  • Loading branch information
oleq authored Oct 14, 2022
2 parents 5809d20 + e8841d3 commit 57100fa
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
/* Multiplied by the height of the line in "px" should give SVG "viewport" dimensions */
font-size: .8333350694em;

color: inherit;

/* Inherit cursor style (#5). */
cursor: inherit;

Expand All @@ -25,13 +23,19 @@
& * {
/* Inherit cursor style (#5). */
cursor: inherit;
}

/* Allows dynamic coloring of the icons. */
/* Allows dynamic coloring of an icon by inheriting its color from the parent. */
&.ck-icon_inherit-color {
color: inherit;

&:not([fill]) {
/* Needed by FF. */
fill: currentColor;
& * {
color: inherit;

&:not([fill]) {
/* Needed by FF. */
fill: currentColor;
}
}
}
}
60 changes: 59 additions & 1 deletion packages/ckeditor5-ui/_src/icon/iconview.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,43 @@ export default class IconView extends View {
*/
this.set( 'fillColor', '' );

/**
* When set true (default), all parts of the icon inherit the fill color from the CSS `color` property of the
* icon's DOM parent.
*
* This effectively makes the icon monochromatic and allows it to change its fill color dynamically, for instance,
* when a {@link module:ui/button/buttonview~ButtonView} displays an icon and it switches between different states
* (pushed, hovered, etc.) the icon will follow along.
*
* **Note**: For the monochromatic icon to render properly, it must be made up of shapes that can be filled
* with color instead of, for instance, paths with strokes. Be sure to use the *outline stroke* tool
* (the name could be different in your vector graphics editor) before exporting your icon. Also, remove any
* excess `fill="..."` attributes that could break the color inheritance.
*
* **Note**: If you want to preserve the original look of your icon and disable dynamic color inheritance,
* set this flag to `false`.
*
* @observable
* @default true
* @member {Boolean} #isColorInherited
*/
this.set( 'isColorInherited', true );

this.setTemplate( {
tag: 'svg',
ns: 'http://www.w3.org/2000/svg',
attributes: {
class: [
'ck',
'ck-icon'
'ck-icon',

// Exclude icon internals from the CSS reset to allow rich (non-monochromatic) icons
// (https://github.com/ckeditor/ckeditor5/issues/12599).
'ck-reset_all-excluded',

// The class to remove the dynamic color inheritance is toggleable
// (https://github.com/ckeditor/ckeditor5/issues/12599).
bind.if( 'isColorInherited', 'ck-icon_inherit-color' )
],
viewBox: bind.to( 'viewBox' )
}
Expand Down Expand Up @@ -103,6 +133,14 @@ export default class IconView extends View {
this.viewBox = viewBox;
}

// Preserve presentational attributes of the <svg> element from the source.
// They can affect rendering of the entire icon (https://github.com/ckeditor/ckeditor5/issues/12597).
for ( const { name, value } of Array.from( svg.attributes ) ) {
if ( IconView.presentationalAttributeNames.includes( name ) ) {
this.element.setAttribute( name, value );
}
}

while ( this.element.firstChild ) {
this.element.removeChild( this.element.firstChild );
}
Expand All @@ -126,3 +164,23 @@ export default class IconView extends View {
}
}
}

/**
* A list of presentational attributes that can be set on the `<svg>` element and should be preserved
* when the icon {@link module:ui/icon~IconView#content content} is loaded.
*
* See https://www.w3.org/TR/SVG/styling.html#TermPresentationAttribute.
*
* @protected
* @member {Array.<String>} module:ui/icon~IconView.presentationalAttributeNames
*/
IconView.presentationalAttributeNames = [
'alignment-baseline', 'baseline-shift', 'clip-path', 'clip-rule', 'color', 'color-interpolation',
'color-interpolation-filters', 'color-rendering', 'cursor', 'direction', 'display', 'dominant-baseline', 'fill', 'fill-opacity',
'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style',
'font-variant', 'font-weight', 'image-rendering', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start',
'mask', 'opacity', 'overflow', 'paint-order', 'pointer-events', 'shape-rendering', 'stop-color', 'stop-opacity', 'stroke',
'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width',
'text-anchor', 'text-decoration', 'text-overflow', 'text-rendering', 'transform', 'unicode-bidi', 'vector-effect',
'visibility', 'white-space', 'word-spacing', 'writing-mode'
];
62 changes: 61 additions & 1 deletion packages/ckeditor5-ui/src/icon/iconview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export default class IconView extends View {
declare public content: string | undefined;
declare public viewBox: string;
declare public fillColor: string;
declare public isColorInherited: boolean;
declare public static presentationalAttributeNames: Array<string>;

/**
* @inheritDoc
Expand Down Expand Up @@ -59,13 +61,43 @@ export default class IconView extends View {
*/
this.set( 'fillColor', '' );

/**
* When set true (default), all parts of the icon inherit the fill color from the CSS `color` property of the
* icon's DOM parent.
*
* This effectively makes the icon monochromatic and allows it to change its fill color dynamically, for instance,
* when a {@link module:ui/button/buttonview~ButtonView} displays an icon and it switches between different states
* (pushed, hovered, etc.) the icon will follow along.
*
* **Note**: For the monochromatic icon to render properly, it must be made up of shapes that can be filled
* with color instead of, for instance, paths with strokes. Be sure to use the *outline stroke* tool
* (the name could be different in your vector graphics editor) before exporting your icon. Also, remove any
* excess `fill="..."` attributes that could break the color inheritance.
*
* **Note**: If you want to preserve the original look of your icon and disable dynamic color inheritance,
* set this flag to `false`.
*
* @observable
* @default true
* @member {Boolean} #isColorInherited
*/
this.set( 'isColorInherited', true );

this.setTemplate( {
tag: 'svg',
ns: 'http://www.w3.org/2000/svg',
attributes: {
class: [
'ck',
'ck-icon'
'ck-icon',

// Exclude icon internals from the CSS reset to allow rich (non-monochromatic) icons
// (https://github.com/ckeditor/ckeditor5/issues/12599).
'ck-reset_all-excluded',

// The class to remove the dynamic color inheritance is toggleable
// (https://github.com/ckeditor/ckeditor5/issues/12599).
bind.if( 'isColorInherited', 'ck-icon_inherit-color' )
],
viewBox: bind.to( 'viewBox' )
}
Expand Down Expand Up @@ -108,6 +140,14 @@ export default class IconView extends View {
this.viewBox = viewBox;
}

// Preserve presentational attributes of the <svg> element from the source.
// They can affect rendering of the entire icon (https://github.com/ckeditor/ckeditor5/issues/12597).
for ( const { name, value } of Array.from( svg.attributes ) ) {
if ( IconView.presentationalAttributeNames.includes( name ) ) {
this.element!.setAttribute( name, value );
}
}

while ( this.element!.firstChild ) {
this.element!.removeChild( this.element!.firstChild );
}
Expand All @@ -131,3 +171,23 @@ export default class IconView extends View {
}
}
}

/**
* A list of presentational attributes that can be set on the `<svg>` element and should be preserved
* when the icon {@link module:ui/icon~IconView#content content} is loaded.
*
* See https://www.w3.org/TR/SVG/styling.html#TermPresentationAttribute.
*
* @protected
* @member {Array.<String>} module:ui/icon~IconView.presentationalAttributeNames
*/
IconView.presentationalAttributeNames = [
'alignment-baseline', 'baseline-shift', 'clip-path', 'clip-rule', 'color', 'color-interpolation',
'color-interpolation-filters', 'color-rendering', 'cursor', 'direction', 'display', 'dominant-baseline', 'fill', 'fill-opacity',
'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style',
'font-variant', 'font-weight', 'image-rendering', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start',
'mask', 'opacity', 'overflow', 'paint-order', 'pointer-events', 'shape-rendering', 'stop-color', 'stop-opacity', 'stroke',
'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width',
'text-anchor', 'text-decoration', 'text-overflow', 'text-rendering', 'transform', 'unicode-bidi', 'vector-effect',
'visibility', 'white-space', 'word-spacing', 'writing-mode'
];
32 changes: 32 additions & 0 deletions packages/ckeditor5-ui/tests/icon/iconview.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ describe( 'IconView', () => {
expect( view.fillColor ).to.equal( '' );
} );

it( 'sets #isColorInherited', () => {
expect( view.isColorInherited ).to.be.true;
} );

it( 'creates element from template', () => {
expect( view.element.tagName ).to.equal( 'svg' );
expect( view.element.classList.contains( 'ck' ) ).to.be.true;
Expand All @@ -46,6 +50,18 @@ describe( 'IconView', () => {
} );
} );

describe( 'color inheritance CSS class', () => {
it( 'should toggle depending view#isColorInherited', () => {
expect( view.element.classList.contains( 'ck-icon_inherit-color' ) ).to.be.true;

view.isColorInherited = false;
expect( view.element.classList.contains( 'ck-icon_inherit-color' ) ).to.be.false;

view.isColorInherited = true;
expect( view.element.classList.contains( 'ck-icon_inherit-color' ) ).to.be.true;
} );
} );

describe( 'inline svg', () => {
it( 'should react to changes in view#content', () => {
assertIconInnerHTML( view, '' );
Expand Down Expand Up @@ -85,6 +101,22 @@ describe( 'IconView', () => {
sinon.assert.notCalled( innerHTMLSpy.set );
sinon.assert.calledTwice( removeChildSpy );
} );

describe( 'preservation of presentational attributes on the <svg> element', () => {
it( 'should use the static list of attributes from the IconView class', () => {
expect( IconView.presentationalAttributeNames ).to.have.length( 58 );
} );

for ( const attributeName of IconView.presentationalAttributeNames ) {
it( `should work for the "${ attributeName }" attribute`, () => {
view.content = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" ${ attributeName }="${ attributeName }-value">
<g id="test"></g>
</svg>`;

expect( view.element.getAttribute( attributeName ), attributeName ).to.equal( attributeName + '-value' );
} );
}
} );
} );

describe( 'fill color', () => {
Expand Down
30 changes: 26 additions & 4 deletions packages/ckeditor5-ui/tests/manual/icon/icon.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,56 @@
width: 200px;
text-align: center;
}

#icons-rich, #icons-rich-reference {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 30px;
width: fit-content;
}

#icons-rich .ck-icon,
#icons-rich-reference svg {
width: 120px;
height: 120px;
}
</style>
</head>

<div class="test-cases">
<h2>Icon sizing</h2>
<h2>Monochrome icons</h2>
<h3>Icon sizing</h3>

<ol>
<li id="icon20"></li>
<li id="icon40"></li>
<li id="icon60"></li>
</ol>

<h2>Icon colors</h2>
<h3>Icon colors</h3>

<ol>
<li>icon <span id="icon-red"></span> has own color</li>
<li style="color: blue">icon <span id="icon-blue-inherited"></span> inherits color</li>
</ol>

<h2>Icon with dirty markup</h2>
<h3>Icon with dirty markup</h3>

<ol>
<li id="icon-dirty"></li>
</ol>

<h2>Icon with own viewBox</h2>
<h3>Icon with own viewBox</h3>

<ol>
<li id="icon-view-box"></li>
</ol>

<h2>Colorful icons with <code>IconView</code> component (attributes on &lt;svg&gt;, in <code>.ck-reset_all</code> parent)</h2>

<div id="icons-rich" class="ck ck-reset_all"></div>

<h4>Reference icons (raw)</h4>

<div id="icons-rich-reference"></div>
</div>
Loading

0 comments on commit 57100fa

Please sign in to comment.