Skip to content
Open
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
1 change: 1 addition & 0 deletions .codesandbox/ci.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"packages/pigment-css-core",
"packages/pigment-css-nextjs-plugin",
"packages/pigment-css-react",
"packages/pigment-css-react-new",
"packages/pigment-css-theme",
"packages/pigment-css-unplugin",
"packages/pigment-css-utils",
Expand Down
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
/packages/pigment-css-react/tests/**/fixtures
/packages/pigment-css-core/exports/
/packages/pigment-css-core/tests/**/fixtures
/packages/pigment-css-react-new/exports/
/packages/pigment-css-react-new/tests/**/fixtures
/packages/pigment-css-nextjs-plugin/loader.js
# Ignore fixtures
/packages-internal/scripts/typescript-to-proptypes/test/*/*
Expand Down
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ module.exports = {
],
'no-use-before-define': 'off',

'react/react-in-jsx-scope': 'off',

// disabled type-aware linting due to performance considerations
'@typescript-eslint/dot-notation': 'off',
'dot-notation': 'error',
Expand Down
9 changes: 8 additions & 1 deletion nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@
"build": {
"cache": true,
"dependsOn": ["copy-license", "^build"],
"outputs": ["{projectRoot}/build", "{projectRoot}/dist", "{projectRoot}/.next"]
"outputs": [
"{projectRoot}/build",
"{projectRoot}/processors",
"{projectRoot}/runtime",
"{projectRoot}/dist",
"{projectRoot}/.next",
"{projectRoot}/exports"
]
},
"preview": {
"dependsOn": ["^build"]
Expand Down
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
"validate-declarations": "tsx scripts/validateTypescriptDeclarations.mts"
},
"dependencies": {
"@pigment-css/react": "workspace:^",
"globby": "^14.0.1"
},
"devDependencies": {
Expand Down Expand Up @@ -108,8 +107,6 @@
"prettier": "^3.3.3",
"pretty-quick": "^4.0.0",
"process": "^0.11.10",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-docgen": "^5.4.3",
"remark": "^13.0.0",
"rimraf": "^6.0.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/pigment-css-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@
}
},
"files": [
"src",
"build",
"exports",
"processors",
"runtime",
"src",
"package.json",
"styles.css",
"LICENSE"
Expand Down
12 changes: 12 additions & 0 deletions packages/pigment-css-core/src/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,17 @@ export type CompoundVariant<T extends Variants> = VariantNames<T> & {
};

type CVAConfig<V extends Variants> = {
/**
* Documentation: https://pigment-css.com/features/styling#variants
*/
variants?: V;
/**
* Documentation: https://pigment-css.com/features/styling#compound-variants
*/
compoundVariants?: CompoundVariant<V>[];
/**
* Documentation: https://pigment-css.com/features/styling#default-variants
*/
defaultVariants?: VariantNames<V>;
};

Expand Down Expand Up @@ -68,6 +77,9 @@ interface CssWithOption {
<M extends BaseInterface>(metadata: M): CssNoOption;
}

/**
* Documentation: https://pigment-css.com/features/styling#css
*/
const css: CssNoOption & CssWithOption = () => {
throw new Error(generateErrorMessage('css'));
};
Expand Down
3 changes: 3 additions & 0 deletions packages/pigment-css-core/src/keyframes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ interface KeyframesWithOption {
<M extends BaseInterface>(metadata: M): KeyframesNoOption;
}

/**
* Documentation: https://pigment-css.com/features/styling#keyframes
*/
const keyframes: KeyframesWithOption & KeyframesNoOption = () => {
throw new Error(generateErrorMessage('keyframes'));
};
Expand Down
204 changes: 122 additions & 82 deletions packages/pigment-css-core/src/processors/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* CssProcessor.
*/

