@unocss/transformer-directives 0.62.3 → 0.63.0

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.mjs +123 -62
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -11,9 +11,9 @@ npm i -D @unocss/transformer-directives
11
11
  ```
12
12
 
13
13
  ```ts
14
+ import transformerDirectives from '@unocss/transformer-directives'
14
15
  // uno.config.ts
15
16
  import { defineConfig } from 'unocss'
16
- import transformerDirectives from '@unocss/transformer-directives'
17
17
 
18
18
  export default defineConfig({
19
19
  // ...
package/dist/index.mjs CHANGED
@@ -1,62 +1,6 @@
1
1
  import { expandVariantGroup, notNull, regexScopePlaceholder, toArray, cssIdRE } from '@unocss/core';
2
+ import { transformThemeString, hasThemeFn, hasIconFn } from '@unocss/rule-utils';
2
3
  import { generate, parse, clone, List, walk } from 'css-tree';
3
- import { transformThemeString, hasThemeFn } from '@unocss/rule-utils';
4
-
5
- const screenRuleRE = /(@screen [^{]+)(.+)/g;
6
- function handleScreen({ code, uno }, node) {
7
- let breakpointName = "";
8
- let prefix = "";
9
- if (node.name === "screen" && node.prelude?.type === "Raw")
10
- breakpointName = node.prelude.value.trim();
11
- if (!breakpointName)
12
- return;
13
- const match = breakpointName.match(/^(?:(lt|at)-)?(\w+)$/);
14
- if (match) {
15
- prefix = match[1];
16
- breakpointName = match[2];
17
- }
18
- const resolveBreakpoints = () => {
19
- let breakpoints;
20
- if (uno.userConfig && uno.userConfig.theme)
21
- breakpoints = uno.userConfig.theme.breakpoints;
22
- if (!breakpoints)
23
- breakpoints = uno.config.theme.breakpoints;
24
- return breakpoints ? Object.entries(breakpoints).sort((a, b) => Number.parseInt(a[1].replace(/[a-z]+/gi, "")) - Number.parseInt(b[1].replace(/[a-z]+/gi, ""))).map(([point, size]) => ({ point, size })) : void 0;
25
- };
26
- const variantEntries = (resolveBreakpoints() ?? []).map(({ point, size }, idx) => [point, size, idx]);
27
- const generateMediaQuery = (breakpointName2, prefix2) => {
28
- const [, size, idx] = variantEntries.find((i) => i[0] === breakpointName2);
29
- if (prefix2) {
30
- if (prefix2 === "lt")
31
- return `@media (max-width: ${calcMaxWidthBySize(size)})`;
32
- else if (prefix2 === "at")
33
- return `@media (min-width: ${size})${variantEntries[idx + 1] ? ` and (max-width: ${calcMaxWidthBySize(variantEntries[idx + 1][1])})` : ""}`;
34
- else
35
- throw new Error(`breakpoint variant not supported: ${prefix2}`);
36
- }
37
- return `@media (min-width: ${size})`;
38
- };
39
- if (!variantEntries.find((i) => i[0] === breakpointName))
40
- throw new Error(`breakpoint ${breakpointName} not found`);
41
- const offset = node.loc.start.offset;
42
- const str = code.original.slice(offset, node.loc.end.offset);
43
- const matches = Array.from(str.matchAll(screenRuleRE));
44
- if (!matches.length)
45
- return;
46
- for (const match2 of matches) {
47
- code.overwrite(
48
- offset + match2.index,
49
- offset + match2.index + match2[1].length,
50
- `${generateMediaQuery(breakpointName, prefix)}`
51
- );
52
- }
53
- }
54
- function calcMaxWidthBySize(size) {
55
- const value = size.match(/^-?\d+\.?\d*/)?.[0] || "";
56
- const unit = size.slice(value.length);
57
- const maxWidth = Number.parseFloat(value) - 0.1;
58
- return Number.isNaN(maxWidth) ? size : `${maxWidth}${unit}`;
59
- }
60
4
 
61
5
  async function handleApply(ctx, node) {
62
6
  const { code, uno, options, filename } = ctx;
@@ -110,7 +54,7 @@ async function parseApply({ code, uno, applyVariable }, node, childNode) {
110
54
  context: "rule"
111
55
  });
112
56
  const prelude = clone(node.prelude);
113
- prelude.children.forEach((child) => {
57
+ prelude.children?.forEach((child) => {
114
58
  const selectorListAst = clone(ruleAST.prelude);
115
59
  const classSelectors = new List();
116
60
  selectorListAst.children.forEach((selectorAst) => {
@@ -145,7 +89,58 @@ function removeComments(value) {
145
89
  return value.replace(/(\/\*(?:.|\n)*?\*\/)|(\/\/.*)/g, "");
146
90
  }
147
91
 
148
- function handleFunction({ code, uno, options }, node) {
92
+ async function transformIconString(uno, icon, color) {
93
+ const presetIcons = uno.userConfig.presets?.flat()?.findLast((i) => i.name === "@unocss/preset-icons");
94
+ if (!presetIcons) {
95
+ console.warn("@unocss/preset-icons not found, icon() directive will be keep as-is");
96
+ return;
97
+ }
98
+ const {
99
+ scale = 1,
100
+ prefix = "i-",
101
+ collections: customCollections,
102
+ extraProperties = {},
103
+ customizations = {},
104
+ autoInstall = false,
105
+ collectionsNodeResolvePath,
106
+ unit
107
+ } = presetIcons.options;
108
+ const api = presetIcons.api;
109
+ const loaderOptions = {
110
+ addXmlNs: true,
111
+ scale,
112
+ customCollections,
113
+ autoInstall,
114
+ cwd: collectionsNodeResolvePath,
115
+ // avoid warn from @iconify/loader: we'll warn below if not found
116
+ warn: void 0,
117
+ customizations: {
118
+ ...customizations,
119
+ additionalProps: { ...extraProperties },
120
+ trimCustomSvg: true,
121
+ async iconCustomizer(collection, icon2, props) {
122
+ await customizations.iconCustomizer?.(collection, icon2, props);
123
+ if (unit) {
124
+ if (!props.width)
125
+ props.width = `${scale}${unit}`;
126
+ if (!props.height)
127
+ props.height = `${scale}${unit}`;
128
+ }
129
+ }
130
+ }
131
+ };
132
+ const loader = await api.createNodeLoader?.() || (async () => void 0);
133
+ for (const p of toArray(prefix)) {
134
+ if (icon.startsWith(p)) {
135
+ icon = icon.slice(p.length);
136
+ const parsed = await api.parseIconWithLoader(icon, loader, loaderOptions);
137
+ if (parsed)
138
+ return `url("data:image/svg+xml;utf8,${color ? api.encodeSvgForCss(parsed.svg).replace(/currentcolor/gi, color) : api.encodeSvgForCss(parsed.svg)}")`;
139
+ }
140
+ }
141
+ }
142
+
143
+ async function handleFunction({ code, uno, options }, node) {
149
144
  const { throwOnMissing = true } = options;
150
145
  switch (node.name) {
151
146
  case "theme": {
@@ -155,9 +150,75 @@ function handleFunction({ code, uno, options }, node) {
155
150
  const value = transformThemeString(themeStr, uno.config.theme, throwOnMissing);
156
151
  if (value)
157
152
  code.overwrite(node.loc.start.offset, node.loc.end.offset, value);
153
+ break;
154
+ }
155
+ case "icon": {
156
+ const params = node.children.toArray().filter((i) => i.type === "String").map((i) => i.value);
157
+ if (params.length === 0)
158
+ throw new Error("icon() expects at least one argument");
159
+ const value = await transformIconString(uno, ...params);
160
+ if (value)
161
+ code.overwrite(node.loc.start.offset, node.loc.end.offset, value);
162
+ break;
163
+ }
164
+ }
165
+ }
166
+
167
+ const screenRuleRE = /(@screen [^{]+)(.+)/g;
168
+ function handleScreen({ code, uno }, node) {
169
+ let breakpointName = "";
170
+ let prefix = "";
171
+ if (node.name === "screen" && node.prelude?.type === "Raw")
172
+ breakpointName = node.prelude.value.trim();
173
+ if (!breakpointName)
174
+ return;
175
+ const match = breakpointName.match(/^(?:(lt|at)-)?(\w+)$/);
176
+ if (match) {
177
+ prefix = match[1];
178
+ breakpointName = match[2];
179
+ }
180
+ const resolveBreakpoints = () => {
181
+ let breakpoints;
182
+ if (uno.userConfig && uno.userConfig.theme)
183
+ breakpoints = uno.userConfig.theme.breakpoints;
184
+ if (!breakpoints)
185
+ breakpoints = uno.config.theme.breakpoints;
186
+ return breakpoints ? Object.entries(breakpoints).sort((a, b) => Number.parseInt(a[1].replace(/[a-z]+/gi, "")) - Number.parseInt(b[1].replace(/[a-z]+/gi, ""))).map(([point, size]) => ({ point, size })) : void 0;
187
+ };
188
+ const variantEntries = (resolveBreakpoints() ?? []).map(({ point, size }, idx) => [point, size, idx]);
189
+ const generateMediaQuery = (breakpointName2, prefix2) => {
190
+ const [, size, idx] = variantEntries.find((i) => i[0] === breakpointName2);
191
+ if (prefix2) {
192
+ if (prefix2 === "lt")
193
+ return `@media (max-width: ${calcMaxWidthBySize(size)})`;
194
+ else if (prefix2 === "at")
195
+ return `@media (min-width: ${size})${variantEntries[idx + 1] ? ` and (max-width: ${calcMaxWidthBySize(variantEntries[idx + 1][1])})` : ""}`;
196
+ else
197
+ throw new Error(`breakpoint variant not supported: ${prefix2}`);
158
198
  }
199
+ return `@media (min-width: ${size})`;
200
+ };
201
+ if (!variantEntries.find((i) => i[0] === breakpointName))
202
+ throw new Error(`breakpoint ${breakpointName} not found`);
203
+ const offset = node.loc.start.offset;
204
+ const str = code.original.slice(offset, node.loc.end.offset);
205
+ const matches = Array.from(str.matchAll(screenRuleRE));
206
+ if (!matches.length)
207
+ return;
208
+ for (const match2 of matches) {
209
+ code.overwrite(
210
+ offset + match2.index,
211
+ offset + match2.index + match2[1].length,
212
+ `${generateMediaQuery(breakpointName, prefix)}`
213
+ );
159
214
  }
160
215
  }
216
+ function calcMaxWidthBySize(size) {
217
+ const value = size.match(/^-?\d+\.?\d*/)?.[0] || "";
218
+ const unit = size.slice(value.length);
219
+ const maxWidth = Number.parseFloat(value) - 0.1;
220
+ return Number.isNaN(maxWidth) ? size : `${maxWidth}${unit}`;
221
+ }
161
222
 
162
223
  async function transformDirectives(code, uno, options, filename, originalCode, offset) {
163
224
  let { applyVariable } = options;
@@ -171,8 +232,8 @@ async function transformDirectives(code, uno, options, filename, originalCode, o
171
232
  const parseCode = originalCode || code.original;
172
233
  const hasApply = parseCode.includes("@apply") || applyVariable.some((s) => parseCode.includes(s));
173
234
  const hasScreen = parseCode.includes("@screen");
174
- const hasThemeFn$1 = hasThemeFn(parseCode);
175
- if (!hasApply && !hasThemeFn$1 && !hasScreen)
235
+ const hasFn = hasThemeFn(parseCode) || hasIconFn(parseCode);
236
+ if (!hasApply && !hasFn && !hasScreen)
176
237
  return;
177
238
  const ast = parse(parseCode, {
178
239
  parseCustomProperty: true,
@@ -196,7 +257,7 @@ async function transformDirectives(code, uno, options, filename, originalCode, o
196
257
  if (hasScreen && node.type === "Atrule")
197
258
  handleScreen(ctx, node);
198
259
  if (node.type === "Function")
199
- handleFunction(ctx, node);
260
+ await handleFunction(ctx, node);
200
261
  if (hasApply && node.type === "Rule")
201
262
  await handleApply(ctx, node);
202
263
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@unocss/transformer-directives",
3
3
  "type": "module",
4
- "version": "0.62.3",
4
+ "version": "0.63.0",
5
5
  "description": "UnoCSS transformer for `@apply` directive",
6
6
  "author": "hannoeru <me@hanlee.co>",
7
7
  "license": "MIT",
@@ -32,9 +32,9 @@
32
32
  "dist"
33
33
  ],
34
34
  "dependencies": {
35
- "css-tree": "^2.3.1",
36
- "@unocss/core": "0.62.3",
37
- "@unocss/rule-utils": "0.62.3"
35
+ "css-tree": "^3.0.0",
36
+ "@unocss/core": "0.63.0",
37
+ "@unocss/rule-utils": "0.63.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "magic-string": "^0.30.11"