@carbon/motion 11.51.0-rc.0 → 11.52.0-rc.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.
- package/js/generated/tokens.d.ts +1 -1
- package/package.json +4 -3
- package/style-dictionary/formats/js-surfaces.js +154 -0
- package/style-dictionary/formats/js-tokens.js +148 -0
- package/style-dictionary/formats/scss-surfaces.js +152 -0
- package/style-dictionary/formats/scss-tokens.js +101 -0
- package/style-dictionary/sd.config.js +220 -0
- package/style-dictionary/transforms/cubic-bezier.js +38 -0
- package/style-dictionary/transforms/duration.js +39 -0
- package/style-dictionary/utils/dts-type-literal.js +60 -0
package/js/generated/tokens.d.ts
CHANGED
|
@@ -36,4 +36,4 @@ export declare const slow02: string;
|
|
|
36
36
|
// Easing tokens
|
|
37
37
|
export declare const easings: Record<'standard' | 'entrance' | 'exit', Record<'productive' | 'expressive', string>>;
|
|
38
38
|
|
|
39
|
-
export declare const unstable_tokens: readonly string[];
|
|
39
|
+
export declare const unstable_tokens: readonly string[];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carbon/motion",
|
|
3
3
|
"description": "Motion helpers for digital and software products using the Carbon Design System",
|
|
4
|
-
"version": "11.
|
|
4
|
+
"version": "11.52.0-rc.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": [
|
|
7
7
|
"index.scss",
|
|
@@ -38,13 +38,14 @@
|
|
|
38
38
|
"postinstall": "ibmtelemetry --config=telemetry.yml"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@carbon/cli": "^11.
|
|
41
|
+
"@carbon/cli": "^11.50.0-rc.0",
|
|
42
42
|
"rimraf": "^6.0.1",
|
|
43
|
+
"style-dictionary": "^5.5.0",
|
|
43
44
|
"typescript": "^6.0.3",
|
|
44
45
|
"typescript-config-carbon": "^0.11.0"
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
47
48
|
"@ibm/telemetry-js": "^1.5.0"
|
|
48
49
|
},
|
|
49
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "474482a993dff6b9085309aa4bc8484cf71b1612"
|
|
50
51
|
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2018, 2026
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Emits `js/generated/surfaces.{js,d.ts}` from surfaces.json.
|
|
12
|
+
*
|
|
13
|
+
* Surface data lives in `$extensions["carbon.motion"]` on each leaf token —
|
|
14
|
+
* not in `$value` — so Style Dictionary cannot derive the output from the
|
|
15
|
+
* token value alone. This format reads surfaces.json directly (the same
|
|
16
|
+
* pattern used by `carbon/scss-tokens` in @carbon/themes, which reads Carbon
|
|
17
|
+
* JS metadata directly instead of using SD token values).
|
|
18
|
+
*
|
|
19
|
+
* Two files are emitted by wiring this format into two SD `files` entries
|
|
20
|
+
* that differ only in `options.output` ('js' | 'dts').
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const { toDtsTypeLiteral } = require('../utils/dts-type-literal');
|
|
26
|
+
|
|
27
|
+
const FILE_BANNER = `// Code generated by @carbon/motion. DO NOT EDIT.
|
|
28
|
+
//
|
|
29
|
+
// Copyright IBM Corp. 2018, 2026
|
|
30
|
+
//
|
|
31
|
+
// This source code is licensed under the Apache-2.0 license found in the
|
|
32
|
+
// LICENSE file in the root directory of this source tree.
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
// Path is resolved relative to this file (style-dictionary/formats/) → src/dtcg/
|
|
36
|
+
const SURFACES_PATH = path.resolve(__dirname, '../../src/dtcg/surfaces.json');
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Collect surface entries from surfaces.json.
|
|
40
|
+
* Each surface node carries `$extensions["carbon.motion"]` with the recipe.
|
|
41
|
+
*
|
|
42
|
+
* @returns {Array<{ name: string, description: string, recipe: object }>}
|
|
43
|
+
*/
|
|
44
|
+
function collectSurfaces() {
|
|
45
|
+
const dtcg = JSON.parse(fs.readFileSync(SURFACES_PATH, 'utf8'));
|
|
46
|
+
const results = [];
|
|
47
|
+
|
|
48
|
+
function traverse(obj, pathSegments) {
|
|
49
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
50
|
+
if (key.startsWith('$')) continue;
|
|
51
|
+
const segments = [...pathSegments, key];
|
|
52
|
+
if (
|
|
53
|
+
value &&
|
|
54
|
+
typeof value === 'object' &&
|
|
55
|
+
value.$extensions &&
|
|
56
|
+
value.$extensions['carbon.motion']
|
|
57
|
+
) {
|
|
58
|
+
results.push({
|
|
59
|
+
name: segments.join('-'),
|
|
60
|
+
description: value.$description || '',
|
|
61
|
+
recipe: value.$extensions['carbon.motion'],
|
|
62
|
+
});
|
|
63
|
+
} else if (value && typeof value === 'object') {
|
|
64
|
+
traverse(value, segments);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
traverse(dtcg, []);
|
|
70
|
+
return results;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {{ options: object }} args
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
function carbonJsMotionSurfacesFormat({ options }) {
|
|
78
|
+
const outputMode = options?.output ?? 'js'; // 'js' | 'dts'
|
|
79
|
+
|
|
80
|
+
const surfaceList = collectSurfaces();
|
|
81
|
+
// Drop the leading "surface-" prefix from the export name.
|
|
82
|
+
const surfaceNames = surfaceList.map(({ name }) =>
|
|
83
|
+
name.startsWith('surface-') ? name.slice('surface-'.length) : name
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const lines = [FILE_BANNER];
|
|
87
|
+
|
|
88
|
+
// ── Individual named surface exports ─────────────────────────────────────
|
|
89
|
+
for (let i = 0; i < surfaceList.length; i++) {
|
|
90
|
+
const { description, recipe } = surfaceList[i];
|
|
91
|
+
const exportName = surfaceNames[i];
|
|
92
|
+
|
|
93
|
+
if (description) {
|
|
94
|
+
lines.push(`/** ${description} */`);
|
|
95
|
+
}
|
|
96
|
+
if (outputMode === 'js') {
|
|
97
|
+
lines.push(
|
|
98
|
+
`export const ${exportName} = ${JSON.stringify(recipe, null, 2)};`
|
|
99
|
+
);
|
|
100
|
+
} else {
|
|
101
|
+
lines.push(
|
|
102
|
+
`export declare const ${exportName}: ${toDtsTypeLiteral(recipe)};`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
lines.push('');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── surfaces map ─────────────────────────────────────────────────────────
|
|
109
|
+
if (outputMode === 'js') {
|
|
110
|
+
lines.push('export const surfaces = {');
|
|
111
|
+
for (const name of surfaceNames) {
|
|
112
|
+
lines.push(` ${name},`);
|
|
113
|
+
}
|
|
114
|
+
lines.push('};');
|
|
115
|
+
lines.push('');
|
|
116
|
+
} else {
|
|
117
|
+
lines.push('export declare const surfaces: {');
|
|
118
|
+
for (const name of surfaceNames) {
|
|
119
|
+
lines.push(` ${name}: typeof ${name};`);
|
|
120
|
+
}
|
|
121
|
+
lines.push('};');
|
|
122
|
+
lines.push('');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── getMotionSurface ──────────────────────────────────────────────────────
|
|
126
|
+
if (outputMode === 'js') {
|
|
127
|
+
lines.push(
|
|
128
|
+
`export function getMotionSurface(name) {`,
|
|
129
|
+
` const surface = surfaces[name];`,
|
|
130
|
+
` if (!surface) {`,
|
|
131
|
+
` throw new Error(`,
|
|
132
|
+
` \`Unable to find motion surface \\\`\${name}\\\`. Expected one of: \${Object.keys(surfaces).join(', ')}\``,
|
|
133
|
+
` );`,
|
|
134
|
+
` }`,
|
|
135
|
+
` return surface;`,
|
|
136
|
+
`}`,
|
|
137
|
+
``
|
|
138
|
+
);
|
|
139
|
+
} else {
|
|
140
|
+
lines.push(
|
|
141
|
+
`export declare function getMotionSurface(name: ${surfaceNames
|
|
142
|
+
.map((n) => `'${n}'`)
|
|
143
|
+
.join(' | ')}): (typeof surfaces)[typeof name];`,
|
|
144
|
+
``
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return lines.join('\n');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = {
|
|
152
|
+
name: 'carbon/motion-js-surfaces',
|
|
153
|
+
format: carbonJsMotionSurfacesFormat,
|
|
154
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2018, 2026
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Emits `js/generated/tokens.{js,d.ts}` from the motion DTCG tokens.
|
|
12
|
+
*
|
|
13
|
+
* The format receives all resolved tokens (duration + easing) and produces:
|
|
14
|
+
* - V11 duration exports (durationFast01, durationFast02, …)
|
|
15
|
+
* - V10 deprecated aliases (fast01, fast02, …)
|
|
16
|
+
* - `easings` nested object ({ standard: { productive, expressive }, … })
|
|
17
|
+
* - `unstable_tokens` array
|
|
18
|
+
*
|
|
19
|
+
* Two files are emitted by wiring this format into two SD `files` entries
|
|
20
|
+
* that differ only in `options.output` ('js' | 'dts').
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const FILE_BANNER = `// Code generated by @carbon/motion. DO NOT EDIT.
|
|
24
|
+
//
|
|
25
|
+
// Copyright IBM Corp. 2018, 2026
|
|
26
|
+
//
|
|
27
|
+
// This source code is licensed under the Apache-2.0 license found in the
|
|
28
|
+
// LICENSE file in the root directory of this source tree.
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convert kebab-case to camelCase.
|
|
33
|
+
* @param {string} str
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
function kebabToCamel(str) {
|
|
37
|
+
return str.replace(/-([a-z0-9])/gi, (_, ch) => ch.toUpperCase());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Derive the V10 camelCase alias name from a V11 camelCase name.
|
|
42
|
+
* e.g. 'durationFast01' → 'fast01'
|
|
43
|
+
*/
|
|
44
|
+
function toV10CamelName(v11Camel) {
|
|
45
|
+
// Strip the leading 'duration' prefix and lowercase the first char.
|
|
46
|
+
const withoutPrefix = v11Camel.slice('duration'.length);
|
|
47
|
+
return withoutPrefix.charAt(0).toLowerCase() + withoutPrefix.slice(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {{ dictionary: import('style-dictionary').Dictionary, options: object }} args
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
function carbonJsMotionTokensFormat({ dictionary, options }) {
|
|
55
|
+
const outputMode = options?.output ?? 'js'; // 'js' | 'dts'
|
|
56
|
+
|
|
57
|
+
// Build a lookup from kebab token name → resolved value
|
|
58
|
+
const tokenMap = new Map(
|
|
59
|
+
dictionary.allTokens.map((t) => [t.name, t.value ?? t.$value])
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const lines = [FILE_BANNER];
|
|
63
|
+
|
|
64
|
+
// ── Duration tokens (V11 canonical) ──────────────────────────────────────
|
|
65
|
+
lines.push('// Duration tokens (V11)');
|
|
66
|
+
const durationTokens = dictionary.allTokens.filter((t) =>
|
|
67
|
+
t.name.startsWith('duration-')
|
|
68
|
+
);
|
|
69
|
+
const durationCamels = [];
|
|
70
|
+
|
|
71
|
+
for (const token of durationTokens) {
|
|
72
|
+
const camel = kebabToCamel(token.name);
|
|
73
|
+
durationCamels.push(camel);
|
|
74
|
+
const value = token.value ?? token.$value;
|
|
75
|
+
const description = token.$description ?? token.description ?? '';
|
|
76
|
+
|
|
77
|
+
if (description) {
|
|
78
|
+
lines.push(`/** ${description} */`);
|
|
79
|
+
}
|
|
80
|
+
if (outputMode === 'js') {
|
|
81
|
+
lines.push(`export const ${camel} = '${value}';`);
|
|
82
|
+
} else {
|
|
83
|
+
lines.push(`export declare const ${camel}: string;`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Duration tokens (V10 deprecated aliases) ──────────────────────────────
|
|
88
|
+
lines.push('');
|
|
89
|
+
lines.push('// Duration tokens (V10 — deprecated aliases)');
|
|
90
|
+
const v10Camels = [];
|
|
91
|
+
|
|
92
|
+
for (const v11Camel of durationCamels) {
|
|
93
|
+
const v10Camel = toV10CamelName(v11Camel);
|
|
94
|
+
v10Camels.push(v10Camel);
|
|
95
|
+
|
|
96
|
+
if (outputMode === 'js') {
|
|
97
|
+
lines.push(`/** @deprecated Use \`${v11Camel}\` instead */`);
|
|
98
|
+
lines.push(`export const ${v10Camel} = ${v11Camel};`);
|
|
99
|
+
} else {
|
|
100
|
+
lines.push(`/** @deprecated Use \`${v11Camel}\` instead */`);
|
|
101
|
+
lines.push(`export declare const ${v10Camel}: string;`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Easing map ────────────────────────────────────────────────────────────
|
|
106
|
+
lines.push('');
|
|
107
|
+
lines.push('// Easing tokens');
|
|
108
|
+
|
|
109
|
+
if (outputMode === 'dts') {
|
|
110
|
+
lines.push(
|
|
111
|
+
"export declare const easings: Record<'standard' | 'entrance' | 'exit', Record<'productive' | 'expressive', string>>;"
|
|
112
|
+
);
|
|
113
|
+
} else {
|
|
114
|
+
lines.push('export const easings = {');
|
|
115
|
+
for (const easingName of ['standard', 'entrance', 'exit']) {
|
|
116
|
+
lines.push(` ${easingName}: {`);
|
|
117
|
+
for (const mode of ['productive', 'expressive']) {
|
|
118
|
+
const key = `easing-${easingName}-${mode}`;
|
|
119
|
+
const value = tokenMap.get(key);
|
|
120
|
+
if (value === undefined) {
|
|
121
|
+
throw new Error(`Missing SD token: ${key}`);
|
|
122
|
+
}
|
|
123
|
+
lines.push(` ${mode}: '${value}',`);
|
|
124
|
+
}
|
|
125
|
+
lines.push(` },`);
|
|
126
|
+
}
|
|
127
|
+
lines.push('};');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── unstable_tokens ───────────────────────────────────────────────────────
|
|
131
|
+
lines.push('');
|
|
132
|
+
if (outputMode === 'dts') {
|
|
133
|
+
lines.push('export declare const unstable_tokens: readonly string[];');
|
|
134
|
+
} else {
|
|
135
|
+
lines.push('export const unstable_tokens = [');
|
|
136
|
+
for (const camel of durationCamels) lines.push(` '${camel}',`);
|
|
137
|
+
for (const v10 of v10Camels) lines.push(` '${v10}',`);
|
|
138
|
+
lines.push('];');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
lines.push('');
|
|
142
|
+
return lines.join('\n');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = {
|
|
146
|
+
name: 'carbon/motion-js-tokens',
|
|
147
|
+
format: carbonJsMotionTokensFormat,
|
|
148
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2018, 2026
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Emits `scss/generated/_surfaces.scss` from surfaces.json.
|
|
12
|
+
*
|
|
13
|
+
* Surface data lives in `$extensions["carbon.motion"]` on each leaf token —
|
|
14
|
+
* not in `$value` — so Style Dictionary cannot derive the output from the
|
|
15
|
+
* token value alone. This format reads surfaces.json directly (the same
|
|
16
|
+
* pattern used by `carbon/scss-tokens` in @carbon/themes, which reads Carbon
|
|
17
|
+
* JS metadata directly instead of using SD token values).
|
|
18
|
+
*
|
|
19
|
+
* Generates:
|
|
20
|
+
* $surfaces: (
|
|
21
|
+
* disclosure: ( kind: 'reveal', duration: 'moderate-01', … ),
|
|
22
|
+
* …
|
|
23
|
+
* );
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
const FILE_BANNER = `// Code generated by @carbon/motion. DO NOT EDIT.
|
|
30
|
+
//
|
|
31
|
+
// Copyright IBM Corp. 2018, 2026
|
|
32
|
+
//
|
|
33
|
+
// This source code is licensed under the Apache-2.0 license found in the
|
|
34
|
+
// LICENSE file in the root directory of this source tree.
|
|
35
|
+
//
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
// Path is resolved relative to this file (style-dictionary/formats/) → src/dtcg/
|
|
39
|
+
const SURFACES_PATH = path.resolve(__dirname, '../../src/dtcg/surfaces.json');
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Collect surface entries from surfaces.json.
|
|
43
|
+
*
|
|
44
|
+
* @returns {Array<{ name: string, description: string, recipe: object }>}
|
|
45
|
+
*/
|
|
46
|
+
function collectSurfaces() {
|
|
47
|
+
const dtcg = JSON.parse(fs.readFileSync(SURFACES_PATH, 'utf8'));
|
|
48
|
+
const results = [];
|
|
49
|
+
|
|
50
|
+
function traverse(obj, pathSegments) {
|
|
51
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
52
|
+
if (key.startsWith('$')) continue;
|
|
53
|
+
const segments = [...pathSegments, key];
|
|
54
|
+
if (
|
|
55
|
+
value &&
|
|
56
|
+
typeof value === 'object' &&
|
|
57
|
+
value.$extensions &&
|
|
58
|
+
value.$extensions['carbon.motion']
|
|
59
|
+
) {
|
|
60
|
+
results.push({
|
|
61
|
+
name: segments.join('-'),
|
|
62
|
+
description: value.$description || '',
|
|
63
|
+
recipe: value.$extensions['carbon.motion'],
|
|
64
|
+
});
|
|
65
|
+
} else if (value && typeof value === 'object') {
|
|
66
|
+
traverse(value, segments);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
traverse(dtcg, []);
|
|
72
|
+
return results;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Convert a camelCase JS key to kebab-case for Sass.
|
|
77
|
+
* e.g. "enterEasing" → "enter-easing", "blockSize" → "block-size"
|
|
78
|
+
*
|
|
79
|
+
* @param {string} str
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
function camelToKebab(str) {
|
|
83
|
+
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Serialise a JS recipe value to a Sass map / list / string literal.
|
|
88
|
+
* Arrays become Sass lists, plain objects become Sass maps, numbers stay
|
|
89
|
+
* numeric, strings get single-quoted.
|
|
90
|
+
*
|
|
91
|
+
* @param {*} value
|
|
92
|
+
* @param {number} [indent=0]
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
function toSassLiteral(value, indent = 0) {
|
|
96
|
+
const pad = ' '.repeat(indent);
|
|
97
|
+
const innerPad = ' '.repeat(indent + 1);
|
|
98
|
+
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
const items = value.map((v) => toSassLiteral(v, 0)).join(', ');
|
|
101
|
+
return `(${items})`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (value !== null && typeof value === 'object') {
|
|
105
|
+
const entries = Object.entries(value)
|
|
106
|
+
.map(
|
|
107
|
+
([k, v]) =>
|
|
108
|
+
`${innerPad}${camelToKebab(k)}: ${toSassLiteral(v, indent + 1)}`
|
|
109
|
+
)
|
|
110
|
+
.join(',\n');
|
|
111
|
+
return `(\n${entries},\n${pad})`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (typeof value === 'number') return String(value);
|
|
115
|
+
|
|
116
|
+
// string — single-quote it
|
|
117
|
+
return `'${String(value)}'`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @returns {string}
|
|
122
|
+
*/
|
|
123
|
+
function carbonScssMotionSurfacesFormat() {
|
|
124
|
+
const surfaceList = collectSurfaces();
|
|
125
|
+
const lines = [FILE_BANNER];
|
|
126
|
+
|
|
127
|
+
lines.push('$surfaces: (');
|
|
128
|
+
|
|
129
|
+
for (const { name, recipe } of surfaceList) {
|
|
130
|
+
// "surface-disclosure" → "disclosure"
|
|
131
|
+
const surfaceName = name.startsWith('surface-')
|
|
132
|
+
? name.slice('surface-'.length)
|
|
133
|
+
: name;
|
|
134
|
+
|
|
135
|
+
lines.push(` ${surfaceName}: (`);
|
|
136
|
+
for (const [key, value] of Object.entries(recipe)) {
|
|
137
|
+
const sassKey = camelToKebab(key);
|
|
138
|
+
lines.push(` ${sassKey}: ${toSassLiteral(value, 2)},`);
|
|
139
|
+
}
|
|
140
|
+
lines.push(` ),`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
lines.push(');');
|
|
144
|
+
lines.push('');
|
|
145
|
+
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
name: 'carbon/motion-scss-surfaces',
|
|
151
|
+
format: carbonScssMotionSurfacesFormat,
|
|
152
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2018, 2026
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Emits `scss/generated/_tokens.scss` from the motion DTCG tokens.
|
|
12
|
+
*
|
|
13
|
+
* Generates:
|
|
14
|
+
* - `$easings` Sass map (standard/entrance/exit × productive/expressive)
|
|
15
|
+
* - V11 `$duration-*` variables
|
|
16
|
+
* - V10 `$fast-*` / `$moderate-*` / `$slow-*` deprecated aliases
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const FILE_BANNER = `// Code generated by @carbon/motion. DO NOT EDIT.
|
|
20
|
+
//
|
|
21
|
+
// Copyright IBM Corp. 2018, 2026
|
|
22
|
+
//
|
|
23
|
+
// This source code is licensed under the Apache-2.0 license found in the
|
|
24
|
+
// LICENSE file in the root directory of this source tree.
|
|
25
|
+
//
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {{ dictionary: import('style-dictionary').Dictionary }} args
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
function carbonScssMotionTokensFormat({ dictionary }) {
|
|
33
|
+
// Build a lookup from kebab token name → resolved value
|
|
34
|
+
const tokenMap = new Map(
|
|
35
|
+
dictionary.allTokens.map((t) => [t.name, t.value ?? t.$value])
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const lines = [FILE_BANNER];
|
|
39
|
+
|
|
40
|
+
// ── $easings map ──────────────────────────────────────────────────────────
|
|
41
|
+
lines.push('/// Common component easings');
|
|
42
|
+
lines.push('/// @type Map');
|
|
43
|
+
lines.push('/// @access public');
|
|
44
|
+
lines.push('/// @group @carbon/motion');
|
|
45
|
+
lines.push('$easings: (');
|
|
46
|
+
for (const easingName of ['standard', 'entrance', 'exit']) {
|
|
47
|
+
lines.push(` ${easingName}: (`);
|
|
48
|
+
for (const mode of ['productive', 'expressive']) {
|
|
49
|
+
const key = `easing-${easingName}-${mode}`;
|
|
50
|
+
const value = tokenMap.get(key);
|
|
51
|
+
if (value === undefined) {
|
|
52
|
+
throw new Error(`Missing SD token: ${key}`);
|
|
53
|
+
}
|
|
54
|
+
lines.push(` ${mode}: ${value},`);
|
|
55
|
+
}
|
|
56
|
+
lines.push(` ),`);
|
|
57
|
+
}
|
|
58
|
+
lines.push(') !default;');
|
|
59
|
+
lines.push('');
|
|
60
|
+
|
|
61
|
+
// ── V11 duration variables ────────────────────────────────────────────────
|
|
62
|
+
const durationTokens = dictionary.allTokens.filter((t) =>
|
|
63
|
+
t.name.startsWith('duration-')
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
for (const token of durationTokens) {
|
|
67
|
+
const description = token.$description ?? token.description ?? '';
|
|
68
|
+
if (description) lines.push(`/// ${description}`);
|
|
69
|
+
lines.push('/// @access public');
|
|
70
|
+
lines.push('/// @type Duration');
|
|
71
|
+
lines.push('/// @group @carbon/motion');
|
|
72
|
+
lines.push(`$${token.name}: ${token.value ?? token.$value} !default;`);
|
|
73
|
+
lines.push('');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── V10 deprecated aliases ────────────────────────────────────────────────
|
|
77
|
+
lines.push('/// V10 backwards compatibility tokens');
|
|
78
|
+
const v10Map = {
|
|
79
|
+
'duration-fast-01': 'fast-01',
|
|
80
|
+
'duration-fast-02': 'fast-02',
|
|
81
|
+
'duration-moderate-01': 'moderate-01',
|
|
82
|
+
'duration-moderate-02': 'moderate-02',
|
|
83
|
+
'duration-slow-01': 'slow-01',
|
|
84
|
+
'duration-slow-02': 'slow-02',
|
|
85
|
+
};
|
|
86
|
+
for (const [v11, v10] of Object.entries(v10Map)) {
|
|
87
|
+
lines.push('/// @access public');
|
|
88
|
+
lines.push('/// @deprecated');
|
|
89
|
+
lines.push('/// @type Duration');
|
|
90
|
+
lines.push('/// @group @carbon/motion');
|
|
91
|
+
lines.push(`$${v10}: $${v11} !default;`);
|
|
92
|
+
lines.push('');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return lines.join('\n');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
name: 'carbon/motion-scss-tokens',
|
|
100
|
+
format: carbonScssMotionTokensFormat,
|
|
101
|
+
};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2018, 2026
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Style Dictionary configuration for @carbon/motion.
|
|
12
|
+
*
|
|
13
|
+
* Wires together the custom plugins in this directory and drives two separate
|
|
14
|
+
* builds:
|
|
15
|
+
*
|
|
16
|
+
* runJs() → js/generated/tokens.{js,d.ts}
|
|
17
|
+
* js/generated/surfaces.{js,d.ts}
|
|
18
|
+
*
|
|
19
|
+
* runScss() → scss/generated/_tokens.scss
|
|
20
|
+
* scss/generated/_surfaces.scss
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const { default: StyleDictionary } = require('style-dictionary');
|
|
26
|
+
|
|
27
|
+
// ── Custom plugins ─────────────────────────────────────────────────────────
|
|
28
|
+
const carbonMotionDuration = require('./transforms/duration');
|
|
29
|
+
const carbonMotionCubicBezier = require('./transforms/cubic-bezier');
|
|
30
|
+
const carbonJsMotionTokens = require('./formats/js-tokens');
|
|
31
|
+
const carbonJsMotionSurfaces = require('./formats/js-surfaces');
|
|
32
|
+
const carbonScssMotionTokens = require('./formats/scss-tokens');
|
|
33
|
+
const carbonScssMotionSurfaces = require('./formats/scss-surfaces');
|
|
34
|
+
|
|
35
|
+
// ── Paths ──────────────────────────────────────────────────────────────────
|
|
36
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
37
|
+
const DTCG_DIR = path.join(ROOT, 'src', 'dtcg');
|
|
38
|
+
const SCSS_GENERATED = path.join(ROOT, 'scss', 'generated');
|
|
39
|
+
const JS_GENERATED = path.join(ROOT, 'js', 'generated');
|
|
40
|
+
|
|
41
|
+
// ── Custom name transform ──────────────────────────────────────────────────
|
|
42
|
+
//
|
|
43
|
+
// Builds a kebab-case name from the token path segments.
|
|
44
|
+
// e.g. ['duration', 'fast', '01'] → 'duration-fast-01'
|
|
45
|
+
// ['easing', 'standard', 'productive'] → 'easing-standard-productive'
|
|
46
|
+
const carbonMotionNameKebab = {
|
|
47
|
+
name: 'carbon/motion-name-kebab',
|
|
48
|
+
type: 'name',
|
|
49
|
+
transform(token) {
|
|
50
|
+
return token.path.join('-');
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// ── Transform group ────────────────────────────────────────────────────────
|
|
55
|
+
const CARBON_MOTION_TRANSFORMS = [
|
|
56
|
+
'attribute/cti',
|
|
57
|
+
'carbon/motion-name-kebab',
|
|
58
|
+
'carbon/motion-duration',
|
|
59
|
+
'carbon/motion-cubic-bezier',
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
// ── SD configs ────────────────────────────────────────────────────────────
|
|
63
|
+
//
|
|
64
|
+
// Separate configs are used for JS and SCSS so that `runJs()` and `runScss()`
|
|
65
|
+
// only invoke their respective platforms, matching the themes pattern where
|
|
66
|
+
// each build step is independently callable.
|
|
67
|
+
|
|
68
|
+
// motion.json — JS output
|
|
69
|
+
const jsTokensConfig = {
|
|
70
|
+
source: [path.join(DTCG_DIR, 'motion.json')],
|
|
71
|
+
platforms: {
|
|
72
|
+
'js/tokens': {
|
|
73
|
+
transformGroup: 'carbon/motion',
|
|
74
|
+
buildPath: JS_GENERATED + '/',
|
|
75
|
+
files: [
|
|
76
|
+
{
|
|
77
|
+
destination: 'tokens.js',
|
|
78
|
+
format: 'carbon/motion-js-tokens',
|
|
79
|
+
options: { output: 'js' },
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
destination: 'tokens.d.ts',
|
|
83
|
+
format: 'carbon/motion-js-tokens',
|
|
84
|
+
options: { output: 'dts' },
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// motion.json — SCSS output
|
|
92
|
+
const scssTokensConfig = {
|
|
93
|
+
source: [path.join(DTCG_DIR, 'motion.json')],
|
|
94
|
+
platforms: {
|
|
95
|
+
'scss/tokens': {
|
|
96
|
+
transformGroup: 'carbon/motion',
|
|
97
|
+
buildPath: SCSS_GENERATED + '/',
|
|
98
|
+
files: [
|
|
99
|
+
{
|
|
100
|
+
destination: '_tokens.scss',
|
|
101
|
+
format: 'carbon/motion-scss-tokens',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// surfaces.json — JS output
|
|
109
|
+
//
|
|
110
|
+
// Surfaces are processed by their formats reading surfaces.json directly;
|
|
111
|
+
// SD still manages the build lifecycle and output directory creation.
|
|
112
|
+
// motion.json is included so SD can resolve the alias references in
|
|
113
|
+
// surfaces.json $value fields (even though the format reads $extensions
|
|
114
|
+
// directly and does not consume the resolved values).
|
|
115
|
+
const jsSurfacesConfig = {
|
|
116
|
+
source: [
|
|
117
|
+
path.join(DTCG_DIR, 'motion.json'),
|
|
118
|
+
path.join(DTCG_DIR, 'surfaces.json'),
|
|
119
|
+
],
|
|
120
|
+
// The two source files both have a top-level $description; SD reports this
|
|
121
|
+
// as a collision even though it is DTCG metadata, not a real token conflict.
|
|
122
|
+
log: { warnings: 'disabled' },
|
|
123
|
+
platforms: {
|
|
124
|
+
'js/surfaces': {
|
|
125
|
+
transformGroup: 'carbon/motion',
|
|
126
|
+
buildPath: JS_GENERATED + '/',
|
|
127
|
+
files: [
|
|
128
|
+
{
|
|
129
|
+
destination: 'surfaces.js',
|
|
130
|
+
format: 'carbon/motion-js-surfaces',
|
|
131
|
+
options: { output: 'js' },
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
destination: 'surfaces.d.ts',
|
|
135
|
+
format: 'carbon/motion-js-surfaces',
|
|
136
|
+
options: { output: 'dts' },
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// surfaces.json — SCSS output
|
|
144
|
+
const scssSurfacesConfig = {
|
|
145
|
+
source: [
|
|
146
|
+
path.join(DTCG_DIR, 'motion.json'),
|
|
147
|
+
path.join(DTCG_DIR, 'surfaces.json'),
|
|
148
|
+
],
|
|
149
|
+
log: { warnings: 'disabled' },
|
|
150
|
+
platforms: {
|
|
151
|
+
'scss/surfaces': {
|
|
152
|
+
transformGroup: 'carbon/motion',
|
|
153
|
+
buildPath: SCSS_GENERATED + '/',
|
|
154
|
+
files: [
|
|
155
|
+
{
|
|
156
|
+
destination: '_surfaces.scss',
|
|
157
|
+
format: 'carbon/motion-scss-surfaces',
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// ── Build a registered SD instance ────────────────────────────────────────
|
|
165
|
+
// In SD v5, register* methods live on the instance, not the class.
|
|
166
|
+
// We create one base instance with all plugins registered, then extend it
|
|
167
|
+
// per-config so each build inherits the registrations.
|
|
168
|
+
function createBase() {
|
|
169
|
+
const base = new StyleDictionary({});
|
|
170
|
+
base.registerTransform(carbonMotionNameKebab);
|
|
171
|
+
base.registerTransform(carbonMotionDuration);
|
|
172
|
+
base.registerTransform(carbonMotionCubicBezier);
|
|
173
|
+
base.registerTransformGroup({
|
|
174
|
+
name: 'carbon/motion',
|
|
175
|
+
transforms: CARBON_MOTION_TRANSFORMS,
|
|
176
|
+
});
|
|
177
|
+
base.registerFormat(carbonJsMotionTokens);
|
|
178
|
+
base.registerFormat(carbonJsMotionSurfaces);
|
|
179
|
+
base.registerFormat(carbonScssMotionTokens);
|
|
180
|
+
base.registerFormat(carbonScssMotionSurfaces);
|
|
181
|
+
return base;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── JS build ──────────────────────────────────────────────────────────────
|
|
185
|
+
// Generates:
|
|
186
|
+
// js/generated/tokens.{js,d.ts}
|
|
187
|
+
// js/generated/surfaces.{js,d.ts}
|
|
188
|
+
async function runJs() {
|
|
189
|
+
fs.mkdirSync(JS_GENERATED, { recursive: true });
|
|
190
|
+
const base = createBase();
|
|
191
|
+
await (await base.extend(jsTokensConfig)).buildAllPlatforms();
|
|
192
|
+
await (await base.extend(jsSurfacesConfig)).buildAllPlatforms();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── SCSS build ────────────────────────────────────────────────────────────
|
|
196
|
+
// Generates:
|
|
197
|
+
// scss/generated/_tokens.scss
|
|
198
|
+
// scss/generated/_surfaces.scss
|
|
199
|
+
async function runScss() {
|
|
200
|
+
fs.mkdirSync(SCSS_GENERATED, { recursive: true });
|
|
201
|
+
const base = createBase();
|
|
202
|
+
await (await base.extend(scssTokensConfig)).buildAllPlatforms();
|
|
203
|
+
await (await base.extend(scssSurfacesConfig)).buildAllPlatforms();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Full build (JS + SCSS) ─────────────────────────────────────────────────
|
|
207
|
+
async function run() {
|
|
208
|
+
await runJs();
|
|
209
|
+
await runScss();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = {
|
|
213
|
+
run,
|
|
214
|
+
runJs,
|
|
215
|
+
runScss,
|
|
216
|
+
jsTokensConfig,
|
|
217
|
+
scssTokensConfig,
|
|
218
|
+
jsSurfacesConfig,
|
|
219
|
+
scssSurfacesConfig,
|
|
220
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2018, 2026
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Converts a DTCG cubicBezier $value array `[x1, y1, x2, y2]` into the CSS
|
|
12
|
+
* string `"cubic-bezier(x1, y1, x2, y2)"`.
|
|
13
|
+
*
|
|
14
|
+
* Style Dictionary v5 does not perform this conversion for cubicBezier tokens
|
|
15
|
+
* automatically, so we handle it here.
|
|
16
|
+
*
|
|
17
|
+
* Examples:
|
|
18
|
+
* [0.2, 0, 0.38, 0.9] → 'cubic-bezier(0.2, 0, 0.38, 0.9)'
|
|
19
|
+
* [0, 0, 0.3, 1] → 'cubic-bezier(0, 0, 0.3, 1)'
|
|
20
|
+
*/
|
|
21
|
+
module.exports = {
|
|
22
|
+
name: 'carbon/motion-cubic-bezier',
|
|
23
|
+
type: 'value',
|
|
24
|
+
transitive: true,
|
|
25
|
+
filter(token) {
|
|
26
|
+
return token.$type === 'cubicBezier' || token.type === 'cubicBezier';
|
|
27
|
+
},
|
|
28
|
+
transform(token) {
|
|
29
|
+
const v = token.value !== undefined ? token.value : token.$value;
|
|
30
|
+
|
|
31
|
+
if (Array.isArray(v) && v.length === 4) {
|
|
32
|
+
return `cubic-bezier(${v.join(', ')})`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Already a resolved string — pass through.
|
|
36
|
+
return v;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2018, 2026
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Converts a DTCG duration $value object `{ value: N, unit: "ms" }` into the
|
|
12
|
+
* CSS string `"Nms"`.
|
|
13
|
+
*
|
|
14
|
+
* The DTCG spec stores duration as a composite object rather than a bare
|
|
15
|
+
* string; Style Dictionary does not know this shape natively, so we resolve
|
|
16
|
+
* it here before the value reaches any format.
|
|
17
|
+
*
|
|
18
|
+
* Examples:
|
|
19
|
+
* { value: 70, unit: 'ms' } → '70ms'
|
|
20
|
+
* { value: 400, unit: 'ms' } → '400ms'
|
|
21
|
+
*/
|
|
22
|
+
module.exports = {
|
|
23
|
+
name: 'carbon/motion-duration',
|
|
24
|
+
type: 'value',
|
|
25
|
+
transitive: true,
|
|
26
|
+
filter(token) {
|
|
27
|
+
return token.$type === 'duration' || token.type === 'duration';
|
|
28
|
+
},
|
|
29
|
+
transform(token) {
|
|
30
|
+
const v = token.value !== undefined ? token.value : token.$value;
|
|
31
|
+
|
|
32
|
+
if (v && typeof v === 'object' && 'value' in v && 'unit' in v) {
|
|
33
|
+
return `${v.value}${v.unit}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Already a resolved string (e.g. from a transitive pass) — pass through.
|
|
37
|
+
return v;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2018, 2026
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Serialise a JS value as a TypeScript type literal.
|
|
12
|
+
* Preserves string/number literal types (and nested object/tuple structure)
|
|
13
|
+
* so generated `.d.ts` files discriminate on `kind` and keep token names
|
|
14
|
+
* assignable to hand-authored unions like `DurationName`.
|
|
15
|
+
*
|
|
16
|
+
* Examples:
|
|
17
|
+
* 'reveal' → '"reveal"'
|
|
18
|
+
* 0 → '0'
|
|
19
|
+
* { kind: 'reveal' } → '{\n kind: "reveal"\n}'
|
|
20
|
+
* ['entrance', 'productive'] → '[\n "entrance",\n "productive"\n]'
|
|
21
|
+
*
|
|
22
|
+
* @param {*} value
|
|
23
|
+
* @param {number} [indent=0]
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
function toDtsTypeLiteral(value, indent = 0) {
|
|
27
|
+
const pad = ' '.repeat(indent);
|
|
28
|
+
const innerPad = ' '.repeat(indent + 1);
|
|
29
|
+
|
|
30
|
+
if (Array.isArray(value)) {
|
|
31
|
+
if (value.length === 0) return '[]';
|
|
32
|
+
const items = value
|
|
33
|
+
.map((item) => `${innerPad}${toDtsTypeLiteral(item, indent + 1)}`)
|
|
34
|
+
.join(',\n');
|
|
35
|
+
return `[\n${items}\n${pad}]`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (value !== null && typeof value === 'object') {
|
|
39
|
+
const entries = Object.entries(value);
|
|
40
|
+
if (entries.length === 0) return '{}';
|
|
41
|
+
const body = entries
|
|
42
|
+
.map(
|
|
43
|
+
([key, nested]) =>
|
|
44
|
+
`${innerPad}${key}: ${toDtsTypeLiteral(nested, indent + 1)}`
|
|
45
|
+
)
|
|
46
|
+
.join(',\n');
|
|
47
|
+
return `{\n${body}\n${pad}}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
51
|
+
return String(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (value === null) return 'null';
|
|
55
|
+
|
|
56
|
+
// string literal type
|
|
57
|
+
return JSON.stringify(String(value));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { toDtsTypeLiteral };
|