Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
145b9d9
feat: Style Macro DevTool
snowystinger May 27, 2025
7d8b453
add parcel patch
snowystinger May 27, 2025
3266c18
small fixes
snowystinger May 27, 2025
8e4f8ee
fix mergeStyles
snowystinger May 27, 2025
b956f9c
Merge branch 'main' into style-macro-devtool
snowystinger Jun 3, 2025
af59e2e
Add README so people know how to develop this extension
snowystinger Jun 3, 2025
85e70d2
add todos, fix tests, fix lint
snowystinger Jun 3, 2025
3adab93
fix dev logic
snowystinger Jun 3, 2025
690037a
add todo
snowystinger Jun 3, 2025
5a89555
use static method to inject script
snowystinger Jun 4, 2025
a68dbfd
fix the patch
snowystinger Jun 5, 2025
2bf9d6a
I don't think there is a bug here, different states get different mac…
snowystinger Jun 5, 2025
237392f
remove console logs
snowystinger Jun 5, 2025
2706cd1
add extension icon
snowystinger Jun 5, 2025
a95b4f6
use document idle instead of start to help with tab reload
snowystinger Jun 16, 2025
15ab735
Merge branch 'main' into style-macro-devtool
snowystinger Jun 16, 2025
6fe2d0b
fix package.json
snowystinger Jun 16, 2025
70f3ef5
Merge branch 'main' into style-macro-devtool
snowystinger Jul 11, 2025
3258ea0
Merge branch 'main' into style-macro-devtool
snowystinger Oct 22, 2025
cafae4e
have dev tool handle all book keeping and poll to avoid memory leak
snowystinger Oct 23, 2025
05bc7cf
Add architecture diagrams and explanations
snowystinger Oct 23, 2025
22c9464
Add patch back in
snowystinger Oct 24, 2025
3cbc47f
fix install
snowystinger Oct 24, 2025
ef751da
remove console.log
snowystinger Oct 24, 2025
16dbc84
fix patch and ts error
snowystinger Oct 24, 2025
3e92ced
fix lint
snowystinger Oct 24, 2025
3c1ce45
fix RSC
snowystinger Oct 24, 2025
5e1e2f0
fix rsc more
snowystinger Oct 24, 2025
f5cb5a8
Merge branch 'main' into style-macro-devtool
snowystinger Oct 29, 2025
4d9ce27
Update static macros to store their data directly in a css class
snowystinger Oct 29, 2025
3ec550d
fix tests
snowystinger Oct 29, 2025
148ba97
fix lint
snowystinger Oct 29, 2025
0efc6b7
fix handling between multiple tabs
snowystinger Oct 29, 2025
e5ab27b
fix polling cleanup to work with new classnames
snowystinger Oct 29, 2025
ab75a0e
revert button change
snowystinger Oct 29, 2025
105aa4a
remove static macros extension information in production
snowystinger Oct 29, 2025
4bc53f8
add info to the architecture
snowystinger Oct 29, 2025
6916d4e
Merge branch 'main' into style-macro-devtool
snowystinger Dec 18, 2025
ad1713a
fix merge
snowystinger Dec 18, 2025
ecf912a
fix lint in test file
snowystinger Dec 18, 2025
b10ecb4
fix more merge issues
snowystinger Dec 18, 2025
5ec550d
fix tableview styles
snowystinger Dec 18, 2025
b7a5536
add location to style macro hash
LFDanLu Dec 18, 2025
87c472a
fix tests
snowystinger Jan 6, 2026
66a9dce
Add more to readme
snowystinger Jan 6, 2026
d20f3e3
Merge branch 'main' into style-macro-devtool
snowystinger Jan 6, 2026
4c1fd62
review comments
snowystinger Jan 13, 2026
f4c933e
Merge branch 'main' into style-macro-devtool
snowystinger Jan 13, 2026
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
10 changes: 9 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -505,4 +505,12 @@ export default [{
rules: {
"react/react-in-jsx-scope": OFF,
},
}];
}, {
files: ["packages/dev/style-macro-chrome-plugin/**"],
languageOptions: {
globals: {
...globals.webextensions,
...globals.browser
}
}
}];
2 changes: 1 addition & 1 deletion packages/@react-spectrum/s2/src/TableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1465,7 +1465,7 @@ export const Row = /*#__PURE__*/ (forwardRef as forwardRefType)(function Row<T e
className={renderProps => row({
...renderProps,
...tableVisualOptions
}) + (renderProps.isFocusVisible && ' ' + raw('&:before { content: ""; display: inline-block; position: sticky; inset-inline-start: 0; width: 3px; height: 100%; margin-inline-end: -3px; margin-block-end: 1px; z-index: 3; background-color: var(--rowFocusIndicatorColor)'))}
}) + (renderProps.isFocusVisible ? ' ' + raw('&:before { content: ""; display: inline-block; position: sticky; inset-inline-start: 0; width: 3px; height: 100%; margin-inline-end: -3px; margin-block-end: 1px; z-index: 3; background-color: var(--rowFocusIndicatorColor)') : '')}
{...otherProps}>
{selectionMode !== 'none' && selectionBehavior === 'toggle' && (
// Not sure what we want to do with this className, in Cell it currently overrides the className that would have been applied.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,24 @@
import {mergeStyles} from '../runtime';
import {style} from '../spectrum-theme';

function stripMacro(css) {
return css.replaceAll(/ -macro-static-[0-9a-zA-Z]+/gi, '').replaceAll(/ -macro-dynamic-[0-9a-zA-Z]+/gi, '');
}

describe('mergeStyles', () => {
it('should merge styles', () => {
let a = style({backgroundColor: 'red-1000', color: 'pink-100'});
let b = style({fontSize: 'body-xs', backgroundColor: 'gray-50'});
let expected = style({backgroundColor: 'gray-50', color: 'pink-100', fontSize: 'body-xs'});
let merged = mergeStyles(a, b);
expect(merged).toBe(expected);
expect(stripMacro(merged)).toBe(stripMacro(expected.toString()));
});

it('should merge with arbitrary values', () => {
let a = style({backgroundColor: 'red-1000', color: '[hotpink]'});
let b = style({fontSize: '[15px]', backgroundColor: 'gray-50'});
let expected = style({backgroundColor: 'gray-50', color: '[hotpink]', fontSize: '[15px]'});
let merged = mergeStyles(a, b);
expect(merged).toBe(expected);
expect(stripMacro(merged)).toBe(stripMacro(expected.toString()));
});
});
80 changes: 50 additions & 30 deletions packages/@react-spectrum/s2/style/__tests__/style-macro.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,13 @@ describe('style-macro', () => {
}
}

.-macro-static-E8tar {
--macro-data-E8tar: {"style":{"marginTop":{":first-child":{"default":4,"lg":8}}},"loc":"undefined:undefined:undefined"};
}

"
`);
expect(js).toMatchInlineSnapshot('" Jbs1 Jbpv1"');
expect(js).toMatchInlineSnapshot('" Jbs1 Jbpv1 -macro-static-E8tar"');
});

it('should support self references', () => {
Expand Down Expand Up @@ -114,10 +118,14 @@ describe('style-macro', () => {
}
}

.-macro-static-ootVze {
--macro-data-ootVze: {"style":{"borderWidth":2,"paddingX":"edge-to-text","width":"calc(200px - self(borderStartWidth) - self(paddingStart))"},"loc":"undefined:undefined:undefined"};
}

"
`);

expect(js).toMatchInlineSnapshot('" _kc1 hc1 mCPFGYc1 lc1 SMBFGYc1 Rv1 ZjUQgKd1 -m_-mc1 -S_-Sv1"');
expect(js).toMatchInlineSnapshot('" _kc1 hc1 mCPFGYc1 lc1 SMBFGYc1 Rv1 ZjUQgKd1 -m_-mc1 -S_-Sv1 -macro-static-ootVze"');
});

