From 0ab887aa9c092fd7d169f476b6fcb89db7650eb3 Mon Sep 17 00:00:00 2001 From: Hovhannes Babayan Date: Mon, 24 Feb 2025 16:21:50 +0400 Subject: [PATCH] fix: ensure that S2 style macro produces different class names for different S2 themes --- .../s2/style/__tests__/style-macro.test.js | 24 ++++++++++++++ .../s2/style/spectrum-theme.ts | 2 ++ .../@react-spectrum/s2/style/style-macro.ts | 17 +++++++--- packages/@react-spectrum/s2/style/types.ts | 3 +- packages/@react-spectrum/s2/style/version.ts | 32 +++++++++++++++++++ 5 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 packages/@react-spectrum/s2/style/version.ts diff --git a/packages/@react-spectrum/s2/style/__tests__/style-macro.test.js b/packages/@react-spectrum/s2/style/__tests__/style-macro.test.js index 3762768c1b6..771a3b10a90 100644 --- a/packages/@react-spectrum/s2/style/__tests__/style-macro.test.js +++ b/packages/@react-spectrum/s2/style/__tests__/style-macro.test.js @@ -26,6 +26,30 @@ function testStyle(...args) { } describe('style-macro', () => { + it('should prefix rules with the hash of theme version', () => { + let {css, js} = testStyle({ + alignItems: 'center', + color: 'red-400' + }); + expect(css).toMatchInlineSnapshot(` + "@layer _.a, _.b; + + @layer _.a { + .chc_2c { + align-items: center; + } + + + .chcaJ { + color: light-dark(rgb(255, 188, 180), rgb(115, 24, 11)); + } + } + + " + `); + expect(js).toMatchInlineSnapshot('" chc_2c chcaJ"'); + }); + it('should handle nested css conditions', () => { let {css, js} = testStyle({ marginTop: { diff --git a/packages/@react-spectrum/s2/style/spectrum-theme.ts b/packages/@react-spectrum/s2/style/spectrum-theme.ts index c6c5cdc929c..6a9d57c9803 100644 --- a/packages/@react-spectrum/s2/style/spectrum-theme.ts +++ b/packages/@react-spectrum/s2/style/spectrum-theme.ts @@ -14,6 +14,7 @@ import {ArbitraryValue, CSSProperties, CSSValue, PropertyValueMap} from './types import {autoStaticColor, colorScale, colorToken, fontSizeToken, generateOverlayColorScale, getToken, simpleColorScale, weirdColorToken} from './tokens' with {type: 'macro'}; import {Color, createArbitraryProperty, createColorProperty, createMappedProperty, createRenamedProperty, createSizingProperty, createTheme, parseArbitraryValue} from './style-macro'; import type * as CSS from 'csstype'; +import {getThemeVersion} from './version'; interface MacroContext { addAsset(asset: {type: string, content: string}): void @@ -453,6 +454,7 @@ const fontSize = { } as const; export const style = createTheme({ + version: getThemeVersion(), properties: { // colors color: createColorProperty({ diff --git a/packages/@react-spectrum/s2/style/style-macro.ts b/packages/@react-spectrum/s2/style/style-macro.ts index 4fa4a8d8b16..961a03b791e 100644 --- a/packages/@react-spectrum/s2/style/style-macro.ts +++ b/packages/@react-spectrum/s2/style/style-macro.ts @@ -114,11 +114,11 @@ function mapConditionalShorthand, atStart = false) { +function createValueLookup(values: Array, atStart = false, prefix = '') { let map = new Map(); for (let value of values) { if (!map.has(value)) { - map.set(value, generateName(map.size, atStart)); + map.set(value, prefix + generateName(map.size, atStart)); } } return map; @@ -138,8 +138,9 @@ interface MacroContext { } export function createTheme(theme: T): StyleFunction, 'default' | Extract> { - let themePropertyMap = createValueLookup(Object.keys(theme.properties), true); - let themeConditionMap = createValueLookup(Object.keys(theme.conditions), true); + let themePropertyMap = createValueLookup(Object.keys(theme.properties), true, theme.version); + let themeConditionMap = createValueLookup(Object.keys(theme.conditions), true, theme.version); + let propertyFunctions = new Map(Object.entries(theme.properties).map(([k, v]) => { if (typeof v === 'function') { return [k, v]; @@ -425,6 +426,14 @@ export function createTheme(theme: T): StyleFunction = (value: T, property: string) => PropertyValueD export type ShorthandProperty = (value: T) => {[name: string]: Value}; export interface Theme { + version?: string, properties: { [name: string]: PropertyValueMap | PropertyFunction | string[] }, @@ -53,7 +54,7 @@ type PropertyValue = export type ArbitraryValue = CustomProperty | `[${string}]`; type PropertyValue2 = PropertyValue | ArbitraryValue; type Merge = T extends any ? T : never; -type ShorthandValue = +type ShorthandValue = P extends string[] ? PropertyValue2 : P extends ShorthandProperty diff --git a/packages/@react-spectrum/s2/style/version.ts b/packages/@react-spectrum/s2/style/version.ts new file mode 100644 index 00000000000..5984631c205 --- /dev/null +++ b/packages/@react-spectrum/s2/style/version.ts @@ -0,0 +1,32 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +export function getThemeVersion() { + let s2PkgPath = require.resolve('@react-spectrum/s2'); + while (s2PkgPath.length > 2) { + const filePath = path.join(s2PkgPath, 'package.json'); + try { + const stats = fs.statSync(filePath, {throwIfNoEntry: false}); + if (stats?.isFile()) { + break; + } + } catch { /* empty */ } + s2PkgPath = path.dirname(s2PkgPath); + } + const s2PkgMetadata = require(path.join(s2PkgPath, 'package.json')); + return hashSemVer(s2PkgMetadata.version); +} + +function hashSemVer(version: string) { + const alphabet = 'abcdefghijklmnopqrstuvwxyz'; + const base = alphabet.length; + const [major, minor, patch] = version.split('.').map(Number); + const combined = (major << 16) | (minor << 8) | patch; + let hashStr = ''; + let num = combined; + while (num > 0) { + hashStr = alphabet[num % base] + hashStr; + num = Math.floor(num / base); + } + return hashStr; +}