@terrazzo/plugin-css 0.0.3 → 0.0.5
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 +2 -2
- package/dist/build.d.ts +8 -0
- package/dist/build.js +94 -0
- package/dist/build.js.map +1 -0
- package/dist/index.d.ts +4 -21
- package/dist/index.js +24 -258
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +34 -0
- package/dist/lib.js +64 -0
- package/dist/lib.js.map +1 -0
- package/package.json +6 -6
- package/src/build.ts +108 -0
- package/src/index.ts +29 -300
- package/src/lib.ts +110 -0
package/README.md
CHANGED
package/dist/build.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BuildHookOptions } from '@terrazzo/parser';
|
|
2
|
+
import { type CSSPluginOptions } from './lib.js';
|
|
3
|
+
export interface BuildFormatOptions {
|
|
4
|
+
exclude: CSSPluginOptions['exclude'];
|
|
5
|
+
getTransforms: BuildHookOptions['getTransforms'];
|
|
6
|
+
modeSelectors: CSSPluginOptions['modeSelectors'];
|
|
7
|
+
}
|
|
8
|
+
export default function buildFormat({ getTransforms, exclude, modeSelectors }: BuildFormatOptions): string;
|
package/dist/build.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { isTokenMatch } from '@terrazzo/token-tools';
|
|
2
|
+
import { generateShorthand } from '@terrazzo/token-tools/css';
|
|
3
|
+
import { printRules, FORMAT_ID } from './lib.js';
|
|
4
|
+
const P3_MQ = '@media (color-gamut: p3)';
|
|
5
|
+
const REC2020_MQ = '@media (color-gamut: rec2020)';
|
|
6
|
+
export default function buildFormat({ getTransforms, exclude, modeSelectors }) {
|
|
7
|
+
const rules = [];
|
|
8
|
+
// :root
|
|
9
|
+
const rootTokens = getTransforms({ format: FORMAT_ID, mode: '.' });
|
|
10
|
+
if (rootTokens.length) {
|
|
11
|
+
const rootRule = { selectors: [':root'], declarations: {} };
|
|
12
|
+
// note: "nestedQuery" was designed specifically for higher-gamut colors to
|
|
13
|
+
// apply color-gamut media queries to existing selectors (i.e. keep the same
|
|
14
|
+
// targets, and apply another nested layer of media query filtering based on
|
|
15
|
+
// hardware). Because of how CSS works they need to get built out into their
|
|
16
|
+
// own selectors that have different structures depending on whether
|
|
17
|
+
// `selectors` has a media query or not.
|
|
18
|
+
const p3Rule = { selectors: [':root'], nestedQuery: P3_MQ, declarations: {} };
|
|
19
|
+
const rec2020Rule = { selectors: [':root'], nestedQuery: REC2020_MQ, declarations: {} };
|
|
20
|
+
rules.push(rootRule, p3Rule, rec2020Rule);
|
|
21
|
+
for (const token of rootTokens) {
|
|
22
|
+
if (isTokenMatch(token.token.id, exclude ?? [])) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const localID = token.localID ?? token.token.id;
|
|
26
|
+
// single-value token
|
|
27
|
+
if (token.type === 'SINGLE_VALUE') {
|
|
28
|
+
rootRule.declarations[localID] = token.value;
|
|
29
|
+
}
|
|
30
|
+
// multi-value token (wide gamut color)
|
|
31
|
+
else if (token.value.srgb && token.value.p3 && token.value.rec2020) {
|
|
32
|
+
rootRule.declarations[localID] = token.value.srgb;
|
|
33
|
+
if (token.value.p3 !== token.value.srgb) {
|
|
34
|
+
p3Rule.declarations[localID] = token.value.p3;
|
|
35
|
+
rec2020Rule.declarations[localID] = token.value.rec2020;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// multi-value token
|
|
39
|
+
else if (token.type === 'MULTI_VALUE') {
|
|
40
|
+
const shorthand = generateShorthand({ $type: token.token.$type, localID });
|
|
41
|
+
if (shorthand) {
|
|
42
|
+
rootRule.declarations[token.localID ?? token.token.id] = shorthand;
|
|
43
|
+
}
|
|
44
|
+
for (const [name, value] of Object.entries(token.value)) {
|
|
45
|
+
rootRule.declarations[name === '.' ? localID : [localID, name].join('-')] = value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// modeSelectors (note: without these, modes won’t get written to CSS)
|
|
51
|
+
for (const { selectors, tokens, mode } of modeSelectors ?? []) {
|
|
52
|
+
if (!selectors.length) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const selectorTokens = getTransforms({ format: 'css', id: tokens, mode });
|
|
56
|
+
if (!selectorTokens.length) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const selectorRule = { selectors, declarations: {} };
|
|
60
|
+
const selectorP3Rule = { selectors, nestedQuery: P3_MQ, declarations: {} };
|
|
61
|
+
const selectorRec2020Rule = { selectors, nestedQuery: REC2020_MQ, declarations: {} };
|
|
62
|
+
rules.push(selectorRule, selectorP3Rule, selectorRec2020Rule);
|
|
63
|
+
for (const token of selectorTokens) {
|
|
64
|
+
if (token.token.aliasOf) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const localID = token.localID ?? token.token.id;
|
|
68
|
+
// single-value token
|
|
69
|
+
if (token.type === 'SINGLE_VALUE') {
|
|
70
|
+
selectorRule.declarations[localID] = token.value;
|
|
71
|
+
}
|
|
72
|
+
// multi-value token (wide gamut color)
|
|
73
|
+
else if (token.value.srgb && token.value.p3 && token.value.rec2020) {
|
|
74
|
+
selectorRule.declarations[localID] = token.value.srgb;
|
|
75
|
+
if (token.value.p3 !== token.value.srgb) {
|
|
76
|
+
selectorP3Rule.declarations[localID] = token.value.p3;
|
|
77
|
+
selectorRec2020Rule.declarations[localID] = token.value.rec2020;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// multi-value token
|
|
81
|
+
else {
|
|
82
|
+
const shorthand = generateShorthand({ $type: token.token.$type, localID });
|
|
83
|
+
if (shorthand) {
|
|
84
|
+
selectorRule.declarations[localID] = shorthand;
|
|
85
|
+
}
|
|
86
|
+
for (const [name, subvalue] of Object.entries(token.value)) {
|
|
87
|
+
selectorRule.declarations[`${localID}-${name}`] = subvalue;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return printRules(rules);
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
import type { Plugin } from '@terrazzo/parser';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
tokens?: string[];
|
|
7
|
-
/** Provide CSS selectors to generate. (e.g.: `["@media (prefers-color-scheme: dark)", "[data-color-theme='dark']"]` ) */
|
|
8
|
-
selectors: string[];
|
|
9
|
-
}
|
|
10
|
-
export interface CSSPluginOptions {
|
|
11
|
-
/** Where to output CSS */
|
|
12
|
-
filename?: string;
|
|
13
|
-
/** Glob patterns to exclude tokens from output */
|
|
14
|
-
exclude?: string[];
|
|
15
|
-
/** Define mode selectors as media queries or CSS classes */
|
|
16
|
-
modeSelectors?: ModeSelector[];
|
|
17
|
-
/** Control the final CSS variable name */
|
|
18
|
-
variableName?: (name: string) => string;
|
|
19
|
-
}
|
|
20
|
-
export declare const FORMAT_ID = "css";
|
|
21
|
-
export declare const FILE_PREFIX = "/* -------------------------------------------\n * Autogenerated by \uD83D\uDCA0 Terrazzo. DO NOT EDIT!\n * ------------------------------------------- */";
|
|
22
|
-
export default function cssPlugin({ filename, exclude, variableName, modeSelectors, }?: CSSPluginOptions): Plugin;
|
|
2
|
+
import { type CSSPluginOptions } from './lib.js';
|
|
3
|
+
export * from './build.js';
|
|
4
|
+
export * from './lib.js';
|
|
5
|
+
export default function cssPlugin({ filename, exclude, variableName, modeSelectors, transform: customTransform, }?: CSSPluginOptions): Plugin;
|
package/dist/index.js
CHANGED
|
@@ -22,279 +22,45 @@
|
|
|
22
22
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
23
|
* SOFTWARE.
|
|
24
24
|
*/
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
export default function cssPlugin({ filename = './index.css', exclude, variableName, modeSelectors, } = {}) {
|
|
25
|
+
import { makeCSSVar, transformCSSValue } from '@terrazzo/token-tools/css';
|
|
26
|
+
import { FILE_PREFIX, FORMAT_ID } from './lib.js';
|
|
27
|
+
import buildFormat from './build.js';
|
|
28
|
+
import { validateCustomTransform } from '@terrazzo/token-tools';
|
|
29
|
+
export * from './build.js';
|
|
30
|
+
export * from './lib.js';
|
|
31
|
+
export default function cssPlugin({ filename = './index.css', exclude, variableName, modeSelectors, transform: customTransform, } = {}) {
|
|
32
32
|
const transformName = (id) => variableName?.(id) || makeCSSVar(id);
|
|
33
33
|
const transformAlias = (id) => `var(${transformName(id)})`;
|
|
34
34
|
return {
|
|
35
35
|
name: '@terrazzo/plugin-css',
|
|
36
|
-
async transform({ tokens, setTransform }) {
|
|
36
|
+
async transform({ tokens, getTransforms, setTransform }) {
|
|
37
|
+
// skip work if another .css plugin has already run
|
|
38
|
+
const cssTokens = getTransforms({ format: FORMAT_ID, id: '*', mode: '*' });
|
|
39
|
+
if (cssTokens.length) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
37
42
|
for (const id in tokens) {
|
|
38
|
-
if (!Object.hasOwn(tokens, id)) {
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
43
|
const token = tokens[id];
|
|
42
44
|
const localID = transformName(id);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
value: transformBooleanValue($value, { aliasOf, transformAlias }),
|
|
51
|
-
mode,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
case 'border': {
|
|
57
|
-
for (const mode in token.mode) {
|
|
58
|
-
const { $value, aliasOf, partialAliasOf } = token.mode[mode];
|
|
59
|
-
setTransform(id, {
|
|
60
|
-
format: FORMAT_ID,
|
|
61
|
-
localID,
|
|
62
|
-
value: transformBorderValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
63
|
-
mode,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
case 'color': {
|
|
69
|
-
for (const mode in token.mode) {
|
|
70
|
-
const { $value, aliasOf } = token.mode[mode];
|
|
71
|
-
setTransform(id, {
|
|
72
|
-
format: FORMAT_ID,
|
|
73
|
-
localID,
|
|
74
|
-
value: transformColorValue($value, { aliasOf, transformAlias }),
|
|
75
|
-
mode,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
case 'cubicBezier': {
|
|
81
|
-
for (const mode in token.mode) {
|
|
82
|
-
const { $value, aliasOf, partialAliasOf } = token.mode[mode];
|
|
83
|
-
setTransform(id, {
|
|
84
|
-
format: FORMAT_ID,
|
|
85
|
-
localID,
|
|
86
|
-
value: transformCubicBezierValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
87
|
-
mode,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
case 'dimension': {
|
|
93
|
-
for (const mode in token.mode) {
|
|
94
|
-
const { $value, aliasOf } = token.mode[mode];
|
|
95
|
-
setTransform(id, {
|
|
96
|
-
format: FORMAT_ID,
|
|
97
|
-
localID,
|
|
98
|
-
value: transformDimensionValue($value, { aliasOf, transformAlias }),
|
|
99
|
-
mode,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
case 'duration': {
|
|
105
|
-
for (const mode in token.mode) {
|
|
106
|
-
const { $value, aliasOf } = token.mode[mode];
|
|
107
|
-
setTransform(id, {
|
|
108
|
-
format: FORMAT_ID,
|
|
109
|
-
localID,
|
|
110
|
-
value: transformDurationValue($value, { aliasOf, transformAlias }),
|
|
111
|
-
mode,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
case 'fontFamily': {
|
|
117
|
-
for (const mode in token.mode) {
|
|
118
|
-
const { $value, aliasOf, partialAliasOf } = token.mode[mode];
|
|
119
|
-
setTransform(id, {
|
|
120
|
-
format: FORMAT_ID,
|
|
121
|
-
localID,
|
|
122
|
-
value: transformFontFamilyValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
123
|
-
mode,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
case 'fontWeight': {
|
|
129
|
-
for (const mode in token.mode) {
|
|
130
|
-
const { $value, aliasOf } = token.mode[mode];
|
|
131
|
-
setTransform(id, {
|
|
132
|
-
format: FORMAT_ID,
|
|
133
|
-
localID,
|
|
134
|
-
value: transformFontWeightValue($value, { aliasOf, transformAlias }),
|
|
135
|
-
mode,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
case 'gradient': {
|
|
141
|
-
for (const mode in token.mode) {
|
|
142
|
-
const { $value, aliasOf, partialAliasOf } = token.mode[mode];
|
|
143
|
-
setTransform(id, {
|
|
144
|
-
format: FORMAT_ID,
|
|
145
|
-
localID,
|
|
146
|
-
value: transformGradientValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
case 'link': {
|
|
152
|
-
for (const mode in token.mode) {
|
|
153
|
-
const { $value, aliasOf } = token.mode[mode];
|
|
154
|
-
setTransform(id, {
|
|
155
|
-
format: FORMAT_ID,
|
|
156
|
-
localID,
|
|
157
|
-
value: transformLinkValue($value, { aliasOf, transformAlias }),
|
|
158
|
-
mode,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
case 'number': {
|
|
164
|
-
for (const mode in token.mode) {
|
|
165
|
-
const { $value, aliasOf } = token.mode[mode];
|
|
166
|
-
setTransform(id, {
|
|
167
|
-
format: FORMAT_ID,
|
|
168
|
-
localID,
|
|
169
|
-
value: transformNumberValue($value, { aliasOf, transformAlias }),
|
|
170
|
-
mode,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
|
-
case 'shadow': {
|
|
176
|
-
for (const mode in token.mode) {
|
|
177
|
-
const { $value, aliasOf, partialAliasOf } = token.mode[mode];
|
|
178
|
-
setTransform(id, {
|
|
179
|
-
format: FORMAT_ID,
|
|
180
|
-
localID,
|
|
181
|
-
value: transformShadowValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
182
|
-
mode,
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
break;
|
|
186
|
-
}
|
|
187
|
-
case 'string': {
|
|
188
|
-
for (const mode in token.mode) {
|
|
189
|
-
const { $value, aliasOf } = token.mode[mode];
|
|
190
|
-
setTransform(id, {
|
|
191
|
-
format: FORMAT_ID,
|
|
192
|
-
localID,
|
|
193
|
-
value: transformStringValue($value, { aliasOf, transformAlias }),
|
|
194
|
-
mode,
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
case 'strokeStyle': {
|
|
200
|
-
for (const mode in token.mode) {
|
|
201
|
-
const { $value, aliasOf } = token.mode[mode];
|
|
202
|
-
setTransform(id, {
|
|
203
|
-
format: FORMAT_ID,
|
|
204
|
-
localID,
|
|
205
|
-
value: transformStrokeStyleValue($value, { aliasOf, transformAlias }),
|
|
206
|
-
mode,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
case 'transition': {
|
|
212
|
-
for (const mode in token.mode) {
|
|
213
|
-
const { $value, aliasOf, partialAliasOf } = token.mode[mode];
|
|
214
|
-
setTransform(id, {
|
|
215
|
-
format: FORMAT_ID,
|
|
216
|
-
localID,
|
|
217
|
-
value: transformTransitionValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
218
|
-
mode,
|
|
219
|
-
});
|
|
45
|
+
for (const mode in token.mode) {
|
|
46
|
+
if (customTransform) {
|
|
47
|
+
const value = customTransform(token, mode);
|
|
48
|
+
if (value !== undefined && value !== null) {
|
|
49
|
+
validateCustomTransform(value, { $type: token.$type });
|
|
50
|
+
setTransform(id, { format: FORMAT_ID, localID, value, mode });
|
|
51
|
+
continue;
|
|
220
52
|
}
|
|
221
|
-
break;
|
|
222
53
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
setTransform(id, {
|
|
227
|
-
format: FORMAT_ID,
|
|
228
|
-
localID,
|
|
229
|
-
value: transformTypographyValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
230
|
-
mode,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
break;
|
|
54
|
+
const transformedValue = transformCSSValue(token, { mode, transformAlias });
|
|
55
|
+
if (transformedValue !== undefined) {
|
|
56
|
+
setTransform(id, { format: FORMAT_ID, localID, value: transformedValue, mode });
|
|
234
57
|
}
|
|
235
58
|
}
|
|
236
59
|
}
|
|
237
60
|
},
|
|
238
61
|
async build({ getTransforms, outputFile }) {
|
|
239
62
|
const output = [FILE_PREFIX, ''];
|
|
240
|
-
|
|
241
|
-
output.push(':root {');
|
|
242
|
-
const rootTokens = getTransforms({ format: 'css', mode: '.' });
|
|
243
|
-
for (const token of rootTokens) {
|
|
244
|
-
if (isTokenMatch(token.token.id, exclude ?? [])) {
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
const localID = token.localID ?? token.token.id;
|
|
248
|
-
if (token.type === 'SINGLE_VALUE') {
|
|
249
|
-
output.push(` ${localID}: ${token.value};`);
|
|
250
|
-
}
|
|
251
|
-
else if (token.type === 'MULTI_VALUE') {
|
|
252
|
-
const shorthand = generateShorthand({ $type: token.token.$type, localID });
|
|
253
|
-
if (shorthand) {
|
|
254
|
-
output.push(` ${token.localID ?? token.token.id}: ${shorthand};`);
|
|
255
|
-
}
|
|
256
|
-
for (const [name, value] of Object.entries(token.value)) {
|
|
257
|
-
output.push(` ${name === '.' ? localID : [localID, name].join('-')}: ${value};`);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
output.push('}');
|
|
262
|
-
for (const { selectors, tokens, mode } of modeSelectors ?? []) {
|
|
263
|
-
const selectorTokens = getTransforms({ format: 'css', id: tokens, mode });
|
|
264
|
-
if (!selectorTokens.length) {
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
let indent = ' ';
|
|
268
|
-
for (const selector of selectors) {
|
|
269
|
-
output.push('', `${selector} {`);
|
|
270
|
-
if (selector.startsWith('@')) {
|
|
271
|
-
output.push(' :root {');
|
|
272
|
-
indent = ' ';
|
|
273
|
-
}
|
|
274
|
-
for (const token of selectorTokens) {
|
|
275
|
-
if (token.token.aliasOf) {
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
const localID = token.localID ?? token.token.id;
|
|
279
|
-
if (token.type === 'SINGLE_VALUE') {
|
|
280
|
-
output.push(`${indent}${localID}: ${token.value};`);
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
const shorthand = generateShorthand({ $type: token.token.$type, localID });
|
|
284
|
-
if (shorthand) {
|
|
285
|
-
output.push(`${indent}${localID}: ${shorthand};`);
|
|
286
|
-
}
|
|
287
|
-
for (const [name, subvalue] of Object.entries(token.value)) {
|
|
288
|
-
output.push(`${indent}${localID}-${name}: ${subvalue};`);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
if (selector.startsWith('@')) {
|
|
293
|
-
output.push(' }');
|
|
294
|
-
}
|
|
295
|
-
output.push('}');
|
|
296
|
-
}
|
|
297
|
-
}
|
|
63
|
+
output.push(buildFormat({ exclude, getTransforms, modeSelectors }), '\n');
|
|
298
64
|
outputFile(filename, output.join('\n'));
|
|
299
65
|
},
|
|
300
66
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,SAAS,EAAyB,MAAM,UAAU,CAAC;AACzE,OAAO,WAAW,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AAEzB,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,QAAQ,GAAG,aAAa,EACxB,OAAO,EACP,YAAY,EACZ,aAAa,EACb,SAAS,EAAE,eAAe,MACN,EAAE;IACtB,MAAM,aAAa,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IAC3E,MAAM,cAAc,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,OAAO,aAAa,CAAC,EAAE,CAAC,GAAG,CAAC;IAEnE,OAAO;QACL,IAAI,EAAE,sBAAsB;QAC5B,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE;YACrD,mDAAmD;YACnD,MAAM,SAAS,GAAG,aAAa,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;YAC3E,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;gBACrB,OAAO;YACT,CAAC;YAED,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,CAAC;gBACxB,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAE,CAAC;gBAC1B,MAAM,OAAO,GAAG,aAAa,CAAC,EAAE,CAAC,CAAC;gBAClC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;oBAC9B,IAAI,eAAe,EAAE,CAAC;wBACpB,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;wBAC3C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;4BAC1C,uBAAuB,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;4BACvD,YAAY,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;4BAC9D,SAAS;wBACX,CAAC;oBACH,CAAC;oBAED,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;oBAC5E,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;wBACnC,YAAY,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC;oBAClF,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE;YACvC,MAAM,MAAM,GAAa,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YAC3C,MAAM,CAAC,IAAI,CACT,WAAW,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC,EACtD,IAAI,CACL,CAAC;YACF,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1C,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { TokenNormalized, TokenTransformed } from '@terrazzo/parser';
|
|
2
|
+
export declare const FORMAT_ID = "css";
|
|
3
|
+
export declare const FILE_PREFIX = "/* -------------------------------------------\n * Autogenerated by \u26CB Terrazzo. DO NOT EDIT!\n * ------------------------------------------- */";
|
|
4
|
+
export interface CSSPluginOptions {
|
|
5
|
+
/** Where to output CSS */
|
|
6
|
+
filename?: string;
|
|
7
|
+
/** Glob patterns to exclude tokens from output */
|
|
8
|
+
exclude?: string[];
|
|
9
|
+
/** Define mode selectors as media queries or CSS classes */
|
|
10
|
+
modeSelectors?: ModeSelector[];
|
|
11
|
+
/** Control the final CSS variable name */
|
|
12
|
+
variableName?: (name: string) => string;
|
|
13
|
+
/** Override certain token values */
|
|
14
|
+
transform?: (token: TokenNormalized, mode: string) => TokenTransformed['value'];
|
|
15
|
+
}
|
|
16
|
+
export interface ModeSelector {
|
|
17
|
+
/** The name of the mode to match */
|
|
18
|
+
mode: string;
|
|
19
|
+
/** (optional) Provide token IDs to match. Globs are allowed (e.g: `["color.*", "shadow.dark"]`) */
|
|
20
|
+
tokens?: string[];
|
|
21
|
+
/** Provide CSS selectors to generate. (e.g.: `["@media (prefers-color-scheme: dark)", "[data-color-theme='dark']"]` ) */
|
|
22
|
+
selectors: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface CSSRule {
|
|
25
|
+
selectors: string[];
|
|
26
|
+
nestedQuery?: string;
|
|
27
|
+
declarations: Record<string, string>;
|
|
28
|
+
}
|
|
29
|
+
/** Convert CSSRules into a formatted, indented CSS string */
|
|
30
|
+
export declare function printRules(rules: CSSRule[]): string;
|
|
31
|
+
export interface GetRuleOptions {
|
|
32
|
+
/** Combine a selector with parent selectors (e.g. if adding a @media-query within another selector list) */
|
|
33
|
+
parentSelectors?: string[];
|
|
34
|
+
}
|
package/dist/lib.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const FORMAT_ID = 'css';
|
|
2
|
+
export const FILE_PREFIX = `/* -------------------------------------------
|
|
3
|
+
* Autogenerated by ⛋ Terrazzo. DO NOT EDIT!
|
|
4
|
+
* ------------------------------------------- */`;
|
|
5
|
+
/** Convert CSSRules into a formatted, indented CSS string */
|
|
6
|
+
export function printRules(rules) {
|
|
7
|
+
const output = [];
|
|
8
|
+
for (const rule of rules) {
|
|
9
|
+
if (!rule.selectors.length || !Object.keys(rule.declarations).length) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
const mqSelectors = [];
|
|
13
|
+
const joinableSelectors = [];
|
|
14
|
+
for (const s of rule.selectors) {
|
|
15
|
+
(s.startsWith('@') ? mqSelectors : joinableSelectors).push(s);
|
|
16
|
+
}
|
|
17
|
+
// @media-query selectors get pushed individually
|
|
18
|
+
for (const s of mqSelectors) {
|
|
19
|
+
output.push(_printRule({ ...rule, selectors: [s] }));
|
|
20
|
+
}
|
|
21
|
+
// all other selectors get joined as one
|
|
22
|
+
if (joinableSelectors.length) {
|
|
23
|
+
output.push(_printRule({ ...rule, selectors: joinableSelectors }));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return output.join('\n\n');
|
|
27
|
+
}
|
|
28
|
+
function _printRule(rule) {
|
|
29
|
+
const output = [];
|
|
30
|
+
const isMediaQuery = rule.selectors.some((s) => s.startsWith('@'));
|
|
31
|
+
let indent = '';
|
|
32
|
+
// if both levels are media queries, preserve order
|
|
33
|
+
if (rule.nestedQuery && isMediaQuery) {
|
|
34
|
+
output.push(`${indent}${rule.selectors.join(`,\n${indent}`)} {`);
|
|
35
|
+
indent += ' ';
|
|
36
|
+
output.push(`${indent}${rule.nestedQuery} {`);
|
|
37
|
+
}
|
|
38
|
+
// otherwise if nested query exists but parens aren’t media queries, reverse order (media queries on top)
|
|
39
|
+
else if (rule.nestedQuery && !isMediaQuery) {
|
|
40
|
+
output.push(`${indent}${rule.nestedQuery} {`);
|
|
41
|
+
indent += ' ';
|
|
42
|
+
output.push(`${indent}${rule.selectors.join(`,\n${indent}`)} {`);
|
|
43
|
+
}
|
|
44
|
+
// if no media queries, just print selectors
|
|
45
|
+
else {
|
|
46
|
+
output.push(`${indent}${rule.selectors.join(`,\n${indent}`)} {`);
|
|
47
|
+
}
|
|
48
|
+
indent += ' ';
|
|
49
|
+
// note: this is ONLY dependent on whether the top level is a media query (ignores nestedQuery)
|
|
50
|
+
if (isMediaQuery) {
|
|
51
|
+
output.push(`${indent}:root {`);
|
|
52
|
+
indent += ' ';
|
|
53
|
+
}
|
|
54
|
+
for (const [k, v] of Object.entries(rule.declarations)) {
|
|
55
|
+
output.push(`${indent}${k}: ${v};`);
|
|
56
|
+
}
|
|
57
|
+
// base closing brackets on indent level
|
|
58
|
+
while (indent !== '') {
|
|
59
|
+
indent = indent.substring(0, indent.length - 2);
|
|
60
|
+
output.push(`${indent}}`);
|
|
61
|
+
}
|
|
62
|
+
return output.join('\n');
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=lib.js.map
|
package/dist/lib.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terrazzo/plugin-css",
|
|
3
3
|
"description": "Generate CSS from your design tokens schema (requires @terrazzo/cli)",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.5",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Drew Powers",
|
|
7
7
|
"email": "drew@pow.rs"
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
"@types/mime": "^3.0.4",
|
|
27
27
|
"culori": "^3.3.0",
|
|
28
28
|
"mime": "^3.0.0",
|
|
29
|
-
"svgo": "^3.2
|
|
30
|
-
"@terrazzo/parser": "^0.0.
|
|
31
|
-
"@terrazzo/token-tools": "^0.0.
|
|
29
|
+
"svgo": "^3.3.2",
|
|
30
|
+
"@terrazzo/parser": "^0.0.7",
|
|
31
|
+
"@terrazzo/token-tools": "^0.0.3"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"yaml": "^2.4.
|
|
35
|
-
"@terrazzo/cli": "^0.0.
|
|
34
|
+
"yaml": "^2.4.3",
|
|
35
|
+
"@terrazzo/cli": "^0.0.7"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "pnpm run build:clean && pnpm run build:ts && pnpm run build:license",
|
package/src/build.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { BuildHookOptions } from '@terrazzo/parser';
|
|
2
|
+
import { isTokenMatch } from '@terrazzo/token-tools';
|
|
3
|
+
import { generateShorthand } from '@terrazzo/token-tools/css';
|
|
4
|
+
import { type CSSPluginOptions, type CSSRule, printRules, FORMAT_ID } from './lib.js';
|
|
5
|
+
|
|
6
|
+
const P3_MQ = '@media (color-gamut: p3)';
|
|
7
|
+
const REC2020_MQ = '@media (color-gamut: rec2020)';
|
|
8
|
+
|
|
9
|
+
export interface BuildFormatOptions {
|
|
10
|
+
exclude: CSSPluginOptions['exclude'];
|
|
11
|
+
getTransforms: BuildHookOptions['getTransforms'];
|
|
12
|
+
modeSelectors: CSSPluginOptions['modeSelectors'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default function buildFormat({ getTransforms, exclude, modeSelectors }: BuildFormatOptions): string {
|
|
16
|
+
const rules: CSSRule[] = [];
|
|
17
|
+
|
|
18
|
+
// :root
|
|
19
|
+
const rootTokens = getTransforms({ format: FORMAT_ID, mode: '.' });
|
|
20
|
+
if (rootTokens.length) {
|
|
21
|
+
const rootRule: CSSRule = { selectors: [':root'], declarations: {} };
|
|
22
|
+
// note: "nestedQuery" was designed specifically for higher-gamut colors to
|
|
23
|
+
// apply color-gamut media queries to existing selectors (i.e. keep the same
|
|
24
|
+
// targets, and apply another nested layer of media query filtering based on
|
|
25
|
+
// hardware). Because of how CSS works they need to get built out into their
|
|
26
|
+
// own selectors that have different structures depending on whether
|
|
27
|
+
// `selectors` has a media query or not.
|
|
28
|
+
const p3Rule: CSSRule = { selectors: [':root'], nestedQuery: P3_MQ, declarations: {} };
|
|
29
|
+
const rec2020Rule: CSSRule = { selectors: [':root'], nestedQuery: REC2020_MQ, declarations: {} };
|
|
30
|
+
rules.push(rootRule, p3Rule, rec2020Rule);
|
|
31
|
+
|
|
32
|
+
for (const token of rootTokens) {
|
|
33
|
+
if (isTokenMatch(token.token.id, exclude ?? [])) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const localID = token.localID ?? token.token.id;
|
|
37
|
+
// single-value token
|
|
38
|
+
if (token.type === 'SINGLE_VALUE') {
|
|
39
|
+
rootRule.declarations[localID] = token.value;
|
|
40
|
+
}
|
|
41
|
+
// multi-value token (wide gamut color)
|
|
42
|
+
else if (token.value.srgb && token.value.p3 && token.value.rec2020) {
|
|
43
|
+
rootRule.declarations[localID] = token.value.srgb!;
|
|
44
|
+
if (token.value.p3 !== token.value.srgb) {
|
|
45
|
+
p3Rule.declarations[localID] = token.value.p3!;
|
|
46
|
+
rec2020Rule.declarations[localID] = token.value.rec2020!;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// multi-value token
|
|
50
|
+
else if (token.type === 'MULTI_VALUE') {
|
|
51
|
+
const shorthand = generateShorthand({ $type: token.token.$type, localID });
|
|
52
|
+
if (shorthand) {
|
|
53
|
+
rootRule.declarations[token.localID ?? token.token.id] = shorthand;
|
|
54
|
+
}
|
|
55
|
+
for (const [name, value] of Object.entries(token.value)) {
|
|
56
|
+
rootRule.declarations[name === '.' ? localID : [localID, name].join('-')] = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// modeSelectors (note: without these, modes won’t get written to CSS)
|
|
63
|
+
for (const { selectors, tokens, mode } of modeSelectors ?? []) {
|
|
64
|
+
if (!selectors.length) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const selectorTokens = getTransforms({ format: 'css', id: tokens, mode });
|
|
68
|
+
if (!selectorTokens.length) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const selectorRule: CSSRule = { selectors, declarations: {} };
|
|
73
|
+
const selectorP3Rule: CSSRule = { selectors, nestedQuery: P3_MQ, declarations: {} };
|
|
74
|
+
const selectorRec2020Rule: CSSRule = { selectors, nestedQuery: REC2020_MQ, declarations: {} };
|
|
75
|
+
rules.push(selectorRule, selectorP3Rule, selectorRec2020Rule);
|
|
76
|
+
|
|
77
|
+
for (const token of selectorTokens) {
|
|
78
|
+
if (token.token.aliasOf) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const localID = token.localID ?? token.token.id;
|
|
82
|
+
// single-value token
|
|
83
|
+
if (token.type === 'SINGLE_VALUE') {
|
|
84
|
+
selectorRule.declarations[localID] = token.value;
|
|
85
|
+
}
|
|
86
|
+
// multi-value token (wide gamut color)
|
|
87
|
+
else if (token.value.srgb && token.value.p3 && token.value.rec2020) {
|
|
88
|
+
selectorRule.declarations[localID] = token.value.srgb!;
|
|
89
|
+
if (token.value.p3 !== token.value.srgb) {
|
|
90
|
+
selectorP3Rule.declarations[localID] = token.value.p3!;
|
|
91
|
+
selectorRec2020Rule.declarations[localID] = token.value.rec2020!;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// multi-value token
|
|
95
|
+
else {
|
|
96
|
+
const shorthand = generateShorthand({ $type: token.token.$type, localID });
|
|
97
|
+
if (shorthand) {
|
|
98
|
+
selectorRule.declarations[localID] = shorthand;
|
|
99
|
+
}
|
|
100
|
+
for (const [name, subvalue] of Object.entries(token.value)) {
|
|
101
|
+
selectorRule.declarations[`${localID}-${name}`] = subvalue;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return printRules(rules);
|
|
108
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,328 +1,57 @@
|
|
|
1
1
|
import type { Plugin } from '@terrazzo/parser';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
transformBooleanValue,
|
|
7
|
-
transformBorderValue,
|
|
8
|
-
transformColorValue,
|
|
9
|
-
transformCubicBezierValue,
|
|
10
|
-
transformDimensionValue,
|
|
11
|
-
transformDurationValue,
|
|
12
|
-
transformFontFamilyValue,
|
|
13
|
-
transformFontWeightValue,
|
|
14
|
-
transformGradientValue,
|
|
15
|
-
transformLinkValue,
|
|
16
|
-
transformNumberValue,
|
|
17
|
-
transformShadowValue,
|
|
18
|
-
transformStringValue,
|
|
19
|
-
transformStrokeStyleValue,
|
|
20
|
-
transformTransitionValue,
|
|
21
|
-
transformTypographyValue,
|
|
22
|
-
} from '@terrazzo/token-tools/css';
|
|
2
|
+
import { makeCSSVar, transformCSSValue } from '@terrazzo/token-tools/css';
|
|
3
|
+
import { FILE_PREFIX, FORMAT_ID, type CSSPluginOptions } from './lib.js';
|
|
4
|
+
import buildFormat from './build.js';
|
|
5
|
+
import { validateCustomTransform } from '@terrazzo/token-tools';
|
|
23
6
|
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
mode: string;
|
|
27
|
-
/** (optional) Provide token IDs to match. Globs are allowed (e.g: `["color.*", "shadow.dark"]`) */
|
|
28
|
-
tokens?: string[];
|
|
29
|
-
/** Provide CSS selectors to generate. (e.g.: `["@media (prefers-color-scheme: dark)", "[data-color-theme='dark']"]` ) */
|
|
30
|
-
selectors: string[];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface CSSPluginOptions {
|
|
34
|
-
/** Where to output CSS */
|
|
35
|
-
filename?: string;
|
|
36
|
-
/** Glob patterns to exclude tokens from output */
|
|
37
|
-
exclude?: string[];
|
|
38
|
-
/** Define mode selectors as media queries or CSS classes */
|
|
39
|
-
modeSelectors?: ModeSelector[];
|
|
40
|
-
/** Control the final CSS variable name */
|
|
41
|
-
variableName?: (name: string) => string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export const FORMAT_ID = 'css';
|
|
45
|
-
|
|
46
|
-
export const FILE_PREFIX = `/* -------------------------------------------
|
|
47
|
-
* Autogenerated by 💠 Terrazzo. DO NOT EDIT!
|
|
48
|
-
* ------------------------------------------- */`;
|
|
7
|
+
export * from './build.js';
|
|
8
|
+
export * from './lib.js';
|
|
49
9
|
|
|
50
10
|
export default function cssPlugin({
|
|
51
11
|
filename = './index.css',
|
|
52
12
|
exclude,
|
|
53
13
|
variableName,
|
|
54
14
|
modeSelectors,
|
|
15
|
+
transform: customTransform,
|
|
55
16
|
}: CSSPluginOptions = {}): Plugin {
|
|
56
17
|
const transformName = (id: string) => variableName?.(id) || makeCSSVar(id);
|
|
57
18
|
const transformAlias = (id: string) => `var(${transformName(id)})`;
|
|
58
19
|
|
|
59
20
|
return {
|
|
60
21
|
name: '@terrazzo/plugin-css',
|
|
61
|
-
async transform({ tokens, setTransform }) {
|
|
22
|
+
async transform({ tokens, getTransforms, setTransform }) {
|
|
23
|
+
// skip work if another .css plugin has already run
|
|
24
|
+
const cssTokens = getTransforms({ format: FORMAT_ID, id: '*', mode: '*' });
|
|
25
|
+
if (cssTokens.length) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
62
29
|
for (const id in tokens) {
|
|
63
|
-
if (!Object.hasOwn(tokens, id)) {
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
30
|
const token = tokens[id]!;
|
|
67
31
|
const localID = transformName(id);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
setTransform(id, {
|
|
74
|
-
|
|
75
|
-
localID,
|
|
76
|
-
value: transformBooleanValue($value, { aliasOf, transformAlias }),
|
|
77
|
-
mode,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
case 'border': {
|
|
83
|
-
for (const mode in token.mode) {
|
|
84
|
-
const { $value, aliasOf, partialAliasOf } = token.mode[mode]!;
|
|
85
|
-
setTransform(id, {
|
|
86
|
-
format: FORMAT_ID,
|
|
87
|
-
localID,
|
|
88
|
-
value: transformBorderValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
89
|
-
mode,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
case 'color': {
|
|
95
|
-
for (const mode in token.mode) {
|
|
96
|
-
const { $value, aliasOf } = token.mode[mode]!;
|
|
97
|
-
setTransform(id, {
|
|
98
|
-
format: FORMAT_ID,
|
|
99
|
-
localID,
|
|
100
|
-
value: transformColorValue($value, { aliasOf, transformAlias }),
|
|
101
|
-
mode,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
case 'cubicBezier': {
|
|
107
|
-
for (const mode in token.mode) {
|
|
108
|
-
const { $value, aliasOf, partialAliasOf } = token.mode[mode]!;
|
|
109
|
-
setTransform(id, {
|
|
110
|
-
format: FORMAT_ID,
|
|
111
|
-
localID,
|
|
112
|
-
value: transformCubicBezierValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
113
|
-
mode,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
case 'dimension': {
|
|
119
|
-
for (const mode in token.mode) {
|
|
120
|
-
const { $value, aliasOf } = token.mode[mode]!;
|
|
121
|
-
setTransform(id, {
|
|
122
|
-
format: FORMAT_ID,
|
|
123
|
-
localID,
|
|
124
|
-
value: transformDimensionValue($value, { aliasOf, transformAlias }),
|
|
125
|
-
mode,
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
case 'duration': {
|
|
131
|
-
for (const mode in token.mode) {
|
|
132
|
-
const { $value, aliasOf } = token.mode[mode]!;
|
|
133
|
-
setTransform(id, {
|
|
134
|
-
format: FORMAT_ID,
|
|
135
|
-
localID,
|
|
136
|
-
value: transformDurationValue($value, { aliasOf, transformAlias }),
|
|
137
|
-
mode,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
case 'fontFamily': {
|
|
143
|
-
for (const mode in token.mode) {
|
|
144
|
-
const { $value, aliasOf, partialAliasOf } = token.mode[mode]!;
|
|
145
|
-
setTransform(id, {
|
|
146
|
-
format: FORMAT_ID,
|
|
147
|
-
localID,
|
|
148
|
-
value: transformFontFamilyValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
149
|
-
mode,
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
case 'fontWeight': {
|
|
155
|
-
for (const mode in token.mode) {
|
|
156
|
-
const { $value, aliasOf } = token.mode[mode]!;
|
|
157
|
-
setTransform(id, {
|
|
158
|
-
format: FORMAT_ID,
|
|
159
|
-
localID,
|
|
160
|
-
value: transformFontWeightValue($value, { aliasOf, transformAlias }),
|
|
161
|
-
mode,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
case 'gradient': {
|
|
167
|
-
for (const mode in token.mode) {
|
|
168
|
-
const { $value, aliasOf, partialAliasOf } = token.mode[mode]!;
|
|
169
|
-
setTransform(id, {
|
|
170
|
-
format: FORMAT_ID,
|
|
171
|
-
localID,
|
|
172
|
-
value: transformGradientValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
177
|
-
case 'link': {
|
|
178
|
-
for (const mode in token.mode) {
|
|
179
|
-
const { $value, aliasOf } = token.mode[mode]!;
|
|
180
|
-
setTransform(id, {
|
|
181
|
-
format: FORMAT_ID,
|
|
182
|
-
localID,
|
|
183
|
-
value: transformLinkValue($value, { aliasOf, transformAlias }),
|
|
184
|
-
mode,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
case 'number': {
|
|
190
|
-
for (const mode in token.mode) {
|
|
191
|
-
const { $value, aliasOf } = token.mode[mode]!;
|
|
192
|
-
setTransform(id, {
|
|
193
|
-
format: FORMAT_ID,
|
|
194
|
-
localID,
|
|
195
|
-
value: transformNumberValue($value, { aliasOf, transformAlias }),
|
|
196
|
-
mode,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
case 'shadow': {
|
|
202
|
-
for (const mode in token.mode) {
|
|
203
|
-
const { $value, aliasOf, partialAliasOf } = token.mode[mode]!;
|
|
204
|
-
setTransform(id, {
|
|
205
|
-
format: FORMAT_ID,
|
|
206
|
-
localID,
|
|
207
|
-
value: transformShadowValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
208
|
-
mode,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
case 'string': {
|
|
214
|
-
for (const mode in token.mode) {
|
|
215
|
-
const { $value, aliasOf } = token.mode[mode]!;
|
|
216
|
-
setTransform(id, {
|
|
217
|
-
format: FORMAT_ID,
|
|
218
|
-
localID,
|
|
219
|
-
value: transformStringValue($value, { aliasOf, transformAlias }),
|
|
220
|
-
mode,
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
break;
|
|
224
|
-
}
|
|
225
|
-
case 'strokeStyle': {
|
|
226
|
-
for (const mode in token.mode) {
|
|
227
|
-
const { $value, aliasOf } = token.mode[mode]!;
|
|
228
|
-
setTransform(id, {
|
|
229
|
-
format: FORMAT_ID,
|
|
230
|
-
localID,
|
|
231
|
-
value: transformStrokeStyleValue($value, { aliasOf, transformAlias }),
|
|
232
|
-
mode,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
case 'transition': {
|
|
238
|
-
for (const mode in token.mode) {
|
|
239
|
-
const { $value, aliasOf, partialAliasOf } = token.mode[mode]!;
|
|
240
|
-
setTransform(id, {
|
|
241
|
-
format: FORMAT_ID,
|
|
242
|
-
localID,
|
|
243
|
-
value: transformTransitionValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
244
|
-
mode,
|
|
245
|
-
});
|
|
32
|
+
for (const mode in token.mode) {
|
|
33
|
+
if (customTransform) {
|
|
34
|
+
const value = customTransform(token, mode);
|
|
35
|
+
if (value !== undefined && value !== null) {
|
|
36
|
+
validateCustomTransform(value, { $type: token.$type });
|
|
37
|
+
setTransform(id, { format: FORMAT_ID, localID, value, mode });
|
|
38
|
+
continue;
|
|
246
39
|
}
|
|
247
|
-
break;
|
|
248
40
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
format: FORMAT_ID,
|
|
254
|
-
localID,
|
|
255
|
-
value: transformTypographyValue($value, { aliasOf, partialAliasOf, transformAlias }),
|
|
256
|
-
mode,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
break;
|
|
41
|
+
|
|
42
|
+
const transformedValue = transformCSSValue(token, { mode, transformAlias });
|
|
43
|
+
if (transformedValue !== undefined) {
|
|
44
|
+
setTransform(id, { format: FORMAT_ID, localID, value: transformedValue, mode });
|
|
260
45
|
}
|
|
261
46
|
}
|
|
262
47
|
}
|
|
263
48
|
},
|
|
264
49
|
async build({ getTransforms, outputFile }) {
|
|
265
50
|
const output: string[] = [FILE_PREFIX, ''];
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
for (const token of rootTokens) {
|
|
271
|
-
if (isTokenMatch(token.token.id, exclude ?? [])) {
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
const localID = token.localID ?? token.token.id;
|
|
275
|
-
if (token.type === 'SINGLE_VALUE') {
|
|
276
|
-
output.push(` ${localID}: ${token.value};`);
|
|
277
|
-
} else if (token.type === 'MULTI_VALUE') {
|
|
278
|
-
const shorthand = generateShorthand({ $type: token.token.$type, localID });
|
|
279
|
-
if (shorthand) {
|
|
280
|
-
output.push(` ${token.localID ?? token.token.id}: ${shorthand};`);
|
|
281
|
-
}
|
|
282
|
-
for (const [name, value] of Object.entries(token.value)) {
|
|
283
|
-
output.push(` ${name === '.' ? localID : [localID, name].join('-')}: ${value};`);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
output.push('}');
|
|
288
|
-
|
|
289
|
-
for (const { selectors, tokens, mode } of modeSelectors ?? []) {
|
|
290
|
-
const selectorTokens = getTransforms({ format: 'css', id: tokens, mode });
|
|
291
|
-
if (!selectorTokens.length) {
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
let indent = ' ';
|
|
296
|
-
for (const selector of selectors) {
|
|
297
|
-
output.push('', `${selector} {`);
|
|
298
|
-
if (selector.startsWith('@')) {
|
|
299
|
-
output.push(' :root {');
|
|
300
|
-
indent = ' ';
|
|
301
|
-
}
|
|
302
|
-
for (const token of selectorTokens) {
|
|
303
|
-
if (token.token.aliasOf) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
const localID = token.localID ?? token.token.id;
|
|
307
|
-
if (token.type === 'SINGLE_VALUE') {
|
|
308
|
-
output.push(`${indent}${localID}: ${token.value};`);
|
|
309
|
-
} else {
|
|
310
|
-
const shorthand = generateShorthand({ $type: token.token.$type, localID });
|
|
311
|
-
if (shorthand) {
|
|
312
|
-
output.push(`${indent}${localID}: ${shorthand};`);
|
|
313
|
-
}
|
|
314
|
-
for (const [name, subvalue] of Object.entries(token.value)) {
|
|
315
|
-
output.push(`${indent}${localID}-${name}: ${subvalue};`);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
if (selector.startsWith('@')) {
|
|
320
|
-
output.push(' }');
|
|
321
|
-
}
|
|
322
|
-
output.push('}');
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
51
|
+
output.push(
|
|
52
|
+
buildFormat({ exclude, getTransforms, modeSelectors }),
|
|
53
|
+
'\n', // EOF newline
|
|
54
|
+
);
|
|
326
55
|
outputFile(filename, output.join('\n'));
|
|
327
56
|
},
|
|
328
57
|
};
|
package/src/lib.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { TokenNormalized, TokenTransformed } from '@terrazzo/parser';
|
|
2
|
+
|
|
3
|
+
export const FORMAT_ID = 'css';
|
|
4
|
+
|
|
5
|
+
export const FILE_PREFIX = `/* -------------------------------------------
|
|
6
|
+
* Autogenerated by ⛋ Terrazzo. DO NOT EDIT!
|
|
7
|
+
* ------------------------------------------- */`;
|
|
8
|
+
|
|
9
|
+
export interface CSSPluginOptions {
|
|
10
|
+
/** Where to output CSS */
|
|
11
|
+
filename?: string;
|
|
12
|
+
/** Glob patterns to exclude tokens from output */
|
|
13
|
+
exclude?: string[];
|
|
14
|
+
/** Define mode selectors as media queries or CSS classes */
|
|
15
|
+
modeSelectors?: ModeSelector[];
|
|
16
|
+
/** Control the final CSS variable name */
|
|
17
|
+
variableName?: (name: string) => string;
|
|
18
|
+
/** Override certain token values */
|
|
19
|
+
transform?: (token: TokenNormalized, mode: string) => TokenTransformed['value'];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ModeSelector {
|
|
23
|
+
/** The name of the mode to match */
|
|
24
|
+
mode: string;
|
|
25
|
+
/** (optional) Provide token IDs to match. Globs are allowed (e.g: `["color.*", "shadow.dark"]`) */
|
|
26
|
+
tokens?: string[];
|
|
27
|
+
/** Provide CSS selectors to generate. (e.g.: `["@media (prefers-color-scheme: dark)", "[data-color-theme='dark']"]` ) */
|
|
28
|
+
selectors: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// note: this is NOT an adequate replacement for a CSS AST; this just performs
|
|
32
|
+
// basic deduplication and allows some limited parsing/reformatting before
|
|
33
|
+
// flattening to a CSS string.
|
|
34
|
+
export interface CSSRule {
|
|
35
|
+
selectors: string[];
|
|
36
|
+
nestedQuery?: string;
|
|
37
|
+
declarations: Record<string, string>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Convert CSSRules into a formatted, indented CSS string */
|
|
41
|
+
export function printRules(rules: CSSRule[]): string {
|
|
42
|
+
const output: string[] = [];
|
|
43
|
+
for (const rule of rules) {
|
|
44
|
+
if (!rule.selectors.length || !Object.keys(rule.declarations).length) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const mqSelectors: string[] = [];
|
|
49
|
+
const joinableSelectors: string[] = [];
|
|
50
|
+
for (const s of rule.selectors) {
|
|
51
|
+
(s.startsWith('@') ? mqSelectors : joinableSelectors).push(s);
|
|
52
|
+
}
|
|
53
|
+
// @media-query selectors get pushed individually
|
|
54
|
+
for (const s of mqSelectors) {
|
|
55
|
+
output.push(_printRule({ ...rule, selectors: [s] }));
|
|
56
|
+
}
|
|
57
|
+
// all other selectors get joined as one
|
|
58
|
+
if (joinableSelectors.length) {
|
|
59
|
+
output.push(_printRule({ ...rule, selectors: joinableSelectors }));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return output.join('\n\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function _printRule(rule: CSSRule): string {
|
|
66
|
+
const output: string[] = [];
|
|
67
|
+
const isMediaQuery = rule.selectors.some((s) => s.startsWith('@'));
|
|
68
|
+
let indent = '';
|
|
69
|
+
|
|
70
|
+
// if both levels are media queries, preserve order
|
|
71
|
+
if (rule.nestedQuery && isMediaQuery) {
|
|
72
|
+
output.push(`${indent}${rule.selectors.join(`,\n${indent}`)} {`);
|
|
73
|
+
indent += ' ';
|
|
74
|
+
output.push(`${indent}${rule.nestedQuery} {`);
|
|
75
|
+
}
|
|
76
|
+
// otherwise if nested query exists but parens aren’t media queries, reverse order (media queries on top)
|
|
77
|
+
else if (rule.nestedQuery && !isMediaQuery) {
|
|
78
|
+
output.push(`${indent}${rule.nestedQuery} {`);
|
|
79
|
+
indent += ' ';
|
|
80
|
+
output.push(`${indent}${rule.selectors.join(`,\n${indent}`)} {`);
|
|
81
|
+
}
|
|
82
|
+
// if no media queries, just print selectors
|
|
83
|
+
else {
|
|
84
|
+
output.push(`${indent}${rule.selectors.join(`,\n${indent}`)} {`);
|
|
85
|
+
}
|
|
86
|
+
indent += ' ';
|
|
87
|
+
|
|
88
|
+
// note: this is ONLY dependent on whether the top level is a media query (ignores nestedQuery)
|
|
89
|
+
if (isMediaQuery) {
|
|
90
|
+
output.push(`${indent}:root {`);
|
|
91
|
+
indent += ' ';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const [k, v] of Object.entries(rule.declarations)) {
|
|
95
|
+
output.push(`${indent}${k}: ${v};`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// base closing brackets on indent level
|
|
99
|
+
while (indent !== '') {
|
|
100
|
+
indent = indent.substring(0, indent.length - 2);
|
|
101
|
+
output.push(`${indent}}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return output.join('\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface GetRuleOptions {
|
|
108
|
+
/** Combine a selector with parent selectors (e.g. if adding a @media-query within another selector list) */
|
|
109
|
+
parentSelectors?: string[];
|
|
110
|
+
}
|