it('should support allowed overrides', () => {
Expand All @@ -134,9 +142,9 @@ describe('style-macro', () => {
color: 'green-400'
});

expect(js()).toMatchInlineSnapshot('" gw1 pg1"');
expect(overrides).toMatchInlineSnapshot('" g8tmWqb1 pHJ3AUd1"');
expect(js({}, overrides)).toMatchInlineSnapshot('" g8tmWqb1 pg1"');
expect(js()).toMatchInlineSnapshot('" gw1 pg1 -macro-dynamic-1g1t5qe"');
expect(overrides).toMatchInlineSnapshot('" g8tmWqb1 pHJ3AUd1 -macro-static-YWkqh"');
expect(js({}, overrides)).toMatchInlineSnapshot('" g8tmWqb1 pg1 -macro-dynamic-w1re4y"');
});

it('should support allowed overrides for properties that expand into multiple', () => {
Expand All @@ -151,9 +159,9 @@ describe('style-macro', () => {
translateX: 40
});

expect(js()).toMatchInlineSnapshot('" -_7PloMd-B1 __Ya1"');
expect(overrides).toMatchInlineSnapshot('" -_7PloMd-D1 __Ya1"');
expect(js({}, overrides)).toMatchInlineSnapshot('" -_7PloMd-D1 __Ya1"');
expect(js()).toMatchInlineSnapshot('" -_7PloMd-B1 __Ya1 -macro-dynamic-1g21iuv"');
expect(overrides).toMatchInlineSnapshot('" -_7PloMd-D1 __Ya1 -macro-static-JbY0Pb"');
expect(js({}, overrides)).toMatchInlineSnapshot('" -_7PloMd-D1 __Ya1 -macro-dynamic-7es7uh"');
});

