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
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const plugin = {
const ext = args.path.match( /(\.[^.]+)$/ )?.[ 1 ] || '.js';

return {
contents: addFallbackToVar( source ),
contents: addFallbackToVar( source, { escapeQuotes: true } ),
loader: LOADER_MAP[ ext ] || 'jsx',
};
} );
Expand Down
27 changes: 19 additions & 8 deletions packages/theme/src/postcss-plugins/add-fallback-to-var.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,32 @@
* NOTE: The regex and replacement logic here is mirrored in
* `ds-token-fallbacks.mjs`. If you update one, update the other to match.
*
* @param cssValue A CSS declaration value.
* @param tokenFallbacks Map of CSS variable names to their fallback expressions.
* @return The value with fallbacks injected.
* @param cssValue A CSS declaration value.
* @param tokenFallbacks Map of CSS variable names to their fallback expressions.
* @param options Options.
* @param options.escapeQuotes When true, escape `"` and `'` in fallback values.
* Use this when the input is JS/TS source so that
* injected quotes don't break string literals.
* @return The value with fallbacks injected.
*/
export function addFallbackToVar(
cssValue: string,
tokenFallbacks: Record< string, string >
tokenFallbacks: Record< string, string >,
{ escapeQuotes = false }: { escapeQuotes?: boolean } = {}
): string {
return cssValue.replace(
/var\(\s*(--wpds-[\w-]+)\s*\)/g,
( match, tokenName: string ) => {
const fallback = tokenFallbacks[ tokenName ];
return fallback !== undefined
? `var(${ tokenName }, ${ fallback })`
: match;
let fallback = tokenFallbacks[ tokenName ];
if ( fallback === undefined ) {
return match;
}
if ( escapeQuotes ) {
fallback = fallback
.replaceAll( '"', '\\"' )
.replaceAll( "'", "\\'" );
}
return `var(${ tokenName }, ${ fallback })`;
}
);
}
28 changes: 21 additions & 7 deletions packages/theme/src/postcss-plugins/ds-token-fallbacks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,31 @@ const tokenFallbacks = _tokenFallbacks;
* NOTE: The regex and replacement logic here mirrors `add-fallback-to-var.ts`.
* If you update one, update the other to match.
*
* @param {string} cssValue A CSS declaration value.
* @return {string} The value with fallbacks injected.
* @param {string} cssValue A CSS declaration value.
* @param {Object} [options] Options.
* @param {boolean} [options.escapeQuotes] When true, escape `"` and `'` in
* fallback values. Use this when the
* input is JS/TS source so that
* injected quotes don't break string
* literals. JS will unescape them at
* parse time, so the browser's CSS
* engine still sees the correct value.
* @return {string} The value with fallbacks injected.
*/
export function addFallbackToVar( cssValue ) {
export function addFallbackToVar( cssValue, { escapeQuotes = false } = {} ) {
return cssValue.replace(
/var\(\s*(--wpds-[\w-]+)\s*\)/g,
( match, tokenName ) => {
const fallback = tokenFallbacks[ tokenName ];
return fallback !== undefined
? `var(${ tokenName }, ${ fallback })`
: match;
let fallback = tokenFallbacks[ tokenName ];
if ( fallback === undefined ) {
return match;
}
if ( escapeQuotes ) {
fallback = fallback
.replaceAll( '"', '\\"' )
.replaceAll( "'", "\\'" );
}
return `var(${ tokenName }, ${ fallback })`;
Comment on lines +31 to +40
Copy link
Member

Choose a reason for hiding this comment

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

👀 I see the surface area of the duplicated logic between the ts and mjs files have increased, but still probably fine to leave this way for now.

}
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const mockFallbacks: Record< string, string > = {
'var(--wp-admin-theme-color, #3858e9)',
'--wpds-color-bg-interactive-brand-strong-active':
'color-mix(in oklch, var(--wp-admin-theme-color, #3858e9) 92%, black)',
'--wpds-font-family-body':
'-apple-system, system-ui, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", sans-serif',
'--wpds-font-family-mono': '"Menlo", "Consolas", monaco, monospace',
};

describe( 'addFallbackToVar', () => {
Expand Down Expand Up @@ -88,4 +91,53 @@ describe( 'addFallbackToVar', () => {
const second = addFallbackToVar( first, mockFallbacks );
expect( second ).toBe( first );
} );

describe( 'escapeQuotes', () => {
it( 'does not escape quotes by default', () => {
expect(
addFallbackToVar(
'var(--wpds-font-family-body)',
mockFallbacks
)
).toBe(
'var(--wpds-font-family-body, -apple-system, system-ui, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", sans-serif)'
);
} );

it( 'escapes double quotes when enabled', () => {
expect(
addFallbackToVar(
'var(--wpds-font-family-mono)',
mockFallbacks,
{ escapeQuotes: true }
)
).toBe(
'var(--wpds-font-family-mono, \\"Menlo\\", \\"Consolas\\", monaco, monospace)'
);
} );

it( 'escapes both double and single quotes in the same value', () => {
const fallbacks: Record< string, string > = {
'--wpds-test-token': `"double" and 'single'`,
};
const result = addFallbackToVar(
'var(--wpds-test-token)',
fallbacks,
{ escapeQuotes: true }
);
expect( result ).toBe(
`var(--wpds-test-token, \\"double\\" and \\'single\\')`
);
} );

it( 'leaves values without quotes unchanged when enabled', () => {
expect(
addFallbackToVar(
'var(--wpds-border-radius-sm)',
mockFallbacks,
{ escapeQuotes: true }
)
).toBe( 'var(--wpds-border-radius-sm, 2px)' );
} );
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ const plugin = () => ( {
}
// Sourcemap omitted: replacements are small, inline substitutions
// that preserve line structure, so the debugging impact is negligible.
return { code: addFallbackToVar( code ), map: null };
return {
code: addFallbackToVar( code, { escapeQuotes: true } ),
map: null,
};
},
} );

Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/card/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function Text( { children }: { children: React.ReactNode } ) {
<p
style={ {
margin: 0,
fontFamily: [ 'var(--wp', 'ds-font-family-body)' ].join( '' ),
fontFamily: 'var(--wpds-font-family-body)',
fontSize: 'var(--wpds-font-size-md)',
fontWeight: 'var(--wpds-font-weight-regular)',
lineHeight: 'var(--wpds-font-line-height-sm)',
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/collapsible-card/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function Text( { children }: { children: React.ReactNode } ) {
<p
style={ {
margin: 0,
fontFamily: [ 'var(--wp', 'ds-font-family-body)' ].join( '' ),
fontFamily: 'var(--wpds-font-family-body)',
fontSize: 'var(--wpds-font-size-md)',
fontWeight: 'var(--wpds-font-weight-regular)',
lineHeight: 'var(--wpds-font-line-height-sm)',
Expand Down
Loading