Skip to content

Commit 2fef199

Browse files
committed
create mui-plugin-rtl package
1 parent 28af48f commit 2fef199

File tree

11 files changed

+363
-0
lines changed

11 files changed

+363
-0
lines changed

docs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"@mui/system": "workspace:^",
4141
"@mui/types": "workspace:^",
4242
"@mui/utils": "workspace:^",
43+
"@mui/plugin-rtl": "workspace:^",
4344
"@mui/x-charts": "8.3.1",
4445
"@mui/x-data-grid": "8.3.1",
4546
"@mui/x-data-grid-generator": "8.3.1",

packages/mui-plugin-rtl/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# @mui/plugin-rtl
2+
3+
A fork of [stylis-plugin-rtl](https://github.com/styled-components/stylis-plugin-rtl) to fix issues with CSS layers and to support the latest version of Stylis.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"name": "@mui/plugin-rtl",
3+
"version": "7.1.0",
4+
"author": "MUI Team",
5+
"description": "A plugin for Material UI that provides RTL (right-to-left) support.",
6+
"main": "./src/index.ts",
7+
"keywords": [
8+
"react",
9+
"react-component",
10+
"mui",
11+
"rtl"
12+
],
13+
"repository": {
14+
"type": "git",
15+
"url": "git+https://github.com/mui/material-ui.git",
16+
"directory": "packages/mui-plugin-rtl"
17+
},
18+
"license": "MIT",
19+
"bugs": {
20+
"url": "https://github.com/mui/material-ui/issues"
21+
},
22+
"homepage": "https://github.com/mui/material-ui/tree/master/packages/mui-utils",
23+
"funding": {
24+
"type": "opencollective",
25+
"url": "https://opencollective.com/mui-org"
26+
},
27+
"scripts": {
28+
"build": "pnpm build:node && pnpm build:stable && pnpm build:types && pnpm build:copy-files",
29+
"build:node": "node ../../scripts/build.mjs node",
30+
"build:stable": "node ../../scripts/build.mjs stable",
31+
"build:copy-files": "node ../../scripts/copyFiles.mjs",
32+
"build:types": "tsx ../../scripts/buildTypes.mts",
33+
"prebuild": "rimraf build tsconfig.build.tsbuildinfo",
34+
"release": "pnpm build && pnpm publish",
35+
"test": "cd ../../ && cross-env NODE_ENV=test mocha 'packages/mui-utils/**/*.test.?(c|m)[jt]s?(x)'",
36+
"typescript": "tsc -p tsconfig.json",
37+
"attw": "attw --pack ./build --exclude-entrypoints esm modern --include-entrypoints types deepmerge"
38+
},
39+
"dependencies": {
40+
"@babel/runtime": "^7.27.1",
41+
"cssjanus": "^2.0.1"
42+
},
43+
"devDependencies": {
44+
"@mui/internal-test-utils": "workspace:^",
45+
"@types/chai": "^4.3.20",
46+
"@types/mocha": "^10.0.10",
47+
"@types/node": "^20.17.50",
48+
"@types/sinon": "^17.0.4",
49+
"@types/stylis": "4.2.7",
50+
"chai": "^4.5.0",
51+
"sinon": "^19.0.5",
52+
"stylis": "4.3.6"
53+
},
54+
"peerDependencies": {
55+
"stylis": "4.x"
56+
},
57+
"sideEffects": false,
58+
"publishConfig": {
59+
"access": "public",
60+
"directory": "build"
61+
},
62+
"engines": {
63+
"node": ">=14.0.0"
64+
}
65+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
declare module 'cssjanus' {
2+
export function transform(input: string): string;
3+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import { expect } from 'chai';
2+
import { compile, Middleware, middleware, prefixer, serialize, stringify } from 'stylis';
3+
import muiRtlPlugin from '@mui/plugin-rtl';
4+
5+
const stylis = (css: string, extraPlugins: Middleware[] = []) =>
6+
serialize(compile(css), middleware([...extraPlugins, muiRtlPlugin, stringify]));
7+
8+
describe('integration test with stylis', () => {
9+
it('flips simple rules', () => {
10+
expect(
11+
stylis(
12+
`.a {
13+
padding-left: 5px;
14+
margin-right: 5px;
15+
border-left: 1px solid red;
16+
}
17+
`,
18+
),
19+
).to.equal(`.a{padding-right:5px;margin-left:5px;border-right:1px solid red;}`);
20+
});
21+
22+
it('flips shorthands', () => {
23+
expect(
24+
stylis(
25+
`.a {
26+
padding: 0 5px 0 0;
27+
margin: 0 0 0 5px;
28+
}
29+
`,
30+
),
31+
).to.equal(`.a{padding:0 0 0 5px;margin:0 5px 0 0;}`);
32+
});
33+
34+
it('handles noflip directives', () => {
35+
expect(
36+
stylis(
37+
`
38+
.a {
39+
/* @noflip */
40+
padding: 0 5px 0 0;
41+
margin: 0 0 0 5px;
42+
}
43+
`,
44+
),
45+
).to.equal(`.a{padding:0 5px 0 0;margin:0 5px 0 0;}`);
46+
});
47+
48+
it('flips keyframes', () => {
49+
expect(
50+
stylis(
51+
`@keyframes a {
52+
0% { left: 0px; }
53+
100% { left: 100px; }
54+
}
55+
`,
56+
),
57+
).to.equal(`@keyframes a{0%{right:0px;}100%{right:100px;}}`);
58+
});
59+
60+
it('flips media queries', () => {
61+
expect(
62+
stylis(
63+
`@media (min-width: 500px) {
64+
.a {
65+
padding-left: 5px;
66+
margin-right: 5px;
67+
border-left: 1px solid red;
68+
}
69+
}
70+
`,
71+
),
72+
).to.equal(
73+
`@media (min-width: 500px){.a{padding-right:5px;margin-left:5px;border-right:1px solid red;}}`,
74+
);
75+
});
76+
77+
it('flips supports queries', () => {
78+
expect(
79+
stylis(
80+
`@supports (display: flex) {
81+
.a {
82+
padding-left: 5px;
83+
margin-right: 5px;
84+
border-left: 1px solid red;
85+
}
86+
}
87+
`,
88+
),
89+
).to.equal(
90+
`@supports (display: flex){.a{padding-right:5px;margin-left:5px;border-right:1px solid red;}}`,
91+
);
92+
});
93+
94+
it('works in tandem with prefixer', () => {
95+
expect(
96+
stylis(
97+
`@keyframes a {
98+
0% { left: 0px; }
99+
100% { left: 100px; }
100+
}
101+
`,
102+
[prefixer],
103+
),
104+
).to.equal(
105+
`@-webkit-keyframes a{0%{right:0px;}100%{right:100px;}}@keyframes a{0%{right:0px;}100%{right:100px;}}`,
106+
);
107+
});
108+
109+
it("doesn't crash on empty rules", () => {
110+
// this generates nodes for:
111+
// .cls{}
112+
// .cls .nested{color:hotpink;}
113+
expect(
114+
stylis(`
115+
.cls {
116+
& .nested {
117+
color:hotpink;
118+
}
119+
}
120+
`),
121+
).to.equal(`.cls .nested{color:hotpink;}`);
122+
});
123+
124+
it('works for nested rules', () => {
125+
expect(
126+
stylis(`
127+
.cls {
128+
margin-right: 32px;
129+
& .first-child {
130+
margin-right: 32px;
131+
}
132+
}
133+
`),
134+
).to.equal(`.cls{margin-left:32px;}.cls .first-child{margin-left:32px;}`);
135+
});
136+
137+
it('works for layer rules', () => {
138+
expect(
139+
stylis(`
140+
@layer default {
141+
.cls {
142+
margin-right: 32px;
143+
& .first-child {
144+
margin-right: 32px;
145+
}
146+
}
147+
}
148+
`),
149+
).to.equal(`@layer default{.cls{margin-left:32px;}.cls .first-child{margin-left:32px;}}`);
150+
});
151+
152+
it('works for nested layer rules', () => {
153+
expect(
154+
stylis(`
155+
@layer root {
156+
.foo {
157+
margin-right: 32px;
158+
}
159+
@layer default {
160+
.cls {
161+
margin-right: 32px;
162+
& .first-child {
163+
margin-right: 32px;
164+
}
165+
}
166+
}
167+
}
168+
`),
169+
).to.equal(
170+
`@layer root{.foo{margin-left:32px;}@layer default{.cls{margin-left:32px;}.cls .first-child{margin-left:32px;}}}`,
171+
);
172+
});
173+
});
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* Copied from https://github.com/styled-components/stylis-plugin-rtl/blob/main/src/stylis-rtl.ts
3+
* with a modification at line 67 to handle layer rules.
4+
*/
5+
import cssjanus from 'cssjanus';
6+
import {
7+
COMMENT,
8+
compile,
9+
DECLARATION,
10+
IMPORT,
11+
RULESET,
12+
serialize,
13+
strlen,
14+
Middleware,
15+
KEYFRAMES,
16+
MEDIA,
17+
SUPPORTS,
18+
LAYER,
19+
} from 'stylis';
20+
21+
type MiddlewareParams = Parameters<Middleware>;
22+
23+
function stringifyPreserveComments(
24+
element: MiddlewareParams[0],
25+
index: MiddlewareParams[1],
26+
children: MiddlewareParams[2],
27+
): string {
28+
switch (element.type) {
29+
case IMPORT:
30+
case DECLARATION:
31+
case COMMENT:
32+
return (element.return = element.return || element.value);
33+
case RULESET: {
34+
element.value = Array.isArray(element.props) ? element.props.join(',') : element.props;
35+
36+
if (Array.isArray(element.children)) {
37+
element.children.forEach((x) => {
38+
if (x.type === COMMENT) x.children = x.value;
39+
});
40+
}
41+
}
42+
}
43+
44+
const serializedChildren = serialize(
45+
Array.prototype.concat(element.children),
46+
stringifyPreserveComments,
47+
);
48+
49+
return strlen(serializedChildren)
50+
? (element.return = element.value + '{' + serializedChildren + '}')
51+
: '';
52+
}
53+
54+
function stylisRTLPlugin(
55+
element: MiddlewareParams[0],
56+
index: MiddlewareParams[1],
57+
children: MiddlewareParams[2],
58+
callback: MiddlewareParams[3],
59+
): string | void {
60+
if (
61+
element.type === KEYFRAMES ||
62+
element.type === SUPPORTS ||
63+
(element.type === RULESET &&
64+
(!element.parent ||
65+
element.parent.type === MEDIA ||
66+
element.parent.type === RULESET ||
67+
element.parent.type === LAYER))
68+
) {
69+
const stringified = cssjanus.transform(stringifyPreserveComments(element, index, children));
70+
71+
element.children = stringified ? compile(stringified)[0].children : [];
72+
73+
element.return = '';
74+
}
75+
}
76+
77+
// stable identifier that will not be dropped by minification unless the whole module
78+
// is unused
79+
Object.defineProperty(stylisRTLPlugin, 'name', { value: 'stylisRTLPlugin' });
80+
81+
export default stylisRTLPlugin;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
// This config is for emitting declarations (.d.ts) only
3+
// Actual .ts source files are transpiled via babel
4+
"extends": "./tsconfig.json",
5+
"compilerOptions": {
6+
"composite": true,
7+
"declaration": true,
8+
"noEmit": false,
9+
"emitDeclarationOnly": true,
10+
"outDir": "build/esm",
11+
"rootDir": "./src",
12+
"types": ["react", "node"]
13+
},
14+
"include": ["src/**/*.ts"],
15+
"exclude": ["src/**/*.test.ts*", "src/**/*.spec.ts*"],
16+
"references": [{ "path": "../mui-types/tsconfig.build.json" }]
17+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"allowJs": false,
5+
"types": ["react", "mocha", "node"]
6+
},
7+
"include": ["src/**/*"]
8+
}

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)