@massimo-cassandro/minimo 1.35.0 → 1.35.1

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.
@@ -161,6 +161,8 @@ When set, each mode has its own `source` array, and the generated CSS composes a
161
161
  - the base mode's props go in a top-level `:root { ... }` block, with `color-scheme: <all modes>;` prepended
162
162
  - every other mode's props go in `@media (prefers-color-scheme: <mode>) { :root { ... } }`, with `color-scheme: <mode>;` prepended
163
163
 
164
+ Tokens of a non-base mode can reference tokens defined in the base mode (e.g. a dark token using `{color.1}` defined in light, output as `var(--color-1)`). References to a non-base mode's tokens (from the base mode or from another non-base mode) fail with a "reference not defined" error, since the target would be undefined outside its own `@media` block.
165
+
164
166
  `customPropsGroups`, `mergeCustomProps`, `addLayer`, `pxToRem` etc. all apply the same way as with a single `source`. The JSON output (when `jsonBuildPath` is set) is produced once per mode, with the mode name appended to each filename (e.g. `tokens-light.jsonc` / `tokens-dark.jsonc`).
165
167
 
166
168
  ---
@@ -80,7 +80,16 @@ export const buildSourceModes = async ({
80
80
  const jsonFilesByMode = {};
81
81
 
82
82
  for (const mode of modeNames) {
83
+ // Non-base modes may reference base-mode tokens (always defined, since
84
+ // the base block is the unconditional top-level `:root`). The base
85
+ // sources are passed as `include`: available for reference resolution
86
+ // but flagged isSource: false, and skipped by the css/json formats.
87
+ // Base-mode-only on purpose: a reference to a token that exists only in
88
+ // another `@media` block would build fine but be undefined at runtime.
89
+ const include = mode === baseMode ? [] : sourceModes[baseMode];
90
+
83
91
  const sd = new StyleDictionary({
92
+ include,
84
93
  source: sourceModes[mode],
85
94
  parsers: [LEGACY_TOKENS_PARSER_NAME],
86
95
  log: { verbosity: 'verbose' },
@@ -116,6 +125,7 @@ export const buildSourceModes = async ({
116
125
  const files = buildJsonFiles(concreteFilePaths, jsonDestFile, jsonFormat, jsonExpression, `-${mode}`);
117
126
 
118
127
  const jsonSd = new StyleDictionary({
128
+ include,
119
129
  source: sourceModes[mode],
120
130
  parsers: [LEGACY_TOKENS_PARSER_NAME],
121
131
  log: { verbosity: 'silent' },
@@ -189,8 +189,11 @@ StyleDictionary.registerFormat({
189
189
  return a.name.localeCompare(b.name, 'en', { numeric: true, sensitivity: 'base' });
190
190
  };
191
191
 
192
+ // Tokens loaded via `include` (sourceModes: base-mode tokens made
193
+ // available to other modes) stay in tokenByPath, so references to them
194
+ // resolve to var(--name), but are not declared in this mode's block.
192
195
  const lines = dictionary.allTokens
193
- .slice()
196
+ .filter((token) => token.isSource !== false)
194
197
  // .sort((a, b) => a.name.localeCompare(b.name, 'en', {numeric: true, sensitivity: 'base'}))
195
198
  .sort((a, b) => sortFunction(a,b))
196
199
  .flatMap((token) => {
@@ -247,6 +250,15 @@ StyleDictionary.registerFormat({
247
250
  value = String(token.$value ?? token.value);
248
251
  }
249
252
 
253
+ // Multi-line values (e.g. Open Props' linear() easings) are collapsed
254
+ // to a single line: declarations are parsed one line at a time (see
255
+ // parseCustomProps in ../merge-css.mjs), so a multi-line value would
256
+ // be truncated to its first line.
257
+ value = String(value)
258
+ .replace(/\s*\n\s*/g, ' ')
259
+ .replace(/\(\s+/g, '(')
260
+ .replace(/\s+\)/g, ')');
261
+
250
262
  return [` --${token.name}: ${value};${commentSuffix}`];
251
263
  });
252
264
 
@@ -136,6 +136,10 @@ StyleDictionary.registerFormat({
136
136
  const tokenMap = (expressionMode === 'resolve') ? makeTokenMap(dictionary) : null;
137
137
 
138
138
  for (const token of dictionary.allTokens) {
139
+ // Tokens loaded via `include` (sourceModes: base-mode tokens made
140
+ // available to other modes for reference resolution) are not output.
141
+ if (token.isSource === false) continue;
142
+
139
143
  // Rebuild the nested tree from the token path
140
144
  let node = root;
141
145
  for (let i = 0; i < token.path.length - 1; i++) {
@@ -205,7 +209,7 @@ export const collectConcreteFilePaths = async (sd) => {
205
209
  await sd.hasInitialized;
206
210
  const paths = new Set();
207
211
  for (const token of sd.allTokens) {
208
- if (token.filePath) paths.add(token.filePath);
212
+ if (token.filePath && token.isSource !== false) paths.add(token.filePath);
209
213
  }
210
214
  return [...paths].sort();
211
215
  };
@@ -202,14 +202,17 @@ async function run() {
202
202
  /** @type {Map<string, Map<string, string>>} */
203
203
  const sourceFileByPropByMode = new Map();
204
204
 
205
- /** @param {string[]} sourcePatterns @returns {Promise<Map<string,string>>} */
206
- const resolveSourceFileMap = async (sourcePatterns) => {
205
+ // includePatterns: base-mode sources, passed as `include` for non-base
206
+ // modes so cross-mode references resolve (same as build-source-modes.mjs).
207
+ /** @param {string[]} sourcePatterns @param {string[]} [includePatterns] @returns {Promise<Map<string,string>>} */
208
+ const resolveSourceFileMap = async (sourcePatterns, includePatterns = []) => {
207
209
  /** @type {Map<string, string>} */
208
210
  const map = new Map();
209
211
  const resolvedPaths = resolveSourcePaths(sourcePatterns, configDir);
210
212
  if (!resolvedPaths.length) return map;
211
213
  try {
212
214
  const sd = new StyleDictionary({
215
+ include: resolveSourcePaths(includePatterns, configDir),
213
216
  source: resolvedPaths,
214
217
  parsers: [LEGACY_TOKENS_PARSER_NAME],
215
218
  log: { verbosity: 'silent' },
@@ -217,7 +220,7 @@ async function run() {
217
220
  });
218
221
  const dictionary = await sd.getPlatformTokens('css');
219
222
  for (const token of dictionary.allTokens) {
220
- if (token.filePath) map.set(`--${token.name}`, token.filePath);
223
+ if (token.filePath && token.isSource !== false) map.set(`--${token.name}`, token.filePath);
221
224
  }
222
225
  } catch {
223
226
  // Non-fatal: unused properties will be reported by name only.
@@ -227,7 +230,8 @@ async function run() {
227
230
 
228
231
  if (checkUnused && config.sourceModes) {
229
232
  for (const [mode, modeSource] of Object.entries(config.sourceModes)) {
230
- sourceFileByPropByMode.set(mode, await resolveSourceFileMap(modeSource));
233
+ const includePatterns = mode === sourceModesBase ? [] : config.sourceModes[sourceModesBase];
234
+ sourceFileByPropByMode.set(mode, await resolveSourceFileMap(modeSource, includePatterns));
231
235
  }
232
236
  } else if (checkUnused && Array.isArray(config.source) && config.source.length) {
233
237
  const map = await resolveSourceFileMap(config.source);
@@ -77,6 +77,13 @@ const config = {
77
77
  // customPropsGroups, mergeCustomProps, addLayer, pxToRem etc. all apply
78
78
  // the same way as with a single `source`.
79
79
  //
80
+ // Cross-mode references: tokens of a non-base mode can reference tokens
81
+ // defined in the base mode (e.g. a dark token using {color.1} defined in
82
+ // light); it resolves to var(--color-1). The reverse (or between two
83
+ // non-base modes) is not supported and fails with a "reference not
84
+ // defined" error, since the target would be undefined outside its own
85
+ // @media block.
86
+ //
80
87
  // The JSON output (see JSON TOKENS OUTPUT below) is produced once per
81
88
  // mode, with the mode name appended to each filename, e.g. with
82
89
  // jsonDestFile: 'tokens' -> "tokens-light.jsonc", "tokens-dark.jsonc".
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massimo-cassandro/minimo",
3
- "version": "1.35.0",
3
+ "version": "1.35.1",
4
4
  "description": "Light css framework & utilities",
5
5
  "type": "module",
6
6
  "main": "./index.js",