@massimo-cassandro/minimo 1.36.2 → 1.38.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.
@@ -67,6 +67,12 @@ const matchBraces = (text, openIndex) => {
67
67
  return -1;
68
68
  };
69
69
 
70
+ // Escapes a string for literal use inside a RegExp — needed because
71
+ // customPropsSelector (see config.mjs) can contain regex-special characters,
72
+ // e.g. ':where(html)'.
73
+ /** @param {string} str */
74
+ const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
75
+
70
76
  // Finds every @media (prefers-color-scheme: <mode>) { ... } block in a
71
77
  // previously generated sourceModes CSS destFile, with absolute character
72
78
  // offsets into cssText. Used to split the file into per-mode scopes (see
@@ -93,22 +99,25 @@ export const findMediaModeBlocks = (cssText) => {
93
99
 
94
100
  // Splits a previously generated sourceModes CSS destFile into per-mode
95
101
  // scopes (see build-source-modes.mjs for the shape of the generated file):
96
- // - the base scope is the top-level `:root { ... }` block, i.e. the one
97
- // NOT nested inside a `@media (prefers-color-scheme: ...)` rule
98
- // - every other scope is the `:root { ... }` block nested inside its own
99
- // `@media (prefers-color-scheme: <mode>) { ... }` rule
102
+ // - the base scope is the top-level `<selector> { ... }` block, i.e. the
103
+ // one NOT nested inside a `@media (prefers-color-scheme: ...)` rule
104
+ // - every other scope is the `<selector> { ... }` block nested inside its
105
+ // own `@media (prefers-color-scheme: <mode>) { ... }` rule
100
106
  // @param {string} cssText
107
+ // @param {string} [selector] the customPropsSelector the file was generated
108
+ // with (default: ':root' — see config.mjs)
101
109
  // @returns {Record<string,string>} mode → CSS body text (base mode keyed as '__base__')
102
- const splitModeScopes = (cssText) => {
110
+ const splitModeScopes = (cssText, selector = ':root') => {
103
111
  /** @type {Record<string,string>} */
104
112
  const scopes = {};
113
+ const selectorRe = new RegExp(`${escapeRegExp(selector)}\\s*\\{`);
105
114
 
106
115
  // 1. Every @media (prefers-color-scheme: <mode>) { ... } block, and the
107
- // :root { ... } body nested inside each one.
116
+ // <selector> { ... } body nested inside each one.
108
117
  const mediaBlocks = findMediaModeBlocks(cssText);
109
118
  for (const { mode, start, end } of mediaBlocks) {
110
119
  const body = cssText.slice(start + 1, end);
111
- const rootMatch = /:root\s*\{/.exec(body);
120
+ const rootMatch = selectorRe.exec(body);
112
121
  if (rootMatch) {
113
122
  const rootOpen = rootMatch.index + rootMatch[0].length - 1;
114
123
  const rootClose = matchBraces(body, rootOpen);
@@ -118,9 +127,9 @@ const splitModeScopes = (cssText) => {
118
127
  }
119
128
  }
120
129
 
121
- // 2. The base :root { ... } block: the first one not nested inside any of
122
- // the media blocks found above.
123
- const ROOT_RE = /:root\s*\{/g;
130
+ // 2. The base <selector> { ... } block: the first one not nested inside
131
+ // any of the media blocks found above.
132
+ const ROOT_RE = new RegExp(`${escapeRegExp(selector)}\\s*\\{`, 'g');
124
133
  for (const match of cssText.matchAll(ROOT_RE)) {
125
134
  const openIdx = match.index + match[0].length - 1;
126
135
  const insideMedia = mediaBlocks.some((b) => openIdx > b.start && openIdx < b.end);
@@ -188,22 +197,24 @@ export const loadExistingCustomProps = (absDestPath, sources = true) => {
188
197
  /**
189
198
  * sourceModes equivalent of loadExistingCustomProps(): reads the CSS
190
199
  * destination file (if it exists) and stores a per-mode name → value map,
191
- * scoped to each mode's own `:root { ... }` block (see splitModeScopes()).
200
+ * scoped to each mode's own `<selector> { ... }` block (see splitModeScopes()).
192
201
  * @param {string} absDestPath absolute path of the CSS file to read
193
202
  * @param {string} baseMode mode whose declarations live in the top-level,
194
- * non-nested `:root { ... }` block (no default — always passed explicitly
203
+ * non-nested `<selector> { ... }` block (no default — always passed explicitly
195
204
  * by build-source-modes.mjs, from the resolved sourceModesBase)
196
205
  * @param {true|(string|RegExp)[]} [sources] `true` merges every source, an
197
206
  * array only the token source files matching one of its entries (default: true)
207
+ * @param {string} [selector] the customPropsSelector the pre-existing file was
208
+ * generated with (default: ':root' — see config.mjs)
198
209
  * @returns {Record<string, Record<string,string>>|null}
199
210
  */
200
- export const loadExistingCustomPropsScoped = (absDestPath, baseMode, sources = true) => {
211
+ export const loadExistingCustomPropsScoped = (absDestPath, baseMode, sources = true, selector = ':root') => {
201
212
  mergeSources = sources;
202
213
  if (!existsSync(absDestPath)) {
203
214
  scopedExistingCustomProps = null;
204
215
  return scopedExistingCustomProps;
205
216
  }
206
- const scopes = splitModeScopes(readFileSync(absDestPath, 'utf8'));
217
+ const scopes = splitModeScopes(readFileSync(absDestPath, 'utf8'), selector);
207
218
  scopedExistingCustomProps = {};
208
219
  if (scopes.__base__ !== undefined) {
209
220
  scopedExistingCustomProps[baseMode] = parseCustomProps(scopes.__base__);
@@ -11,9 +11,10 @@ import { buildJsonFiles } from './formats/json.mjs';
11
11
  // Transforms applied to the CSS platform.
12
12
  // Exported so other scripts (e.g. check-unresolved-custom-props.mjs) can
13
13
  // build a Style Dictionary instance with matching transformed token names.
14
+ // name/kebab-prefixed = name/kebab + per-source prefix (see source-prefixes.mjs).
14
15
  export const CSS_TRANSFORMS = [
15
16
  'attribute/cti',
16
- 'name/kebab',
17
+ 'name/kebab-prefixed',
17
18
  'time/seconds',
18
19
  'asset/url',
19
20
  'size/pxToRem-smart',
@@ -27,7 +28,8 @@ export const CSS_TRANSFORMS = [
27
28
  // Transforms applied to the JSON platform.
28
29
  // Kept minimal on purpose: consuming tools expect original values (e.g. "16px"
29
30
  // not "1rem") and alias references ({...}) must be preserved to maintain
30
- // token links.
31
+ // token links. Source prefixes are not applied here (plain name/kebab): the
32
+ // JSON output keeps the original token tree.
31
33
  // Exported so build-source-modes.mjs can build a matching json platform per
32
34
  // sourceModes mode.
33
35
  export const JSON_TRANSFORMS = [
@@ -50,6 +52,7 @@ export const JSON_TRANSFORMS = [
50
52
  * @param {{name: string, prefixes: string[]}[]} opts.customPropsGroups Named groups of name prefixes, moved to the top of the CSS output in list order.
51
53
  * @param {boolean} opts.pxToRem If false, skips the px→rem transform on the CSS platform. Default: true.
52
54
  * @param {string|null} opts.addLayer Wraps the generated custom properties inside `@layer <addLayer> { ... }`. null = no layer.
55
+ * @param {string} opts.selector CSS selector wrapping the generated block (default: ':root').
53
56
  * @returns {Record<string, PlatformConfig>} platforms object ready for Style Dictionary config
54
57
  */
55
58
  export const buildPlatforms = ({
@@ -63,6 +66,7 @@ export const buildPlatforms = ({
63
66
  customPropsGroups = [],
64
67
  pxToRem = true,
65
68
  addLayer = null,
69
+ selector = ':root',
66
70
  }) => {
67
71
  const cssTransforms = pxToRem
68
72
  ? CSS_TRANSFORMS
@@ -80,6 +84,7 @@ export const buildPlatforms = ({
80
84
  options: {
81
85
  outputReferences: true,
82
86
  showFileHeader: true,
87
+ selector,
83
88
  customPropsGroups,
84
89
  addLayer,
85
90
  },
@@ -0,0 +1,129 @@
1
+ // build-tokens-src/source-prefixes.mjs
2
+ // Supports per-source custom property prefixes: an entry of `source` (or of a
3
+ // `sourceModes` mode) can be an object `{ src, prefix }` instead of a plain
4
+ // string, e.g.
5
+ //
6
+ // { src: 'node_modules/open-props/open-props.style-dictionary-tokens.json', prefix: 'op' }
7
+ //
8
+ // so that every token defined by those files is rendered as `--op-<name>`.
9
+ //
10
+ // How it works: the prefix is applied to the token NAME only (see the
11
+ // name/kebab-prefixed transform in transforms.mjs), never to its path.
12
+ // {references} are resolved through the token path and rendered with the
13
+ // already-prefixed name, so a token of another source referencing e.g.
14
+ // {gray.5} becomes `var(--op-gray-5)` with no change to the token files. The
15
+ // JSON output keeps the original (unprefixed) tree structure.
16
+ //
17
+ // The name transform only knows the concrete file a token comes from
18
+ // (token.filePath), while `src` may contain glob patterns: registerSourcePrefixes()
19
+ // expands them (through Style Dictionary itself, so the result matches
20
+ // token.filePath exactly) into a file path -> prefix map.
21
+
22
+ import StyleDictionary from 'style-dictionary';
23
+ import { LEGACY_TOKENS_PARSER_NAME } from './legacy-tokens-parser.mjs';
24
+ import { resolveSourcePaths } from './resolve-source-paths.mjs';
25
+ import { collectConcreteFilePaths } from './formats/json.mjs';
26
+
27
+ /**
28
+ * @typedef {object} PrefixedSource
29
+ * @property {string[]} src Source paths/globs, already resolved (see resolveSourcePaths)
30
+ * @property {string} prefix Normalised prefix, without leading `--` or trailing `-`
31
+ */
32
+
33
+ /** @type {Map<string, string>} */
34
+ let prefixByFile = new Map();
35
+
36
+ /**
37
+ * Normalises a user-provided prefix: 'op', 'op-' and '--op-' all become 'op'.
38
+ * @param {unknown} prefix
39
+ * @returns {string}
40
+ * @throws {Error} if the result is not a valid custom property name segment
41
+ */
42
+ export const normalizePrefix = (prefix) => {
43
+ const value = String(prefix).trim().replace(/^-+/, '').replace(/-+$/, '');
44
+ if (!/^[a-z_][\w-]*$/i.test(value)) {
45
+ throw new Error(`[build-tokens] config: invalid source prefix "${prefix}"`);
46
+ }
47
+ return value;
48
+ };
49
+
50
+ /**
51
+ * Splits a `source` (or `sourceModes[mode]`) array, whose entries can be
52
+ * strings or `{ src, prefix }` objects, into the flat list of patterns to pass
53
+ * to Style Dictionary and the list of the prefixed entries. Patterns are
54
+ * resolved relative to baseDir (see resolveSourcePaths).
55
+ * @param {(string|{src: string|string[], prefix?: string})[]} entries
56
+ * @param {string} baseDir
57
+ * @returns {{patterns: string[], prefixed: PrefixedSource[]}}
58
+ */
59
+ export const splitSourceEntries = (entries, baseDir) => {
60
+ /** @type {string[]} */
61
+ const patterns = [];
62
+ /** @type {PrefixedSource[]} */
63
+ const prefixed = [];
64
+
65
+ for (const entry of entries ?? []) {
66
+ if (typeof entry === 'string') {
67
+ patterns.push(...resolveSourcePaths([entry], baseDir));
68
+ continue;
69
+ }
70
+
71
+ const isObjectEntry = entry !== null && typeof entry === 'object'
72
+ && (typeof entry.src === 'string' || Array.isArray(entry.src));
73
+ if (!isObjectEntry) {
74
+ throw new Error(
75
+ `[build-tokens] config: invalid source entry ${JSON.stringify(entry)} `
76
+ + '(expected a string or an object { src, prefix })'
77
+ );
78
+ }
79
+
80
+ const src = resolveSourcePaths([].concat(entry.src), baseDir);
81
+ patterns.push(...src);
82
+
83
+ if (entry.prefix !== undefined && entry.prefix !== null && entry.prefix !== '') {
84
+ prefixed.push({ src, prefix: normalizePrefix(entry.prefix) });
85
+ }
86
+ }
87
+
88
+ return { patterns, prefixed };
89
+ };
90
+
91
+ /**
92
+ * Expands the `src` patterns of every prefixed entry into concrete file paths
93
+ * and stores the file path -> prefix map used by getSourcePrefix().
94
+ * Must be called after registerLegacyTokensParser() and before any build.
95
+ * @param {PrefixedSource[]} prefixedSources
96
+ * @returns {Promise<void>}
97
+ * @throws {Error} if the same file is given two different prefixes
98
+ */
99
+ export const registerSourcePrefixes = async (prefixedSources) => {
100
+ /** @type {Map<string, string>} */
101
+ const map = new Map();
102
+
103
+ for (const { src, prefix } of prefixedSources) {
104
+ const sd = new StyleDictionary({
105
+ source: src,
106
+ parsers: [LEGACY_TOKENS_PARSER_NAME],
107
+ log: { verbosity: 'silent' },
108
+ platforms: {},
109
+ });
110
+
111
+ for (const filePath of await collectConcreteFilePaths(sd)) {
112
+ const existing = map.get(filePath);
113
+ if (existing !== undefined && existing !== prefix) {
114
+ throw new Error(
115
+ `[build-tokens] config: "${filePath}" has two different prefixes ("${existing}" and "${prefix}")`
116
+ );
117
+ }
118
+ map.set(filePath, prefix);
119
+ }
120
+ }
121
+
122
+ prefixByFile = map;
123
+ };
124
+
125
+ /**
126
+ * @param {string|undefined} filePath token.filePath
127
+ * @returns {string|undefined} the prefix registered for that file, if any
128
+ */
129
+ export const getSourcePrefix = (filePath) => (filePath ? prefixByFile.get(filePath) : undefined);
@@ -3,6 +3,7 @@
3
3
  // Imported once by build-tokens.mjs before buildAllPlatforms() is called.
4
4
 
5
5
  import StyleDictionary from 'style-dictionary';
6
+ import { getSourcePrefix } from './source-prefixes.mjs';
6
7
 
7
8
  const BASE_FONT_SIZE = 16;
8
9
 
@@ -212,3 +213,22 @@ StyleDictionary.registerTransform({
212
213
  filter: (token) => token.$type === 'typography' || token.type === 'typography',
213
214
  transform: (token) => token.$value ?? token.value,
214
215
  });
216
+
217
+ // ---------------------------------------------------------------------------
218
+ // 7. Kebab-case name with per-source prefix
219
+ // Same as the built-in name/kebab, but prepends the prefix registered for the
220
+ // token's source file (`{ src, prefix }` entries of `source`/`sourceModes`, see
221
+ // source-prefixes.mjs), e.g. Open Props' gray.0 with prefix 'op' -> op-gray-0.
222
+ // Only the name changes: the token path is untouched, so {references} keep
223
+ // resolving and are rendered as var(--<prefixed-name>). Files without a
224
+ // prefix get exactly the built-in name/kebab result.
225
+ // ---------------------------------------------------------------------------
226
+ StyleDictionary.registerTransform({
227
+ name: 'name/kebab-prefixed',
228
+ type: 'name',
229
+ transform: (token, options) => {
230
+ const kebab = StyleDictionary.hooks.transforms['name/kebab'].transform;
231
+ const prefix = getSourcePrefix(token.filePath);
232
+ return kebab(prefix ? { ...token, path: [prefix, ...token.path] } : token, options);
233
+ },
234
+ });
@@ -14,6 +14,7 @@
14
14
  // build-tokens-src/platforms.mjs <- builds the platforms object (css + optional json)
15
15
  // build-tokens-src/build-source-modes.mjs <- alternative build path used when sourceModes is set
16
16
  // build-tokens-src/merge-css.mjs <- supports the mergeCustomProps option (flat and per-mode)
17
+ // build-tokens-src/source-prefixes.mjs <- supports `{ src, prefix }` entries in source/sourceModes
17
18
 
18
19
  import StyleDictionary from 'style-dictionary';
19
20
  import * as path from 'node:path';
@@ -35,10 +36,13 @@ import {
35
36
  source,
36
37
  sourceModes,
37
38
  sourceModesBase,
39
+ prefixedSources,
38
40
  mergeCustomProps,
39
41
  customPropsGroups,
40
42
  pxToRem,
41
43
  addLayer,
44
+ useLightDarkFunc,
45
+ customPropsSelector,
42
46
  } from './build-tokens-src/config.mjs';
43
47
 
44
48
  // ── 2. Custom transforms ─────────────────────────────────────────────────────
@@ -50,6 +54,13 @@ import './build-tokens-src/transforms.mjs';
50
54
  import { LEGACY_TOKENS_PARSER_NAME, registerLegacyTokensParser } from './build-tokens-src/legacy-tokens-parser.mjs';
51
55
  registerLegacyTokensParser();
52
56
 
57
+ // ── 2c. Per-source custom property prefixes ──────────────────────────────────
58
+ // Expands the `{ src, prefix }` entries of source/sourceModes into the file ->
59
+ // prefix map read by the name/kebab-prefixed transform (needs the legacy
60
+ // parser above, to load legacy .json sources) — see source-prefixes.mjs.
61
+ import { registerSourcePrefixes } from './build-tokens-src/source-prefixes.mjs';
62
+ await registerSourcePrefixes(prefixedSources);
63
+
53
64
  // ── 3. CSS format ─────────────────────────────────────────────────────────────
54
65
  // customPropsCount is updated by the format at render time
55
66
  // loadExistingCustomProps supports the mergeCustomProps option (see below)
@@ -105,6 +116,8 @@ if (sourceModes) {
105
116
  pxToRem,
106
117
  addLayer,
107
118
  mergeCustomProps,
119
+ useLightDarkFunc,
120
+ customPropsSelector,
108
121
  });
109
122
 
110
123
  } else {
@@ -157,6 +170,7 @@ if (sourceModes) {
157
170
  customPropsGroups,
158
171
  pxToRem,
159
172
  addLayer,
173
+ selector: customPropsSelector,
160
174
  }),
161
175
  });
162
176
 
@@ -24,7 +24,7 @@ import { styleText } from 'node:util';
24
24
  import StyleDictionary from 'style-dictionary';
25
25
  import './build-tokens-src/transforms.mjs';
26
26
  import { CSS_TRANSFORMS } from './build-tokens-src/platforms.mjs';
27
- import { resolveSourcePaths } from './build-tokens-src/resolve-source-paths.mjs';
27
+ import { splitSourceEntries, registerSourcePrefixes } from './build-tokens-src/source-prefixes.mjs';
28
28
  import { findMediaModeBlocks } from './build-tokens-src/merge-css.mjs';
29
29
  import { LEGACY_TOKENS_PARSER_NAME, registerLegacyTokensParser } from './build-tokens-src/legacy-tokens-parser.mjs';
30
30
 
@@ -202,17 +202,22 @@ async function run() {
202
202
  /** @type {Map<string, Map<string, string>>} */
203
203
  const sourceFileByPropByMode = new Map();
204
204
 
205
- // includePatterns: base-mode sources, passed as `include` for non-base
205
+ // includeEntries: base-mode sources, passed as `include` for non-base
206
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
+ /** @param {Parameters<typeof splitSourceEntries>[0]} sourceEntries @param {Parameters<typeof splitSourceEntries>[0]} [includeEntries] @returns {Promise<Map<string,string>>} */
208
+ // Entries can be strings or `{ src, prefix }` objects (see
209
+ // build-tokens-src/source-prefixes.mjs): the prefixes are registered
210
+ // before the transform pipeline runs, so token names match the CSS.
211
+ const resolveSourceFileMap = async (sourceEntries, includeEntries = []) => {
209
212
  /** @type {Map<string, string>} */
210
213
  const map = new Map();
211
- const resolvedPaths = resolveSourcePaths(sourcePatterns, configDir);
214
+ const { patterns: resolvedPaths, prefixed } = splitSourceEntries(sourceEntries, configDir);
215
+ const { patterns: includePaths, prefixed: includePrefixed } = splitSourceEntries(includeEntries, configDir);
212
216
  if (!resolvedPaths.length) return map;
213
217
  try {
218
+ await registerSourcePrefixes([...prefixed, ...includePrefixed]);
214
219
  const sd = new StyleDictionary({
215
- include: resolveSourcePaths(includePatterns, configDir),
220
+ include: includePaths,
216
221
  source: resolvedPaths,
217
222
  parsers: [LEGACY_TOKENS_PARSER_NAME],
218
223
  log: { verbosity: 'silent' },
@@ -230,8 +235,8 @@ async function run() {
230
235
 
231
236
  if (checkUnused && config.sourceModes) {
232
237
  for (const [mode, modeSource] of Object.entries(config.sourceModes)) {
233
- const includePatterns = mode === sourceModesBase ? [] : config.sourceModes[sourceModesBase];
234
- sourceFileByPropByMode.set(mode, await resolveSourceFileMap(modeSource, includePatterns));
238
+ const includeEntries = mode === sourceModesBase ? [] : config.sourceModes[sourceModesBase];
239
+ sourceFileByPropByMode.set(mode, await resolveSourceFileMap(modeSource, includeEntries));
235
240
  }
236
241
  } else if (checkUnused && Array.isArray(config.source) && config.source.length) {
237
242
  const map = await resolveSourceFileMap(config.source);
@@ -51,15 +51,24 @@ const config = {
51
51
  // the pattern syntax. Use template literals instead:
52
52
  // OK: `${minimo_path}/**/*.{json,mjs}`
53
53
  // NO: path.join(minimo_path, '/**/*.{json,mjs}')
54
+ //
55
+ // An entry can also be an object `{ src, prefix }` (src: string or array of
56
+ // strings/globs), to render the custom properties of those files with a
57
+ // prefix: e.g. { src: '...open-props...json', prefix: 'op' } turns Open
58
+ // Props' gray.0 into --op-gray-0. Only the property name changes:
59
+ // {references} to those tokens are rendered as var(--op-gray-0), and the JSON
60
+ // output stays unprefixed. 'op', 'op-' and '--op-' are equivalent. The same
61
+ // syntax works inside sourceModes. Two different prefixes for the same file
62
+ // make the build fail.
54
63
  source: [
55
64
  `${minimo_path}/design-tokens/_src/**/*.tokens.{mjs,jsonc}`, // main tokens
56
65
  `${minimo_path}/src/**/*.tokens.{mjs,jsonc}`, // components tokens
57
66
 
58
67
  // optional extra token source (in this example, openProps, https://open-props.style/)
59
- // `${node_modules_path}/open-props/open-props.style-dictionary-tokens.json`,
68
+ // { src: `${node_modules_path}/open-props/open-props.style-dictionary-tokens.json`, prefix: 'op' },
60
69
 
61
70
  // your project tokens
62
- './project-tokens/*.{js,mjs,jsonc,json}',
71
+ {src: './project-tokens/*.{js,mjs,jsonc,json}', prefix: 'my-project'}
63
72
  ],
64
73
 
65
74
  // Optional light/dark (or other) custom properties split — alternative to
@@ -101,12 +110,54 @@ const config = {
101
110
  // },
102
111
  sourceModes: null,
103
112
 
113
+ // With sourceModes set, and only if it defines both a `light` and a `dark`
114
+ // key: custom properties present in BOTH — matched by name — are written
115
+ // once as `--name: light-dark(<light-value>, <dark-value>);`, instead of
116
+ // being split across the base mode's top-level block and a
117
+ // `@media (prefers-color-scheme: dark) { ... }` rule.
118
+ //
119
+ // Unaffected:
120
+ // - any other key in sourceModes besides light/dark (e.g. a third,
121
+ // custom mode) — always stays on its own
122
+ // `@media (prefers-color-scheme: <mode>) { ... }` rule
123
+ // - a property present in only one of `light`/`dark` — stays in its own
124
+ // mode's block (still behind `@media`, unless that mode happens to be
125
+ // sourceModesBase)
126
+ //
127
+ // Whenever this option is active (sourceModes has both a light and a dark
128
+ // key), the base mode's `color-scheme` declaration is always normalised to
129
+ // read "light dark [...any other mode]", with light before dark — matching
130
+ // light-dark()'s own (light-value, dark-value) argument order — regardless
131
+ // of the order sourceModes keys were declared in, or of which of the two
132
+ // is configured as sourceModesBase below.
133
+ //
134
+ // Interaction with mergeCustomProps: no special handling needed — the
135
+ // per-mode merge with pre-existing custom properties always runs first;
136
+ // this option then reconciles whatever value ends up in each mode
137
+ // (freshly generated or preserved from a pre-existing file) into
138
+ // light-dark(), matched by name. This also means that re-running a build
139
+ // with this option newly turned on, over a destFile previously generated
140
+ // with it off (classic `@media (prefers-color-scheme: dark)` split) and
141
+ // mergeCustomProps: true, converts the preserved values into light-dark()
142
+ // calls too — as with any mergeCustomProps case, a resulting mismatch is
143
+ // left for you to fix.
144
+ //
145
+ // Default: true. Ignored when sourceModes is not set (null/undefined).
146
+ useLightDarkFunc: true,
147
+
104
148
  // With sourceModes set: the mode whose declarations go in the top-level
105
149
  // `:root { ... }` block, instead of nested inside its own
106
150
  // `@media (prefers-color-scheme: <mode>) { ... }` rule (default: the first
107
151
  // key of sourceModes, e.g. 'light'). Ignored when sourceModes is not set.
108
152
  sourceModesBase: 'light',
109
153
 
154
+ // CSS selector wrapping the generated custom properties block(s), instead
155
+ // of the default `:root`. Applies uniformly whether using a single
156
+ // `source` or `sourceModes` (the base mode's top-level block and every
157
+ // other mode's block nested under its own
158
+ // `@media (prefers-color-scheme: <mode>)` rule all use this same
159
+ // selector). Falsy or blank values fall back to ':root'.
160
+ customPropsSelector: ':where(html)',
110
161
 
111
162
  // If true (default), dimension token values expressed in px are converted
112
163
  // to rem in the generated CSS. Values in other units (em, %, vh, dvw, ...)
@@ -132,7 +183,7 @@ const config = {
132
183
  // Comments at the end of a custom property line are preserved; full-line
133
184
  // comments are lost, and a multi-line comment is cut at the end of its first
134
185
  // line (and closed, to keep the CSS valid).
135
- mergeCustomProps: false,
186
+ mergeCustomProps: true,
136
187
 
137
188
  // Wraps the generated custom properties inside a CSS `@layer` at-rule,
138
189
  // e.g. addLayer: 'project' -> `@layer project { :root { ... } }`.
@@ -146,7 +197,7 @@ const config = {
146
197
  customPropsGroups: [
147
198
  {
148
199
  name: 'COLORS',
149
- prefixes: ['accent', 'primary', 'secondary', 'neutral'],
200
+ prefixes: ['accent', 'primary', 'secondary', 'neutral', 'color'],
150
201
  },
151
202
  {
152
203
  name: 'LAYOUT',
@@ -415,7 +415,7 @@ done
415
415
  # installato in node_modules (stessa versione che userà il progetto)
416
416
  MINIMO_PKG_DIR=./node_modules/@massimo-cassandro/minimo
417
417
 
418
- force_cat "${MINIMO_PKG_DIR}/src/custom-properties.css" "${FRONTEND_INSTALL_PATH}/css/custom-properties.css"
418
+ force_cat "${MINIMO_PKG_DIR}/src/custom-properties.css" "${FRONTEND_INSTALL_PATH}/css/minimo-custom-properties.css"
419
419
  force_cat "${MINIMO_PKG_DIR}/src/custom-media.css" "${FRONTEND_INSTALL_PATH}/css/custom-media.css"
420
420
 
421
421
  # entry css principale del progetto: copia diretta di minimo.css, rinominato
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massimo-cassandro/minimo",
3
- "version": "1.36.2",
3
+ "version": "1.38.0",
4
4
  "description": "Light css framework & utilities",
5
5
  "type": "module",
6
6
  "main": "./index.js",