import { SourceLocation } from '@babel/types';
import { SourceLocation, TemplateElement } from '@babel/types';
import {
type TransformedInternalConfig,
type StyleObjectReturn,
Expand All @@ -21,7 +21,7 @@ import {
serializeStyles,
valueToLiteral,
evaluateClassNameArg,
getCSSVar,
transformProbableCssVar,
} from '@pigment-css/utils';
import {
CallParam,
Expand Down Expand Up @@ -119,6 +119,99 @@ export type CssTailProcessorParams = BaseCssProcessorConstructorParams extends [
? T
: never;

function handleTemplateElementOrSimilar(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the test for the change in implementation.

templateParams: (TemplateElement | ExpressionValue)[],
values: ValueCache,
processor: BaseCssProcessor,
) {
const { themeArgs = {}, pigmentFeatures: { useLayer = true } = {} } =
processor.options as TransformedInternalConfig;
// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
const templateStrs: string[] = [];
Copy link
Member

@Janpot Janpot Mar 24, 2025

Choose a reason for hiding this comment

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

const x: TemplateStringsArray = Object.assign([], { raw: [] })

// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
templateStrs.raw = [];
const templateExpressions: Primitive[] = [];
let paramsToIterate = templateParams;
const [firstArg, ...restArgs] = templateParams;
if ('kind' in firstArg && firstArg.kind === ValueType.LAZY) {
const value = values.get(firstArg.ex.name) as string[];
templateStrs.push(...value);
// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
templateStrs.raw.push(...value);
paramsToIterate = restArgs;
}
paramsToIterate.forEach((param) => {
if ('kind' in param) {
switch (param.kind) {
case ValueType.FUNCTION: {
const value = values.get(param.ex.name) as TemplateCallback;
templateExpressions.push(value(themeArgs));
break;
}
case ValueType.CONST: {
if (typeof param.value === 'string') {
templateExpressions.push(transformProbableCssVar(param.value));
} else {
templateExpressions.push(param.value);
}
break;
}
case ValueType.LAZY: {
const evaluatedValue = values.get(param.ex.name);
if (typeof evaluatedValue === 'function') {
templateExpressions.push(evaluatedValue(themeArgs));
} else if (typeof evaluatedValue === 'string') {
templateExpressions.push(transformProbableCssVar(evaluatedValue));
} else {
templateExpressions.push(evaluatedValue as Primitive);
}
break;
}
default:
break;
}
} else if ('type' in param && param.type === 'TemplateElement') {
templateStrs.push(param.value.cooked as string);
// @ts-ignore
templateStrs.raw.push(param.value.raw);
}
});
const { styles } = serializeStyles(
templateExpressions.length > 0 ? [templateStrs, ...templateExpressions] : [templateStrs],
);

const cssText = useLayer
? `@layer pigment.base{${processor.wrapStyle(styles, '')}}`
: processor.wrapStyle(styles, '');
const className = processor.getClassName();
const rules: Rules = {
[`.${className}`]: {
className,
cssText,
displayName: processor.displayName,
start: processor.location?.start ?? null,
},
};
const location = processor.location;
const sourceMapReplacements: Replacements = [
{
length: cssText.length,
original: {
start: {
column: location?.start.column ?? 0,
line: location?.start.line ?? 0,
},
end: {
column: location?.end.column ?? 0,
line: location?.end.line ?? 0,
},
},
},
];
processor.classNames.push(className);
processor.artifacts.push(['css', [rules, sourceMapReplacements]]);
}

/**
* Only deals with css`` or css(metadata)`` calls.
*/
Expand All @@ -138,84 +231,8 @@ export class CssTaggedTemplateProcessor extends BaseCssProcessor {
}

build(values: ValueCache): void {
const { themeArgs, pigmentFeatures: { useLayer = true } = {} } = this
.options as TransformedInternalConfig;
const [, templateParams] = this.templateParam;
// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
const templateStrs: string[] = [];
// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
templateStrs.raw = [];
const templateExpressions: Primitive[] = [];
templateParams.forEach((param) => {
if ('kind' in param) {
switch (param.kind) {
case ValueType.FUNCTION: {
const value = values.get(param.ex.name) as TemplateCallback;
templateExpressions.push(value(themeArgs));
break;
}
case ValueType.CONST: {
templateExpressions.push(param.value);
break;
}
case ValueType.LAZY: {
const evaluatedValue = values.get(param.ex.name);
if (typeof evaluatedValue === 'function') {
templateExpressions.push(evaluatedValue(themeArgs));
} else if (
typeof evaluatedValue === 'object' &&
evaluatedValue &&
(evaluatedValue as unknown as Record<string, boolean>).isThemeVar
) {
templateExpressions.push(getCSSVar(evaluatedValue.toString(), true));
} else {
templateExpressions.push(evaluatedValue as Primitive);
}
break;
}
default:
break;
}
} else if ('type' in param && param.type === 'TemplateElement') {
templateStrs.push(param.value.cooked as string);
// @ts-ignore
templateStrs.raw.push(param.value.raw);
}
});
const { styles } = serializeStyles(
templateExpressions.length > 0 ? [templateStrs, ...templateExpressions] : [templateStrs],
);

const cssText = useLayer
? `@layer pigment.base{${this.wrapStyle(styles, '')}}`
: this.wrapStyle(styles, '');
const className = this.getClassName();
const rules: Rules = {
[`.${className}`]: {
className,
cssText,
displayName: this.displayName,
start: this.location?.start ?? null,
},
};
const location = this.location;
const sourceMapReplacements: Replacements = [
{
length: cssText.length,
original: {
start: {
column: location?.start.column ?? 0,
line: location?.start.line ?? 0,
},
end: {
column: location?.end.column ?? 0,
line: location?.end.line ?? 0,
},
},
},
];
this.classNames.push(className);
this.artifacts.push(['css', [rules, sourceMapReplacements]]);
handleTemplateElementOrSimilar(templateParams, values, this);
}
}

Expand All @@ -232,21 +249,44 @@ export class CssObjectProcessor extends BaseCssProcessor {

getDependencies(): ExpressionValue[] {
const [, ...params] = this.callParam;
return params.flat().filter((param) => 'kind' in param);
return params.flat().filter((param) => 'kind' in param && param.kind !== ValueType.CONST);
}

isMaybeTransformedTemplateLiteral(values: ValueCache): boolean {
const [, firstArg, ...restArgs] = this.callParam;
if (!('kind' in firstArg) || firstArg.kind === ValueType.CONST) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think I've used the in operator in over a decade 😄
It's purely stylistic in this instance, .hasOwnProperty feels more predictable to me.

Copy link
Member

@siriwatknp siriwatknp Mar 28, 2025

Choose a reason for hiding this comment

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

I think in makes TS happy too, not sure about .hasOwnProperty.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct. in narrows the types to the expected ones.

return false;
}
const firstArgVal = values.get(firstArg.ex.name);
if (Array.isArray(firstArgVal) && restArgs.length === firstArgVal.length - 1) {
return true;
}
return false;
}

private buildForTransformedTemplateTag(values: ValueCache) {
const [, ...templateParams] = this.callParam;
handleTemplateElementOrSimilar(templateParams, values, this);
}

build(values: ValueCache): void {
if (this.isMaybeTransformedTemplateLiteral(values)) {
this.buildForTransformedTemplateTag(values);
return;
}
const [, ...callParams] = this.callParam;
const { themeArgs, pigmentFeatures: { useLayer = true } = {} } = this
.options as TransformedInternalConfig;

const evaluatedValues = (callParams as (LazyValue | FunctionValue)[]).map((param) =>
values.get(param.ex.name),
const evaluatedValues = (callParams as ExpressionValue[]).map((param) =>
param.kind === ValueType.CONST ? param.value : values.get(param.ex.name),
);
let stylesList: (object | Function)[];
// let metadata: any;
// check for css(metadata, [styles]) or css(metadata, style) call
const locations: (SourceLocation | null | undefined)[] = [];
// Remove this condition as this supports an older API that has since been
// removed from TS support.
if (
evaluatedValues.length === 2 &&
evaluatedValues[0] &&
Expand Down
7 changes: 7 additions & 0 deletions packages/pigment-css-core/tests/css/fixtures/css.input.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,10 @@ export const cls6 = css(({ theme }) => ({
},
],
}));

export const cls7 = css(
{
color: '$palette.main',
},
`display: black`,
);
Loading