Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
6 changes: 6 additions & 0 deletions packages/pigment-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@
"build"
]
},
"test:update": {
"cache": false,
"dependsOn": [
"build"
]
},
"test:ci": {
"cache": false,
"dependsOn": [
Expand Down
26 changes: 15 additions & 11 deletions packages/pigment-react/src/processors/keyframes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { validateParams } from '@wyw-in-js/processor-utils';
import BaseProcessor from './base-processor';
import type { IOptions } from './styled';
import { cache } from '../utils/emotion';
import { isTaggedTemplateCall, resolveTaggedTemplate } from '../utils/taggedTemplateCall';

export type Primitive = string | number | boolean | null | undefined;

Expand All @@ -33,15 +34,13 @@ export class KeyframesProcessor extends BaseProcessor {
);

const [, callParams] = params;
if (callParams[0] === 'call') {
this.dependencies.push(callParams[1]);
} else if (callParams[0] === 'template') {
callParams[1].forEach((element) => {
if ('kind' in element && element.kind !== ValueType.CONST) {
this.dependencies.push(element);
}
});
}
const [, ...callParamsRest] = callParams;

callParamsRest.flat().forEach((item) => {
if ('kind' in item) {
this.dependencies.push(item);
}
});
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

follow the styled implementation.

this.callParam = callParams;
}

Expand All @@ -50,10 +49,13 @@ export class KeyframesProcessor extends BaseProcessor {
throw new Error(`MUI: "${this.tagSource.imported}" is already built`);
}

const [callType] = this.callParam;
const [callType, ...callArgs] = this.callParam;

if (callType === 'template') {
this.handleTemplate(this.callParam, values);
} else if (isTaggedTemplateCall(callArgs, values)) {
const { themeArgs } = this.options as IOptions;
this.generateArtifacts([resolveTaggedTemplate(callArgs, values, themeArgs)]);
} else {
this.handleCall(this.callParam, values);
}
Expand Down Expand Up @@ -100,7 +102,9 @@ export class KeyframesProcessor extends BaseProcessor {

generateArtifacts(styleObjOrTaggged: CSSInterpolation | string[], ...args: Primitive[]) {
const { styles } = serializeStyles(
[styleObjOrTaggged as Interpolation<{}>, args],
args.length > 0
? [styleObjOrTaggged as Interpolation<{}>, ...args]
: [styleObjOrTaggged as Interpolation<{}>],
Comment on lines +103 to +105
Copy link
Member Author

@siriwatknp siriwatknp Mar 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition is required to prevent ILLEGAL_ESCAPE_SEQUENCE_ERROR from emotion because serializeStyles assumes that [] is an expression.

cache.registered,
);
const cssText = `@keyframes {${styles}}`;
Expand Down
23 changes: 23 additions & 0 deletions packages/pigment-react/src/processors/styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { valueToLiteral } from '../utils/valueToLiteral';
import BaseProcessor from './base-processor';
import { Primitive, TemplateCallback } from './keyframes';
import { cache, css } from '../utils/emotion';
import { isTaggedTemplateCall, resolveTaggedTemplate } from '../utils/taggedTemplateCall';

type Theme = { [key: 'unstable_sxConfig' | string]: string | number | Theme };

Expand Down Expand Up @@ -287,6 +288,24 @@ export class StyledProcessor extends BaseProcessor {
this.generateArtifacts();
}

private buildForTagTemplateCall(values: ValueCache): void {
const { themeArgs } = this.options as IOptions;
const cssClassName = css`
${resolveTaggedTemplate(this.styleArgs, values, themeArgs)}
`;
const cssText = cache.registered[cssClassName] as string;

const baseClass = this.getClassName();
this.baseClasses.push(baseClass);
this.collectedStyles.push([baseClass, cssText, null]);
const variantsAccumulator: VariantData[] = [];
this.processOverrides(values, variantsAccumulator);
variantsAccumulator.forEach((variant) => {
this.processVariant(variant);
});
this.generateArtifacts();
}

/**
* There are 2 main phases in Wyw-in-JS's processing, Evaltime and Runtime. During Evaltime, Wyw-in-JS prepares minimal code that gets evaluated to get the actual values of the styled arguments. Here, we mostly want to replace the styled calls with a simple string/object of its classname. This is necessary for class composition. For ex, you could potentially do this -
* ```js
Expand Down Expand Up @@ -318,6 +337,10 @@ export class StyledProcessor extends BaseProcessor {
this.buildForTemplateTag(values);
return;
}
if (isTaggedTemplateCall(this.styleArgs, values)) {
this.buildForTagTemplateCall(values);
return;
}
const themeImportIdentifier = this.astService.addDefaultImport(
`${process.env.PACKAGE_NAME}/theme`,
'theme',
Expand Down
71 changes: 71 additions & 0 deletions packages/pigment-react/src/utils/taggedTemplateCall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* This is a temporary support for `call` that has tagged template literal as first argument
* It's likely come from bundled code, e.g. `@mui/material` stable build.
* We can remove this once we updated the browserslist.rc in v6
*/
import type { TemplateElement } from '@babel/types';
import { ValueType, type ExpressionValue } from '@wyw-in-js/shared';
import { ValueCache } from '@wyw-in-js/processor-utils';

type Primitive = string | number | boolean | null | undefined;

type TemplateCallback = (params: Record<string, unknown> | undefined) => string | number;

export function isTaggedTemplateCall(
styleArgs:
| ExpressionValue[]
| (TemplateElement | ExpressionValue)[]
| [(TemplateElement | ExpressionValue)[]],
values: ValueCache,
) {
const [firstArg] = styleArgs.flat();
if (!('kind' in firstArg) || firstArg.kind !== ValueType.LAZY) {
return false;
}
const firstValue = values.get(firstArg.ex.name);
return Array.isArray(firstValue) && firstValue.every((val) => typeof val === 'string');
}

export function resolveTaggedTemplate(
styleArgs:
| ExpressionValue[]
| (TemplateElement | ExpressionValue)[]
| [(TemplateElement | ExpressionValue)[]],
values: ValueCache,
themeArgs: { theme?: unknown } | undefined,
) {
const [firstArg, ...exArgs] = styleArgs.flat();
if ('kind' in firstArg && firstArg.kind === ValueType.LAZY) {
const templateExpressions: Primitive[] = [];
const taggedTemplate = firstArg.source.trim().match(/`([^`]+)`/)?.[1] || '';

exArgs.forEach((item) => {
if ('kind' in item) {
switch (item.kind) {
case ValueType.FUNCTION: {
const value = values.get(item.ex.name) as TemplateCallback;
templateExpressions.push(value(themeArgs));
break;
}
case ValueType.CONST:
templateExpressions.push(item.value);
break;
case ValueType.LAZY: {
const evaluatedValue = values.get(item.ex.name);
if (typeof evaluatedValue === 'function') {
templateExpressions.push(evaluatedValue(themeArgs));
} else {
templateExpressions.push(evaluatedValue as Primitive);
}
break;
}
default:
break;
}
}
});

return taggedTemplate.replace(/\$\{[^}]+\}/gm, () => String(templateExpressions.shift()));
}
return ``;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { keyframes } from '@pigment-css/react';

const green = 'green';

const gradientKeyframe = keyframes(({ theme }) => ({
'0%': {
background: theme.palette.primary.main,
},
'50%': {
background: green,
},
'100%': {
background: theme.palette.secondary.main,
},
}));

const gradientKeyframe2 = keyframes`
0% {
background: ${({ theme }) => theme.palette.primary.main};
}

50% {
background: ${green};
}

100% {
background: ${({ theme }) => theme.palette.secondary.main};
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@keyframes g2c7x3u {
0% {
background: red;
}
50% {
background: green;
}
100% {
background: blue;
}
}
@keyframes gb35t65 {
0% {
background: red;
}
50% {
background: green;
}
100% {
background: blue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const gradientKeyframe = 'g2c7x3u';
const gradientKeyframe2 = 'gb35t65';
10 changes: 10 additions & 0 deletions packages/pigment-react/tests/keyframes/fixtures/keyframes.input.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,13 @@ const rotateKeyframe = keyframes({
transform: 'rotate(0deg)',
},
});

const rotateKeyframe2 = keyframes`
from {
transform: rotate(360deg);
}

to {
transform: rotate(0deg);
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,11 @@
transform: rotate(0deg);
}
}
@keyframes r3amm75 {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
const rotateKeyframe = 'r14c1bqo';
const rotateKeyframe2 = 'r3amm75';
23 changes: 23 additions & 0 deletions packages/pigment-react/tests/keyframes/keyframes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,27 @@ describe('Pigment CSS - keyframes', () => {
expect(output.js).to.equal(fixture.js);
expect(output.css).to.equal(fixture.css);
});

it('should transform correctly with theme', async () => {
const { output, fixture } = await runTransformation(
path.join(__dirname, 'fixtures/keyframes-theme.input.js'),
{
themeArgs: {
theme: {
palette: {
primary: {
main: 'red',
},
secondary: {
main: 'blue',
},
},
},
},
},
);

expect(output.js).to.equal(fixture.js);
expect(output.css).to.equal(fixture.css);
});
});
33 changes: 33 additions & 0 deletions packages/pigment-react/tests/styled/fixtures/styled-theme.input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { styled, keyframes } from '@pigment-css/react';

const rotateKeyframe = keyframes({
from: {
transform: 'rotate(360deg)',
},
to: {
transform: 'rotate(0deg)',
},
});

const Component = styled.div(({ theme }) => ({
color: theme.palette.primary.main,
animation: `${rotateKeyframe} 2s ease-out 0s infinite`,
}));

const SliderRail = styled('span', {
name: 'MuiSlider',
slot: 'Rail',
})`
display: none;
position: absolute;
border-radius: inherit;
background-color: currentColor;
opacity: 0.38;
font-size: ${({ theme }) => theme.size.font.h1};
`;

const SliderRail2 = styled.span`
display: block;
opacity: 0.38;
font-size: ${({ theme }) => theme.size.font.h1};
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@keyframes r3sp8jf {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}
.clqufod {
color: red;
animation: r3sp8jf 2s ease-out 0s infinite;
}
.s1fopuc2 {
display: none;
position: absolute;
border-radius: inherit;
background-color: currentColor;
opacity: 0.38;
font-size: 3rem;
}
.s1fopuc2-1 {
font-size: 1.5rem;
}
.s1tggtaa {
display: block;
opacity: 0.38;
font-size: 3rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { styled as _styled3 } from '@pigment-css/react';
import { styled as _styled2 } from '@pigment-css/react';
import { styled as _styled } from '@pigment-css/react';
import _theme from '@pigment-css/react/theme';
const Component = /*#__PURE__*/ _styled('div')({
classes: ['clqufod'],
});
const SliderRail = /*#__PURE__*/ _styled2('span', {
name: 'MuiSlider',
slot: 'Rail',
})({
classes: ['s1fopuc2', 's1fopuc2-1'],
});
const SliderRail2 = /*#__PURE__*/ _styled3('span')({
classes: ['s1tggtaa'],
});
Loading