it('should support allowed overrides for shorthands', () => {
Expand All @@ -168,9 +176,9 @@ describe('style-macro', () => {
padding: 40
});

expect(js()).toMatchInlineSnapshot('" Tk1 Qk1 Sk1 Rk1"');
expect(overrides).toMatchInlineSnapshot('" Tm1 Qm1 Sm1 Rm1"');
expect(js({}, overrides)).toMatchInlineSnapshot('" Tm1 Qm1 Sm1 Rm1"');
expect(js()).toMatchInlineSnapshot('" Tk1 Qk1 Sk1 Rk1 -macro-dynamic-ov9j7t"');
expect(overrides).toMatchInlineSnapshot('" Tm1 Qm1 Sm1 Rm1 -macro-static-JNxqEe"');
expect(js({}, overrides)).toMatchInlineSnapshot('" Tm1 Qm1 Sm1 Rm1 -macro-dynamic-x7v9g1"');
});

it('should support allowed overrides for fontSize', () => {
Expand All @@ -185,9 +193,9 @@ describe('style-macro', () => {
fontSize: 'ui-xs'
});

expect(js()).toMatchInlineSnapshot('" -_6BNtrc-woabcc1 vx1"');
expect(overrides).toMatchInlineSnapshot('" -_6BNtrc-a1 vx1"');
expect(js({}, overrides)).toMatchInlineSnapshot('" -_6BNtrc-a1 vx1"');
expect(js()).toMatchInlineSnapshot('" -_6BNtrc-woabcc1 vx1 -macro-dynamic-4vhxg6"');
expect(overrides).toMatchInlineSnapshot('" -_6BNtrc-a1 vx1 -macro-static-mgSTCe"');
expect(js({}, overrides)).toMatchInlineSnapshot('" -_6BNtrc-a1 vx1 -macro-dynamic-wufo3s"');
});

it("should support allowed overrides for values that aren't defined", () => {
Expand All @@ -202,9 +210,9 @@ describe('style-macro', () => {
minWidth: 32
});

expect(js()).toMatchInlineSnapshot('" gE1"');
expect(overrides).toMatchInlineSnapshot('" Nk1"');
expect(js({}, overrides)).toMatchInlineSnapshot('" Nk1 gE1"');
expect(js()).toMatchInlineSnapshot('" gE1 -macro-dynamic-6mjybw"');
expect(overrides).toMatchInlineSnapshot('" Nk1 -macro-static-TMcWFd"');
expect(js({}, overrides)).toMatchInlineSnapshot('" Nk1 gE1 -macro-dynamic-1255f06"');
});

