@unocss/transformer-directives 0.45.7 → 0.45.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <!-- @unocss-ignore -->
4
4
 
5
- UnoCSS transformer for `@apply` and `theme()` directive
5
+ UnoCSS transformer for `@apply`、`@screen` and `theme()` directive
6
6
 
7
7
  ## Install
8
8
 
@@ -44,8 +44,6 @@ Will be transformed to:
44
44
  }
45
45
  ```
46
46
 
47
- > Currently only `@apply` is supported.
48
-
49
47
  #### CSS Variable Style
50
48
 
51
49
  To be compatible with vanilla CSS, you can use CSS Variables to replace the `@apply` directive.
@@ -74,6 +72,139 @@ transformerDirective({
74
72
  })
75
73
  ```
76
74
 
75
+ ### `@screen`
76
+
77
+ The `@screen` directive allows you to create media queries that reference your breakpoints by name comes from [`theme.breakpoints`](https://github.com/unocss/unocss/blob/main/README.md#extend-theme).
78
+
79
+ ```css
80
+ .grid {
81
+ @apply grid grid-cols-2;
82
+ }
83
+ @screen xs {
84
+ .grid {
85
+ @apply grid-cols-1;
86
+ }
87
+ }
88
+ @screen sm {
89
+ .grid {
90
+ @apply grid-cols-3;
91
+ }
92
+ }
93
+ /* ... */
94
+ ...
95
+ ```
96
+
97
+ Will be transformed to:
98
+
99
+ ```css
100
+ .grid {
101
+ display: grid;
102
+ grid-template-columns: repeat(2, minmax(0, 1fr));
103
+ }
104
+ @media (min-width: 320px) {
105
+ .grid {
106
+ grid-template-columns: repeat(1, minmax(0, 1fr));
107
+ }
108
+ }
109
+ @media (min-width: 640px) {
110
+ .grid {
111
+ grid-template-columns: repeat(3, minmax(0, 1fr));
112
+ }
113
+ }
114
+ /* ... */
115
+ ```
116
+
117
+ #### Breakpoint Variant Support
118
+ `@screen` also supports `lt`、`at` variants
119
+
120
+ ##### `@screen lt`
121
+
122
+ ```css
123
+ .grid {
124
+ @apply grid grid-cols-2;
125
+ }
126
+ @screen lt-xs {
127
+ .grid {
128
+ @apply grid-cols-1;
129
+ }
130
+ }
131
+ @screen lt-sm {
132
+ .grid {
133
+ @apply grid-cols-3;
134
+ }
135
+ }
136
+ /* ... */
137
+ ```
138
+
139
+ Will be transformed to:
140
+
141
+ ```css
142
+ .grid {
143
+ display: grid;
144
+ grid-template-columns: repeat(2, minmax(0, 1fr));
145
+ }
146
+ @media (max-width: 319.9px) {
147
+ .grid {
148
+ grid-template-columns: repeat(1, minmax(0, 1fr));
149
+ }
150
+ }
151
+ @media (max-width: 639.9px) {
152
+ .grid {
153
+ grid-template-columns: repeat(3, minmax(0, 1fr));
154
+ }
155
+ }
156
+ /* ... */
157
+ ```
158
+
159
+ ##### `@screen at`
160
+
161
+ ```css
162
+ .grid {
163
+ @apply grid grid-cols-2;
164
+ }
165
+ @screen at-xs {
166
+ .grid {
167
+ @apply grid-cols-1;
168
+ }
169
+ }
170
+ @screen at-xl {
171
+ .grid {
172
+ @apply grid-cols-3;
173
+ }
174
+ }
175
+ @screen at-xxl {
176
+ .grid {
177
+ @apply grid-cols-4;
178
+ }
179
+ }
180
+ /* ... */
181
+ ```
182
+
183
+ Will be transformed to:
184
+
185
+ ```css
186
+ .grid {
187
+ display: grid;
188
+ grid-template-columns: repeat(2, minmax(0, 1fr));
189
+ }
190
+ @media (min-width: 320px) and (max-width: 639.9px) {
191
+ .grid {
192
+ grid-template-columns: repeat(1, minmax(0, 1fr));
193
+ }
194
+ }
195
+ @media (min-width: 1280px) and (max-width: 1535.9px) {
196
+ .grid {
197
+ grid-template-columns: repeat(3, minmax(0, 1fr));
198
+ }
199
+ }
200
+ @media (min-width: 1536px) {
201
+ .grid {
202
+ grid-template-columns: repeat(4, minmax(0, 1fr));
203
+ }
204
+ }
205
+ /* ... */
206
+ ```
207
+
77
208
  ### `theme()`
78
209
 
79
210
  Use the `theme()` function to access your theme config values using dot notation.
package/dist/index.cjs CHANGED
@@ -16,14 +16,16 @@ function transformerDirectives(options = {}) {
16
16
  };
17
17
  }
