From b1c8582daddda4ab24f4c099fd15ae05209f1531 Mon Sep 17 00:00:00 2001 From: Cee Chen <549407+cee-chen@users.noreply.github.com> Date: Tue, 10 Oct 2023 09:45:35 -0700 Subject: [PATCH] [Emotion] Reduce CSS browser prefixing to supported browsers only (#7272) --- package.json | 1 + src-docs/src/components/codesandbox/link.js | 3 +- src-docs/src/views/app_context.js | 3 + .../src/views/provider/provider_example.js | 44 +- .../src/views/provider/provider_styles.tsx | 9 +- .../__snapshots__/bottom_bar.test.tsx.snap | 50 +- .../button/__snapshots__/button.test.tsx.snap | 2 +- .../__snapshots__/button_empty.test.tsx.snap | 2 +- .../__snapshots__/button_icon.test.tsx.snap | 2 +- .../collapsible_nav_group.test.tsx.snap | 2 +- .../__snapshots__/control_bar.test.tsx.snap | 2 +- .../overlay_mask/overlay_mask.test.tsx | 2 +- .../__snapshots__/page_template.test.tsx.snap | 2 +- .../page_bottom_bar.test.tsx.snap | 20 +- .../portal/__snapshots__/portal.test.tsx.snap | 4 +- src/components/provider/provider.test.tsx | 12 +- src/components/provider/provider.tsx | 15 +- src/services/emotion/css.test.ts | 36 ++ src/services/emotion/css.ts | 25 + src/services/emotion/index.ts | 1 + src/services/emotion/prefixer.test.tsx | 567 ++++++++++++++++++ src/services/emotion/prefixer.ts | 120 ++++ .../__snapshots__/provider.test.tsx.snap | 12 +- src/services/theme/provider.tsx | 15 +- src/services/theme/utils.ts | 2 +- upcoming_changelogs/7272.md | 3 + yarn.lock | 5 + 27 files changed, 875 insertions(+), 86 deletions(-) create mode 100644 src/services/emotion/css.test.ts create mode 100644 src/services/emotion/css.ts create mode 100644 src/services/emotion/prefixer.test.tsx create mode 100644 src/services/emotion/prefixer.ts create mode 100644 upcoming_changelogs/7272.md diff --git a/package.json b/package.json index fbb333bcbde..a706480cee8 100644 --- a/package.json +++ b/package.json @@ -150,6 +150,7 @@ "@types/react-dom": "^18.2.6", "@types/react-is": "^17.0.3", "@types/react-router-dom": "^5.3.3", + "@types/stylis": "^4.2.1", "@types/testing-library__jest-dom": "^5.14.3", "@types/url-parse": "^1.4.8", "@types/uuid": "^8.3.0", diff --git a/src-docs/src/components/codesandbox/link.js b/src-docs/src/components/codesandbox/link.js index ef9d3297ea2..214a8aae8de 100644 --- a/src-docs/src/components/codesandbox/link.js +++ b/src-docs/src/components/codesandbox/link.js @@ -169,12 +169,13 @@ import '@elastic/charts/dist/theme_only_${colorMode}.css';` import React from 'react'; import { createRoot } from 'react-dom/client'; import createCache from '@emotion/cache'; -import { EuiProvider } from '@elastic/eui'; +import { EuiProvider, euiStylisPrefixer } from '@elastic/eui'; import { Demo } from './demo'; const cache = createCache({ key: 'codesandbox', + stylisPlugins: [euiStylisPrefixer], container: document.querySelector('meta[name="emotion-styles"]'), }); cache.compat = true; diff --git a/src-docs/src/views/app_context.js b/src-docs/src/views/app_context.js index 94495219134..1e38535ec8b 100644 --- a/src-docs/src/views/app_context.js +++ b/src-docs/src/views/app_context.js @@ -8,6 +8,7 @@ import { translateUsingPseudoLocale } from '../services'; import { getLocale } from '../store'; import { EuiContext, EuiProvider } from '../../../src/components'; +import { euiStylisPrefixer } from '../../../src/services'; import { EUI_THEMES } from '../../../src/themes'; import favicon16Prod from '../images/favicon/prod/favicon-16x16.png'; @@ -19,11 +20,13 @@ import favicon96Dev from '../images/favicon/dev/favicon-96x96.png'; const generalEmotionCache = createCache({ key: 'css', + stylisPlugins: [euiStylisPrefixer], container: document.querySelector('meta[name="emotion-styles"]'), }); generalEmotionCache.compat = true; const utilityCache = createCache({ key: 'util', + stylisPlugins: [euiStylisPrefixer], container: document.querySelector('meta[name="emotion-styles-utility"]'), }); diff --git a/src-docs/src/views/provider/provider_example.js b/src-docs/src/views/provider/provider_example.js index c01f8a9b524..3d2d7b488c2 100644 --- a/src-docs/src/views/provider/provider_example.js +++ b/src-docs/src/views/provider/provider_example.js @@ -96,21 +96,41 @@ export const ProviderExample = { {''} -

- @emotion/cache and style injection location -

+

@emotion/cache customization

- In the case that your app has its own static stylesheet,{' '} - @emotion styles may not be injected into the - correct location in the {''}, causing - unintentional overrides or unapplied styles.{' '} - - The @emotion/cache library + The{' '} + + @emotion/cache library {' '} - provides configuration options that help with specifying the - injection location. We recommend using {''}{' '} - tags to achieve this. + provides extra configuration options for EUI's CSS-in-JS behavior:

+ diff --git a/src-docs/src/views/provider/provider_styles.tsx b/src-docs/src/views/provider/provider_styles.tsx index 9b4ca730d41..c32fdbfe1f9 100644 --- a/src-docs/src/views/provider/provider_styles.tsx +++ b/src-docs/src/views/provider/provider_styles.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { EuiCodeBlock, EuiSpacer, useEuiTheme } from '../../../../src'; +import { EuiCodeBlock, useEuiTheme } from '../../../../src'; export default () => { const { colorMode } = useEuiTheme(); @@ -22,18 +22,17 @@ export default () => { `} - - {`// App.js -import { EuiProvider } from '@elastic/eui' +import { EuiProvider, euiStylisPrefixer } from '@elastic/eui' import createCache from '@emotion/cache'; const euiCache = createCache({ key: 'eui', + stylisPlugins: [euiStylisPrefixer], container: document.querySelector('meta[name="eui-style-insert"]'), }); -cache.compat = true; +euiCache.compat = true; {/* Content */} diff --git a/src/components/bottom_bar/__snapshots__/bottom_bar.test.tsx.snap b/src/components/bottom_bar/__snapshots__/bottom_bar.test.tsx.snap index fe8ec160fdd..d890fdb1121 100644 --- a/src/components/bottom_bar/__snapshots__/bottom_bar.test.tsx.snap +++ b/src/components/bottom_bar/__snapshots__/bottom_bar.test.tsx.snap @@ -6,12 +6,12 @@ exports[`EuiBottomBar is rendered 1`] = ` >
@@ -38,12 +38,12 @@ exports[`EuiBottomBar props affordForDisplacement can be false 1`] = ` >

`; diff --git a/src/components/control_bar/__snapshots__/control_bar.test.tsx.snap b/src/components/control_bar/__snapshots__/control_bar.test.tsx.snap index a943f8ee0b8..2a29d3c7006 100644 --- a/src/components/control_bar/__snapshots__/control_bar.test.tsx.snap +++ b/src/components/control_bar/__snapshots__/control_bar.test.tsx.snap @@ -49,7 +49,7 @@ exports[`EuiControlBar is rendered 1`] = `

@@ -34,7 +34,7 @@ exports[`_EuiPageBottomBar paddingSize l is rendered 1`] = `

Content diff --git a/src/components/provider/provider.test.tsx b/src/components/provider/provider.test.tsx index 84fc7a9c96e..0f61e2d61e2 100644 --- a/src/components/provider/provider.test.tsx +++ b/src/components/provider/provider.test.tsx @@ -66,14 +66,22 @@ describe('EuiProvider', () => { ) as HTMLStyleElement; }; - it('uses a default cache from Emotion when configured without a cache', () => { - render(); + it('uses a default fallback cache with EUI prefixing when one is not passed', () => { + render( + +
+ + ); expect(emotionCache.key).toEqual('css'); expect(getStyleByCss('html').dataset.emotion).toEqual('css-global'); expect(getStyleByCss('.eui-displayBlock').dataset.emotion).toEqual( 'css-global' ); + // The below CSS would have prefixes if the default `@emotion/css` cache were used + expect(getStyleByCss('test-no-cache').textContent).toMatchInlineSnapshot( + `".css-1b3dqg7-test-no-cache{display:flex;}"` + ); }); it('applies the cache to all styles', () => { diff --git a/src/components/provider/provider.tsx b/src/components/provider/provider.tsx index c9e39613e70..69403787511 100644 --- a/src/components/provider/provider.tsx +++ b/src/components/provider/provider.tsx @@ -7,13 +7,8 @@ */ import React, { PropsWithChildren } from 'react'; -import { cache as fallbackCache, EmotionCache } from '@emotion/css'; +import type { EmotionCache } from '@emotion/css'; -import { - EuiGlobalStyles, - EuiGlobalStylesProps, -} from '../../global_styling/reset/global_styles'; -import { EuiUtilityClasses } from '../../global_styling/utility/utility'; import { EuiThemeProvider, EuiThemeProviderProps, @@ -21,7 +16,15 @@ import { CurrentEuiBreakpointProvider, } from '../../services'; import { emitEuiProviderWarning } from '../../services/theme/warning'; +import { cache as fallbackCache } from '../../services/emotion/css'; + +import { + EuiGlobalStyles, + EuiGlobalStylesProps, +} from '../../global_styling/reset/global_styles'; +import { EuiUtilityClasses } from '../../global_styling/utility/utility'; import { EuiThemeAmsterdam } from '../../themes'; + import { EuiCacheProvider } from './cache'; import { EuiProviderNestedCheck, useIsNestedEuiProvider } from './nested'; import { diff --git a/src/services/emotion/css.test.ts b/src/services/emotion/css.test.ts new file mode 100644 index 00000000000..2e2bab76422 --- /dev/null +++ b/src/services/emotion/css.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { css, cx } from './css'; + +describe('custom EUI Emotion instance', () => { + it("creates a vanilla JS className that contains styles with EUI's custom configuration", () => { + const test = css` + display: flex; + `; + expect(test).toEqual('css-vyoujf'); + + // Should not have any extra browser prefixes, per EUI's configuration + const styleOutput = document.head.querySelector('style[data-emotion]')!; + expect(styleOutput.textContent).toEqual('.css-vyoujf{display:flex;}'); + }); + + // NOTE: Currently, custom Emotion instances do *not* merge css auto labels + // @see https://github.com/emotion-js/emotion/issues/3113 + it('correctly merges css with labels', () => { + const test1 = css` + label: hello; + color: red; + `; + const test2 = css` + label: world; + background-color: blue; + `; + expect(cx(test1, test2)).toEqual('css-4dyepw-hello-world'); + }); +}); diff --git a/src/services/emotion/css.ts b/src/services/emotion/css.ts new file mode 100644 index 00000000000..0c3193996bf --- /dev/null +++ b/src/services/emotion/css.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import createEmotion from '@emotion/css/create-instance'; + +import { euiStylisPrefixer } from './prefixer'; + +/** + * This custom instance is needed for internal EUI components to call + * `@emotion/css` with EUI's custom prefixer plugin + * @see https://emotion.sh/docs/@emotion/css#custom-instances + * + * NOTE: Usage is currently being beta tested internally, + * and is not yet intended to be a public export + */ +export const { css, cx, cache } = createEmotion({ + key: 'css', + stylisPlugins: [euiStylisPrefixer], + speedy: false, +}); diff --git a/src/services/emotion/index.ts b/src/services/emotion/index.ts index d7a50a60227..2fb45487285 100644 --- a/src/services/emotion/index.ts +++ b/src/services/emotion/index.ts @@ -7,3 +7,4 @@ */ export * from './clone_element'; +export * from './prefixer'; diff --git a/src/services/emotion/prefixer.test.tsx b/src/services/emotion/prefixer.test.tsx new file mode 100644 index 00000000000..867e611290e --- /dev/null +++ b/src/services/emotion/prefixer.test.tsx @@ -0,0 +1,567 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FunctionComponent, PropsWithChildren } from 'react'; +import { render } from '@testing-library/react'; +import { css, keyframes } from '@emotion/react'; +import { cache as defaultEmotionCache } from '@emotion/css'; +import createCache from '@emotion/cache'; + +import { EuiProvider } from '../../components/provider'; + +import { euiStylisPrefixer } from './prefixer'; + +describe('euiStylisPrefixer', () => { + const cacheWithPrefixer = createCache({ + key: 'test', + stylisPlugins: [euiStylisPrefixer], + }); + + const wrapper: FunctionComponent = ({ children }) => ( + + {children} + + ); + + const getStyleCss = (label: string) => { + const styleEl = Array.from( + document.querySelectorAll('style[data-emotion]') + ).find((el) => el?.textContent?.includes(label)) as HTMLStyleElement; + if (!styleEl) return; + + // Make output styles a little easier to read + return styleEl + .textContent!.replace('{', ' {\n') + .replace(/;/g, ';\n') + .replace(/:/g, ': '); + }; + + describe('does prefix', () => { + test('user-select', () => { + render( +
, + { wrapper } + ); + expect(getStyleCss('user-select')).toMatchInlineSnapshot(` + ".test-8c1x7t-user-select { + -webkit-user-select: none; + user-select: none; + }" + `); + }); + + test('text-decoration', () => { + render( +
, + { wrapper } + ); + expect(getStyleCss('text-decoration')).toMatchInlineSnapshot(` + ".test-5idn3j-text-decoration { + -webkit-text-decoration: line-through dashed blue; + text-decoration: line-through dashed blue; + text-decoration-line: underline overline; + text-decoration-style: wavy; + text-decoration-color: red; + text-decoration-skip: objects; + }" + `); + }); + + test('text-size-adjust', () => { + render( +
, + { wrapper } + ); + expect(getStyleCss('text-size-adjust')).toMatchInlineSnapshot(` + ".test-15dfadm { + -webkit-text-size-adjust: 80%; + text-size-adjust: 80%; + }" + `); + }); + + test('box-decoration-break', () => { + render( +
, + { wrapper } + ); + expect(getStyleCss('box-decoration-break')).toMatchInlineSnapshot(` + ".test-m64wfr-box-decoration-break { + -webkit-box-decoration-break: slice; + box-decoration-break: slice; + }" + `); + }); + + describe('mask CSS', () => { + render( +
, + { wrapper } + ); + expect(getStyleCss('mask-css')).toMatchInlineSnapshot(` + ".test-16l1rpr-mask-css { + -webkit-mask: url(mask.svg); + mask: url(mask.svg); + -webkit-mask-image: linear-gradient(rgba(0, 0, 0, 1), transparent); + mask-image: linear-gradient(rgba(0, 0, 0, 1), transparent); + -webkit-mask-clip: border-box; + mask-clip: border-box; + -webkit-mask-origin: padding-box; + mask-origin: padding-box; + -webkit-mask-composite: subtract; + mask-composite: subtract; + -webkit-mask-mode: alpha; + mask-mode: alpha; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-repeat: repeat-x; + mask-repeat: repeat-x; + -webkit-mask-size: contain; + mask-size: contain; + }" + `); + }); + + test('background-clip text', () => { + render( +
, + { wrapper } + ); + expect(getStyleCss('background-clip')).toMatchInlineSnapshot(` + ".test-9vijyk-background-clip { + background-clip: content-box; + -webkit-background-clip: text; + background-clip: text; + }" + `); + }); + + test('print-color-adjust', () => { + render( +
, + { wrapper } + ); + expect(getStyleCss('print-color-adjust')).toMatchInlineSnapshot(` + ".test-cx4oo-print-color-adjust { + -webkit-print-color-adjust: economy; + print-color-adjust: economy; + }" + `); + }); + + test('max-content, min-content, fit-content, and stretch sizing values', () => { + render( +
, + { wrapper } + ); + expect(getStyleCss('intrinsic-extrensic-sizing')).toMatchInlineSnapshot(` + ".test-tsfq3u-intrinsic-extrensic-sizing { + height: max-content; + width: min-content; + max-inline-size: fit-content; + min-block-size: -webkit-fill-available; + min-block-size: -moz-available; + min-block-size: stretch; + }" + `); + }); + }); + + describe('does not prefix', () => { + test('flex CSS', () => { + render( +
, + { wrapper } + ); + + expect(getStyleCss('no-flex-prefixes')).toMatchInlineSnapshot(` + ".test-1467bft-no-flex-prefixes { + display: flex; + display: inline-flex; + align-items: center; + align-content: center; + align-self: center; + justify-content: center; + flex-shrink: 0; + flex-grow: 0; + flex-basis: 100%; + flex: 1; + flex-direction: column; + order: 2; + }" + `); + }); + + test('transform & transition CSS', () => { + render( +
, + { wrapper } + ); + + expect(getStyleCss('no-transform-prefixes')).toMatchInlineSnapshot(` + ".test-1oc6c8-no-transform-prefixes { + transform: translateY(-1px); + transition: transform 2s linear; + }" + `); + }); + + test('animation CSS', () => { + const testAnimation = keyframes` + from { opacity: 0; } + to { opacity: 1; } + `; + render( +
, + { wrapper } + ); + + expect(getStyleCss('no-animation-prefixes')).toMatchInlineSnapshot(` + ".test-1aw2200-no-animation-prefixes { + animation: animation-1flhruc; + animation-name: test; + animation-delay: 1s; + animation-direction: reverse; + animation-duration: 50ms; + animation-fill-mode: both; + animation-iteration-count: infinite; + animation-play-state: paused; + animation-timing-function: ease-in-out; + }" + `); + expect(getStyleCss('@keyframes')).toBeTruthy(); + expect(getStyleCss('@-webkit-keyframes')).toBeFalsy(); + }); + + test('position sticky', () => { + render( +
, + { wrapper } + ); + + expect(getStyleCss('no-position-sticky-prefix')).toMatchInlineSnapshot(` + ".test-11x1k54-no-position-sticky-prefix { + position: sticky; + }" + `); + }); + + test('writing mode CSS', () => { + render( +
, + { wrapper } + ); + + expect(getStyleCss('no-writing-mode-prefixes')).toMatchInlineSnapshot(` + ".test-1d4iba8-no-writing-mode-prefixes { + writing-mode: vertical-lr; + writing-mode: vertical-rl; + writing-mode: horizontal-tb; + }" + `); + }); + + test('inline logical properties CSS', () => { + render( +
, + { wrapper } + ); + expect(getStyleCss('no-logical-properties-prefixes')) + .toMatchInlineSnapshot(` + ".test-11697m3-no-logical-properties-prefixes { + padding-inline-start: 1rem; + margin-inline-end: 2em; + }" + `); + }); + + test('columns CSS', () => { + render( +
, + { wrapper } + ); + + expect(getStyleCss('no-columns-prefixes')).toMatchInlineSnapshot(` + ".test-1eossg4-no-columns-prefixes { + columns: 3; + column-count: 5; + column-fill: balance; + column-gap: 10px; + column-width: 20px; + column-span: all; + column-rule: blue dotted 2px; + }" + `); + }); + + test('misc text effect CSS', () => { + render( +
, + { wrapper } + ); + expect(getStyleCss('no-misc-text-effect-prefixes')) + .toMatchInlineSnapshot(` + ".test-29xaas-no-misc-text-effect-prefixes { + appearance: none; + hyphens: auto; + cursor: grab; + }" + `); + }); + + test('misc filter/effect/image CSS', () => { + render( +
, + { wrapper } + ); + + expect(getStyleCss('no-filter-prefixes')).toMatchInlineSnapshot(` + ".test-8ziglt-no-filter-prefixes { + filter: grayscale(1); + clip-path: url(#foo); + backface-visibility: visible; + background: image-set( + linear-gradient(blue, white) 1x, + linear-gradient(blue, green) 2x + ); + }" + `); + }); + + test('misc selectors', () => { + render( +
, + { wrapper } + ); + + expect(getStyleCss('::-moz-placeholder')).toBeFalsy(); + expect(getStyleCss(':-moz-read-only')).toBeFalsy(); + expect(getStyleCss(':-moz-read-write')).toBeFalsy(); + }); + }); + + describe('default Emotion cache', () => { + it('prefixes extra CSS that the EUI plugin does not', () => { + render( + +
+ + ); + + expect(getStyleCss('test-default-cache')).toMatchInlineSnapshot(` + ".css-tfft1m-test-default-cache { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-padding-start: 1rem; + padding-inline-start: 1rem; + -webkit-margin-end: 2em; + margin-inline-end: 2em; + -webkit-animation: something; + animation: something; + -webkit-transform: translateY(-1px); + -moz-transform: translateY(-1px); + -ms-transform: translateY(-1px); + transform: translateY(-1px); + -webkit-transition: -webkit-transform 2s linear; + transition: transform 2s linear; + position: -webkit-sticky; + position: sticky; + -webkit-writing-mode: vertical-rl; + -ms-writing-mode: tb-rl; + writing-mode: vertical-rl; + -webkit-column-count: 2; + column-count: 2; + block-size: -webkit-max-content; + block-size: -moz-max-content; + block-size: max-content; + -webkit-filter: blur(5px); + filter: blur(5px); + cursor: -webkit-grab; + cursor: grab; + }" + `); + expect(getStyleCss('::-moz-placeholder')).toBeTruthy(); + }); + }); +}); diff --git a/src/services/emotion/prefixer.ts b/src/services/emotion/prefixer.ts new file mode 100644 index 00000000000..c1c6259c5a4 --- /dev/null +++ b/src/services/emotion/prefixer.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + charat, + DECLARATION, + hash, + indexof, + MOZ, + replace, + strlen, + WEBKIT, + type Element, +} from 'stylis'; + +// This is a heavily modified version of Emotion's default `prefixer` plugin +// (mostly removing unnecessary prefixes), which is in turn a modified version +// of stylis's default prefixer. +// @see https://github.com/emotion-js/emotion/blob/main/packages/cache/src/prefixer.js +/* eslint-disable prefer-template */ + +/** + * This is a stylis plugin which handles auto-prefixing CSS output by Emotion. + * + * *Please note*: EUI/Elastic targets latest evergreen browsers for support only. + * @see https://www.elastic.co/support/matrix#matrix_browsers + */ +export const euiStylisPrefixer = (element: Element) => { + if (element.length > -1) + if (!element.return) + switch (element.type) { + case DECLARATION: + element.return = prefix(element.value, element.length); + break; + } +}; + +const prefix = (value: Element['value'], length: Element['length']): string => { + switch (hash(value, length)) { + /** + * `-webkit` prefixes + */ + // user-select - https://caniuse.com/mdn-css_properties_user-select - needed by Safari + case 4246: + // text-decoration - https://caniuse.com/text-decoration - iOS Safari is the main one that needs this + case 5572: + // text-size-adjust - https://caniuse.com/text-size-adjust - iOS Safari + case 2756: + // box-decoration-break - https://caniuse.com/css-boxdecorationbreak - Chrome & Safari + case 3005: + // mask, mask-image, mask-(mode|clip|size), mask-(repeat|origin), mask-position, mask-composite - Chrome + case 6391: + case 5879: + case 5623: + case 6135: + case 4599: + case 4855: + // print-color-adjust - https://caniuse.com/css-color-adjust - Chrome + case 2282: + return WEBKIT + value + value; + + // background-clip - https://caniuse.com/background-clip-text - Chrome, only for `text` value + case 4215: + if (~indexof(value, 'text')) { + return WEBKIT + value + value; + } + + /** + * Intrinsic/extrinsic sizing value prefixes + * `stretch` alternatives needed by Chrome & Firefox - https://caniuse.com/intrinsic-width + */ + // (min|max)?(width|height|inline-size|block-size) + case 8116: + case 7059: + case 5753: + case 5535: + case 5445: + case 5701: + case 4933: + case 4677: + case 5533: + case 5789: + case 5021: + case 4765: + // stretch, max-content, min-content, fill-available + if (strlen(value) - 1 - length > 6) + switch (charat(value, length + 1)) { + // (f)ill-available + case 102: + if (~indexof(value, 'fill-available')) { + return replace( + value, + /(.+:)(.+)-([^]+)/, + '$1' + + WEBKIT + + '$2-$3' + + '$1' + + MOZ + + (charat(value, length + 3) === 108 ? '$3' : '$2-$3') + ); + } + // (s)tretch + case 115: + if (~indexof(value, 'stretch')) { + return ( + prefix(replace(value, 'stretch', 'fill-available'), length) + + value + ); + } + } + break; + } + + return value; +}; diff --git a/src/services/theme/__snapshots__/provider.test.tsx.snap b/src/services/theme/__snapshots__/provider.test.tsx.snap index 47336d01d76..5d8598c2489 100644 --- a/src/services/theme/__snapshots__/provider.test.tsx.snap +++ b/src/services/theme/__snapshots__/provider.test.tsx.snap @@ -2,7 +2,7 @@ exports[`EuiThemeProvider CSS variables allows child components to set non-global theme CSS variables 1`] = ` `; @@ -11,7 +11,7 @@ exports[`EuiThemeProvider nested EuiThemeProviders allows avoiding the extra spa Top-level provider
clone provider color onto div
@@ -23,7 +23,7 @@ exports[`EuiThemeProvider nested EuiThemeProviders allows customizing the span w Top-level provider Nested @@ -36,15 +36,15 @@ exports[`EuiThemeProvider nested EuiThemeProviders renders with a span wrapper t Top-level provider Nested Double nested Triple nested diff --git a/src/services/theme/provider.tsx b/src/services/theme/provider.tsx index 0005f356d9f..76fab79de5b 100644 --- a/src/services/theme/provider.tsx +++ b/src/services/theme/provider.tsx @@ -16,13 +16,12 @@ import React, { PropsWithChildren, HTMLAttributes, } from 'react'; -import classNames from 'classnames'; -import { css } from '@emotion/css'; import { Global, type CSSObject } from '@emotion/react'; import isEqual from 'lodash/isEqual'; import type { CommonProps } from '../../components/common'; import { cloneElementWithCss } from '../emotion'; +import { css, cx } from '../emotion/css'; import { EuiSystemContext, @@ -160,7 +159,7 @@ export const EuiThemeProvider = ({ ? false : bodyColor !== theme.colors.text, colorClassName: css` - label: euiColorMode-${_colorMode}; + label: euiColorMode-${_colorMode || colorMode}; color: ${theme.colors.text}; `, setGlobalCSSVariables: isGlobalTheme @@ -177,6 +176,7 @@ export const EuiThemeProvider = ({ isGlobalTheme, bodyColor, _colorMode, + colorMode, setGlobalCSSVariables, globalCSSVariables, setThemeCSSVariables, @@ -191,7 +191,7 @@ export const EuiThemeProvider = ({ const { cloneElement, className, ...rest } = wrapperProps || {}; const props = { ...rest, - className: classNames(className, nestedThemeContext.colorClassName), + className: cx(className, nestedThemeContext.colorClassName), }; // Condition avoids rendering an empty Emotion selector if no // theme-specific CSS variables have been set by child components @@ -202,14 +202,11 @@ export const EuiThemeProvider = ({ if (cloneElement) { return cloneElementWithCss(children, { ...props, - className: classNames(children.props.className, props.className), + className: cx(children.props.className, props.className), }); } else { return ( - + {children} ); diff --git a/src/services/theme/utils.ts b/src/services/theme/utils.ts index 089f985a6ee..c7c9c5216d0 100644 --- a/src/services/theme/utils.ts +++ b/src/services/theme/utils.ts @@ -39,7 +39,7 @@ export const isInverseColorMode = ( /** * Returns the color mode configured in the current EuiThemeProvider. * Returns the parent color mode if none is explicity set. - * @param {string} coloMode - `light`, `dark`, or `inverse` + * @param {string} colorMode - `light`, `dark`, or `inverse` * @param {string} parentColorMode - `LIGHT` or `DARK`; used as the fallback */ export const getColorMode = ( diff --git a/upcoming_changelogs/7272.md b/upcoming_changelogs/7272.md new file mode 100644 index 00000000000..021502d52d9 --- /dev/null +++ b/upcoming_changelogs/7272.md @@ -0,0 +1,3 @@ +**CSS-in-JS conversions** + +- Reduced default CSS prefixes generated by Emotion to only browsers supported by EUI (latest evergreen browsers). This can be customized by passing your own Emotion cache to `EuiProvider`. diff --git a/yarn.lock b/yarn.lock index 895eec82a7d..b811b178a9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5219,6 +5219,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/stylis@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.1.tgz#867fcb0f81719d9ecef533fdda03e32083b959f6" + integrity sha512-OSaMrXUKxVigGlKRrET39V2xdhzlztQ9Aqumn1WbCBKHOi9ry7jKSd7rkyj0GzmWaU960Rd+LpOFpLfx5bMQAg== + "@types/testing-library__jest-dom@^5.14.3", "@types/testing-library__jest-dom@^5.9.1": version "5.14.5" resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz#d113709c90b3c75fdb127ec338dad7d5f86c974f"