it('should support runtime conditions', () => {
Expand Down Expand Up @@ -258,9 +266,9 @@ describe('style-macro', () => {
"
`);

expect(js({})).toMatchInlineSnapshot('" gH1 pt1"');
expect(js({isHovered: true})).toMatchInlineSnapshot('" gF1 po1"');
expect(js({isPressed: true})).toMatchInlineSnapshot('" gE1 pm1"');
expect(js({})).toMatchInlineSnapshot('" gH1 pt1 -macro-dynamic-1e9ks2s"');
expect(js({isHovered: true})).toMatchInlineSnapshot('" gF1 po1 -macro-dynamic-fs3pp9"');
expect(js({isPressed: true})).toMatchInlineSnapshot('" gE1 pm1 -macro-dynamic-qy1zq2"');
});

it('should support nested runtime conditions', () => {
Expand Down Expand Up @@ -301,10 +309,10 @@ describe('style-macro', () => {

"
`);
expect(js({})).toMatchInlineSnapshot('" gH1"');
expect(js({isHovered: true})).toMatchInlineSnapshot('" gF1"');
expect(js({isSelected: true})).toMatchInlineSnapshot('" g_h1"');
expect(js({isSelected: true, isHovered: true})).toMatchInlineSnapshot('" g31"');
expect(js({})).toMatchInlineSnapshot('" gH1 -macro-dynamic-z0gpn3"');
expect(js({isHovered: true})).toMatchInlineSnapshot('" gF1 -macro-dynamic-1rfxkr1"');
expect(js({isSelected: true})).toMatchInlineSnapshot('" g_h1 -macro-dynamic-tk9x4e"');
expect(js({isSelected: true, isHovered: true})).toMatchInlineSnapshot('" g31 -macro-dynamic-1defke2"');
});

it('should support variant runtime conditions', () => {
Expand All @@ -318,9 +326,9 @@ describe('style-macro', () => {
}
});

expect(js({variant: 'accent'})).toMatchInlineSnapshot('" gY1"');
expect(js({variant: 'primary'})).toMatchInlineSnapshot('" gjQquMe1"');
expect(js({variant: 'secondary'})).toMatchInlineSnapshot('" gw1"');
expect(js({variant: 'accent'})).toMatchInlineSnapshot('" gY1 -macro-dynamic-6gbj4w"');
expect(js({variant: 'primary'})).toMatchInlineSnapshot('" gjQquMe1 -macro-dynamic-aqv4wq"');
expect(js({variant: 'secondary'})).toMatchInlineSnapshot('" gw1 -macro-dynamic-66ywce"');
});

it('supports runtime conditions nested inside css conditions', () => {
Expand Down Expand Up @@ -354,16 +362,16 @@ describe('style-macro', () => {
"
`);

expect(js({})).toMatchInlineSnapshot('" plb1"');
expect(js({isSelected: true})).toMatchInlineSnapshot('" ple1"');
expect(js({})).toMatchInlineSnapshot('" plb1 -macro-dynamic-qa0mhq"');
expect(js({isSelected: true})).toMatchInlineSnapshot('" ple1 -macro-dynamic-1inxdsx"');
});

it('should expand shorthand properties to longhands', () => {
let {js, css} = testStyle({
padding: 24
});

expect(js).toMatchInlineSnapshot('" Th1 Qh1 Sh1 Rh1"');
expect(js).toMatchInlineSnapshot('" Th1 Qh1 Sh1 Rh1 -macro-static-tfFFV"');
expect(css).toMatchInlineSnapshot(`
"@layer _.a;

Expand All @@ -388,6 +396,10 @@ describe('style-macro', () => {
}
}

.-macro-static-tfFFV {
--macro-data-tfFFV: {"style":{"padding":24},"loc":"undefined:undefined:undefined"};
}

"
`);
});
Expand All @@ -406,6 +418,10 @@ describe('style-macro', () => {
}
}

.-macro-static-S2MtWd {
--macro-data-S2MtWd: {"style":{"backgroundColor":"blue-1000/50"},"loc":"undefined:undefined:undefined"};
}

"
`);
});
Expand All @@ -427,6 +443,10 @@ describe('style-macro', () => {
}
}

.-macro-static-sFmj5 {
--macro-data-sFmj5: {"style":{"--foo":{"type":"backgroundColor","value":"gray-300"}},"loc":"undefined:undefined:undefined"};
}

"
`);
});
Expand Down
33 changes: 29 additions & 4 deletions packages/@react-spectrum/s2/style/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,45 @@ import {StyleString} from './types';
export function mergeStyles(...styles: (StyleString | null | undefined)[]): StyleString {
let definedStyles = styles.filter(Boolean) as StyleString[];
if (definedStyles.length === 1) {
return definedStyles[0];
let first = definedStyles[0];
if (typeof first !== 'string') {
// static macro has a toString method so that we generate the style macro map for the entry
// it's automatically called in other places, but for our merging, we have to call it ourselves
return (first as StyleString).toString() as StyleString;
}
return first;
}

let map = new Map();
let map = new Map<string, string>();
let macroClasses: string[] = [];

for (let style of definedStyles) {
for (let [k, v] of parse(style)) {
// must call toString here for the static macro
let str = style.toString();

// Extract and preserve macro debug classes
let macroMatches = str.matchAll(/-macro-(static|dynamic)-[^\s]+/g);
for (let match of macroMatches) {
if (!macroClasses.includes(match[0])) {
macroClasses.push(match[0]);
}
}

for (let [k, v] of parse(str)) {
map.set(k, v);
}
}

let res = '';
for (let value of map.values()) {
res += value;
}

// Append all macro debug classes
if (macroClasses.length > 0) {
res += ' ' + macroClasses.join(' ');
}

return res as StyleString;
}

Expand Down
Loading