18
18
  const themeFnRE = /theme\((.*?)\)/g;
19
+ const screenRuleRE = /(@screen) (.+) /g;
19
20
  async function transformDirectives(code, uno, options, filename, originalCode, offset) {
20
21
  const {
21
22
  varStyle = "--at-",
22
23
  throwOnMissing = true
23
24
  } = options;
24
25
  const isApply = code.original.includes("@apply") || varStyle !== false && code.original.includes(varStyle);
26
+ const isScreen = code.original.includes("@screen");
25
27
  const hasThemeFn = code.original.match(themeFnRE);
26
- if (!isApply && !hasThemeFn)
28
+ if (!isApply && !hasThemeFn && !isScreen)
27
29
  return;
28
30
  const ast = cssTree.parse(originalCode || code.original, {
29
31
  parseAtrulePrelude: false,
@@ -123,8 +125,58 @@ async function transformDirectives(code, uno, options, filename, originalCode, o
123
125
  }
124
126
  }
125
127
  };
128
+ const handleScreen = (node) => {
129
+ let breakpointName = "";
130
+ let prefix;
131
+ if (node.name === "screen" && node.prelude?.type === "Raw")
132
+ breakpointName = node.prelude.value.trim();
133
+ if (!breakpointName)
134
+ return;
135
+ const match = breakpointName.match(/^(?:(lt|at)-)?(\w+)$/);
136
+ if (match) {
137
+ prefix = match[1];
138
+ breakpointName = match[2];
139
+ }
140
+ const resolveBreakpoints = () => {
141
+ let breakpoints;
142
+ if (uno.userConfig && uno.userConfig.theme)
143
+ breakpoints = uno.userConfig.theme.breakpoints;
144
+ if (!breakpoints)
145
+ breakpoints = uno.config.theme.breakpoints;
146
+ return breakpoints;
147
+ };
148
+ const variantEntries = Object.entries(resolveBreakpoints() ?? {}).map(([point, size], idx) => [point, size, idx]);
149
+ const generateMediaQuery = (breakpointName2, prefix2) => {
150
+ const [, size, idx] = variantEntries.find((i) => i[0] === breakpointName2);
151
+ if (prefix2) {
152
+ if (prefix2 === "lt")
153
+ return `@media (max-width: ${calcMaxWidthBySize(size)})`;
154
+ else if (prefix2 === "at")
155
+ return `@media (min-width: ${size})${variantEntries[idx + 1] ? ` and (max-width: ${calcMaxWidthBySize(variantEntries[idx + 1][1])})` : ""}`;
156
+ else
157
+ throw new Error(`breakpoint variant not surpported: ${prefix2}`);
158
+ }
159
+ return `@media (min-width: ${size})`;
160
+ };
161
+ if (!variantEntries.find((i) => i[0] === breakpointName))
162
+ throw new Error(`breakpoint ${breakpointName} not found`);
163
+ const offset2 = node.loc.start.offset;
164
+ const str = code.original.slice(offset2, node.loc.end.offset);
165
+ const matches = Array.from(str.matchAll(screenRuleRE));
166
+ if (!matches.length)
167
+ return;
168
+ for (const match2 of matches) {
169
+ code.overwrite(
170
+ offset2 + match2.index,
171
+ offset2 + match2.index + match2[0].length,
172
+ `${generateMediaQuery(breakpointName, prefix)} `
173
+ );
174
+ }
175
+ };
126
176
  const stack = [];
127
177
  const processNode = async (node, _item, _list) => {
178
+ if (isScreen && node.type === "Atrule")
179
+ handleScreen(node);
128
180
  if (hasThemeFn && node.type === "Declaration")
129
181
  handleThemeFn(node);
130
182
  if (isApply && node.type === "Rule") {
@@ -140,6 +192,12 @@ async function transformDirectives(code, uno, options, filename, originalCode, o
140
192
  cssTree.walk(ast, (...args) => stack.push(processNode(...args)));
141
193
  await Promise.all(stack);
142
194
  }
195
+ function calcMaxWidthBySize(size) {
196
+ const value = size.match(/^-?[0-9]+\.?[0-9]*/)?.[0] || "";
197
+ const unit = size.slice(value.length);
198
+ const maxWidth = parseFloat(value) - 0.1;
199
+ return Number.isNaN(maxWidth) ? size : `${maxWidth}${unit}`;
200
+ }
143
201
 
144
202
  exports["default"] = transformerDirectives;
145
203
  exports.transformDirectives = transformDirectives;
package/dist/index.mjs CHANGED
@@ -12,14 +12,16 @@ function transformerDirectives(options = {}) {
12
12
  };
13
13
  }
14
14
  const themeFnRE = /theme\((.*?)\)/g;
15
+ const screenRuleRE = /(@screen) (.+) /g;
15
16
  async function transformDirectives(code, uno, options, filename, originalCode, offset) {
16
17
  const {
17
18
  varStyle = "--at-",
18
19
  throwOnMissing = true
19
20
  } = options;
20
21
  const isApply = code.original.includes("@apply") || varStyle !== false && code.original.includes(varStyle);
22
+ const isScreen = code.original.includes("@screen");
21
23
  const hasThemeFn = code.original.match(themeFnRE);
22
- if (!isApply && !hasThemeFn)
24
+ if (!isApply && !hasThemeFn && !isScreen)
23
25
  return;
24
26
  const ast = parse(originalCode || code.original, {
25
27
  parseAtrulePrelude: false,
@@ -119,8 +121,58 @@ async function transformDirectives(code, uno, options, filename, originalCode, o
119
121
  }
120
122
  }
121
123
  };
124
+ const handleScreen = (node) => {
125
+ let breakpointName = "";
126
+ let prefix;
127
+ if (node.name === "screen" && node.prelude?.type === "Raw")
128
+ breakpointName = node.prelude.value.trim();
129
+ if (!breakpointName)
130
+ return;
131
+ const match = breakpointName.match(/^(?:(lt|at)-)?(\w+)$/);
132
+ if (match) {
133
+ prefix = match[1];
134
+ breakpointName = match[2];
135
+ }
136
+ const resolveBreakpoints = () => {
137
+ let breakpoints;
138
+ if (uno.userConfig && uno.userConfig.theme)
139
+ breakpoints = uno.userConfig.theme.breakpoints;
140
+ if (!breakpoints)
141
+ breakpoints = uno.config.theme.breakpoints;
142
+ return breakpoints;
143
+ };
144
+ const variantEntries = Object.entries(resolveBreakpoints() ?? {}).map(([point, size], idx) => [point, size, idx]);
145
+ const generateMediaQuery = (breakpointName2, prefix2) => {
146
+ const [, size, idx] = variantEntries.find((i) => i[0] === breakpointName2);
147
+ if (prefix2) {
148
+ if (prefix2 === "lt")
149
+ return `@media (max-width: ${calcMaxWidthBySize(size)})`;
150
+ else if (prefix2 === "at")
151
+ return `@media (min-width: ${size})${variantEntries[idx + 1] ? ` and (max-width: ${calcMaxWidthBySize(variantEntries[idx + 1][1])})` : ""}`;
152
+ else
153
+ throw new Error(`breakpoint variant not surpported: ${prefix2}`);
154
+ }
155
+ return `@media (min-width: ${size})`;
156
+ };
157
+ if (!variantEntries.find((i) => i[0] === breakpointName))
158
+ throw new Error(`breakpoint ${breakpointName} not found`);
159
+ const offset2 = node.loc.start.offset;
160
+ const str = code.original.slice(offset2, node.loc.end.offset);
161
+ const matches = Array.from(str.matchAll(screenRuleRE));
162
+ if (!matches.length)
163
+ return;
164
+ for (const match2 of matches) {
165
+ code.overwrite(
166
+ offset2 + match2.index,
167
+ offset2 + match2.index + match2[0].length,
168
+ `${generateMediaQuery(breakpointName, prefix)} `
169
+ );
170
+ }
171
+ };
122
172
  const stack = [];
123
173
  const processNode = async (node, _item, _list) => {
174
+ if (isScreen && node.type === "Atrule")
175
+ handleScreen(node);
124
176
  if (hasThemeFn && node.type === "Declaration")
125
177
  handleThemeFn(node);
126
178
  if (isApply && node.type === "Rule") {
@@ -136,5 +188,11 @@ async function transformDirectives(code, uno, options, filename, originalCode, o
136
188
  walk(ast, (...args) => stack.push(processNode(...args)));
137
189
  await Promise.all(stack);
138
190
  }
191
+ function calcMaxWidthBySize(size) {
192
+ const value = size.match(/^-?[0-9]+\.?[0-9]*/)?.[0] || "";
193
+ const unit = size.slice(value.length);
194
+ const maxWidth = parseFloat(value) - 0.1;
195
+ return Number.isNaN(maxWidth) ? size : `${maxWidth}${unit}`;
196
+ }
139
197
 
140
198
  export { transformerDirectives as default, transformDirectives };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unocss/transformer-directives",
3
- "version": "0.45.7",
3
+ "version": "0.45.12",
4
4
  "description": "UnoCSS transformer for `@apply` directive",
5
5
  "author": "hannoeru <me@hanlee.co>",
6
6
  "license": "MIT",
@@ -31,8 +31,8 @@
31
31
  "dist"
32
32
  ],
33
33
  "dependencies": {
34
- "@unocss/core": "0.45.7",
35
- "css-tree": "^2.1.0"
34
+ "@unocss/core": "0.45.12",
35
+ "css-tree": "^2.2.1"
36
36
  },
37
37
  "devDependencies": {
38
38
  "magic-string": "^0.26.2"