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
52 changes: 52 additions & 0 deletions packages/icon/src/vaadin-icon-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @license
* Copyright (c) 2016 - 2023 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/

import { isSafari } from '@vaadin/component-base/src/browser-utils.js';

/**
* Checks if the current browser supports CSS Container Query units for pseudo elements.
* i.e. if the fix for https://bugs.webkit.org/show_bug.cgi?id=253939 is available.
*/
export function supportsCQUnitsForPseudoElements() {
const testStyle = document.createElement('style');
testStyle.textContent = `
.vaadin-icon-test-element {
container-type: size;
height: 2px;
visibility: hidden;
position: fixed;
}

.vaadin-icon-test-element::before {
content: '';
display: block;
height: 100cqh;
`;
const testElement = document.createElement('div');
testElement.classList.add('vaadin-icon-test-element');

document.body.append(testStyle, testElement);
const { height } = getComputedStyle(testElement, '::before');
testStyle.remove();
testElement.remove();
return height === '2px';
}

/**
* Checks if the current browser needs a fallback for sizing font icons instead of relying on CSS Container Queries.
*/
export function needsFontIconSizingFallback() {
if (!CSS.supports('container-type: inline-size')) {
// The browser does not support CSS Container Queries at all.
return true;
}
if (!isSafari) {
// Browsers other than Safari support CSS Container Queries as expected.
return false;
}
// Check if the browser does not support CSS Container Query units for pseudo elements.
return !supportsCQUnitsForPseudoElements();
}
12 changes: 5 additions & 7 deletions packages/icon/src/vaadin-icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,18 @@
*/
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
import { svg } from 'lit';
import { isSafari } from '@vaadin/component-base/src/browser-utils.js';
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
import { SlotStylesMixin } from '@vaadin/component-base/src/slot-styles-mixin.js';
import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
import { needsFontIconSizingFallback } from './vaadin-icon-helpers.js';
import { ensureSvgLiteral, renderSvg, unsafeSvgLiteral } from './vaadin-icon-svg.js';
import { Iconset } from './vaadin-iconset.js';

const supportsCSSContainerQueries = CSS.supports('container-type: inline-size');
const needsFontIconSizingFallback = !supportsCSSContainerQueries || isSafari;
// ResizeObserver-based fallback for browsers without CSS Container Queries support (Safari 15)
// or buggy Container Queries support for pseudo elements (Safari 16)
const ConditionalResizeMixin = (superClass) => (needsFontIconSizingFallback ? ResizeMixin(superClass) : superClass);
const usesFontIconSizingFallback = needsFontIconSizingFallback();
const ConditionalResizeMixin = (superClass) => (usesFontIconSizingFallback ? ResizeMixin(superClass) : superClass);

/**
* `<vaadin-icon>` is a Web Component for displaying SVG icons.
Expand Down Expand Up @@ -389,7 +386,8 @@ class Icon extends ThemableMixin(
* @override
*/
_onResize() {
if (needsFontIconSizingFallback && (this.char || this.font)) {
if (usesFontIconSizingFallback && (this.char || this.font)) {
// ResizeObserver-based fallback for sizing font icons.
const { paddingTop, paddingBottom, height } = getComputedStyle(this);
const fontIconSize = parseFloat(height) - parseFloat(paddingTop) - parseFloat(paddingBottom);
this.style.setProperty('--_vaadin-font-icon-size', `${fontIconSize}px`);
Expand Down
39 changes: 27 additions & 12 deletions packages/icon/test/icon-font.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { expect } from '@esm-bundle/chai';
import { fixtureSync, nextFrame } from '@vaadin/testing-helpers';
import { fixtureSync, isChrome, nextFrame } from '@vaadin/testing-helpers';
import sinon from 'sinon';
import '../vaadin-icon.js';
import { isSafari } from '@vaadin/component-base/src/browser-utils.js';
import { needsFontIconSizingFallback, supportsCQUnitsForPseudoElements } from '../src/vaadin-icon-helpers.js';
import { iconFontCss } from './test-icon-font.js';

const usesFontIconSizingFallback = !CSS.supports('container-type: inline-size') || isSafari;

/**
* Resolves once the function is invoked on the given object.
*/
Expand All @@ -24,7 +22,7 @@ function onceInvoked(object, functionName) {
* Resolves once the icon resize is complete.
*/
async function onceResized(icon) {
if (usesFontIconSizingFallback) {
if (needsFontIconSizingFallback()) {
await onceInvoked(icon, '_onResize');
}
}
Expand Down Expand Up @@ -182,35 +180,52 @@ describe('vaadin-icon - icon fonts', () => {

// These tests make sure that the heavy container query fallback is only used
// when font icons are used.
(usesFontIconSizingFallback ? describe : describe.skip)('container query fallback', () => {
describe('container query fallback', () => {
// Tests for browsers that require the fallback
const fallBackIt = needsFontIconSizingFallback() ? it : it.skip;
// Tests for browsers that we know for sure not to require the fallback
const supportedIt = isChrome ? it : it.skip;

let icon;

it('should have the custom property (font)', async () => {
supportedIt('should support CQ width units on pseudo elements', async () => {
expect(supportsCQUnitsForPseudoElements()).to.be.true;
});

supportedIt('should not need the fallback', async () => {
expect(needsFontIconSizingFallback()).to.be.false;
});

fallBackIt('should not support CQ width units on pseudo elements', async () => {
expect(supportsCQUnitsForPseudoElements()).to.be.false;
});

fallBackIt('should have the custom property (font)', async () => {
icon = fixtureSync('<vaadin-icon font="foo"></vaadin-icon>');
await nextFrame();
expect(icon.style.getPropertyValue('--_vaadin-font-icon-size')).to.equal('24px');
});

it('should have the custom property (char)', async () => {
fallBackIt('should have the custom property (char)', async () => {
icon = fixtureSync('<vaadin-icon char="foo"></vaadin-icon>');
await nextFrame();
expect(icon.style.getPropertyValue('--_vaadin-font-icon-size')).to.equal('24px');
});

it('should not have the custom property', async () => {
fallBackIt('should not have the custom property', async () => {
icon = fixtureSync('<vaadin-icon></vaadin-icon>');
await nextFrame();
expect(icon.style.getPropertyValue('--_vaadin-font-icon-size')).to.equal('');
});

it('should set the custom property', async () => {
fallBackIt('should set the custom property', async () => {
icon = fixtureSync('<vaadin-icon></vaadin-icon>');
await nextFrame();
icon.font = 'foo';
expect(icon.style.getPropertyValue('--_vaadin-font-icon-size')).to.equal('24px');
});

it('should update the custom property', async () => {
fallBackIt('should update the custom property', async () => {
icon = fixtureSync('<vaadin-icon font="foo"></vaadin-icon>');
await nextFrame();
icon.style.width = '100px';
Expand All @@ -219,7 +234,7 @@ describe('vaadin-icon - icon fonts', () => {
expect(icon.style.getPropertyValue('--_vaadin-font-icon-size')).to.equal('100px');
});

it('should not update the custom property', async () => {
fallBackIt('should not update the custom property', async () => {
icon = fixtureSync('<vaadin-icon></vaadin-icon>');
await nextFrame();
icon.style.width = '100px';
Expand Down