@pantoken/utils 0.3.0 → 0.5.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/dist/index.d.mts CHANGED
@@ -60,33 +60,9 @@ declare function focusOutlineDeclarations(): [name: string, value: string][];
60
60
  */
61
61
  declare function focusOutlineRules(selector?: string): string;
62
62
  //#endregion
63
- //#region src/index.d.ts
63
+ //#region src/resolver.d.ts
64
64
  /** The colour mode to collapse `light-dark()` to. */
65
65
  type Mode = "light" | "dark";
66
- /** One entry of the pantoken spacing scale: the short key, the long (word) key, and the shared CSS value. */
67
- interface SpacingStep {
68
- /** The short scale key, e.g. `"sm"` (the zero step's short key is `"0"`). */
69
- short: string;
70
- /** The long, word-spelled key, e.g. `"small"` (the zero step's long key is `"none"`). */
71
- long: string;
72
- /** The CSS value — a token reference, or a plain `"0"`/`"auto"`. */
73
- value: string;
74
- }
75
- /**
76
- * The pantoken spacing scale, `none`/`0` → `2xl` — the single shared source for
77
- * `@pantoken/components`' spacing/gap utilities and `@pantoken/interactions`' `resolveSpace`, so the
78
- * two never drift apart.
79
- */
80
- declare const SPACING_STEPS: readonly SpacingStep[];
81
- /** The `auto` step — margin only, spelled the same both ways. */
82
- declare const SPACING_AUTO_STEP: SpacingStep;
83
- /**
84
- * Strip `<script>` elements and event-handler attributes from SVG markup.
85
- *
86
- * Defense-in-depth for SVG decoded from vendored data URIs or contributed by plugins.
87
- * Not a full HTML parser — relies on the upstream source being trusted and pinned.
88
- */
89
- declare function sanitizeSvg(svg: string): string;
90
66
  /** A `var(--custom-prop)` reference (optional fallback); capture group 1 is the property name. */
91
67
  declare const VAR_RE: RegExp;
92
68
  /** A `light-dark(<light>, <dark>)` value; capture groups 1 and 2 are the two branches. */
@@ -172,38 +148,8 @@ declare function makeResolver(base: readonly Token[], options?: ResolveOptions):
172
148
  * ```
173
149
  */
174
150
  declare function resolveTokens(base: readonly Token[], options?: ResolveOptions): Map<string, string>;
175
- /**
176
- * Convert a kebab-case string to camelCase (`color-background-brand` → `colorBackgroundBrand`).
177
- *
178
- * @example
179
- * ```ts
180
- * import { camelCase } from "@pantoken/utils";
181
- *
182
- * camelCase("color-background-brand"); // → "colorBackgroundBrand"
183
- * ```
184
- */
185
- declare function camelCase(kebab: string): string;
186
- /** An RGBA colour: `r`/`g`/`b` are 0–255 integers, `a` is 0–1 (defaults to 1). */
187
- interface Rgba {
188
- r: number;
189
- g: number;
190
- b: number;
191
- a: number;
192
- }
193
- /**
194
- * Parse `#rgb`, `#rrggbb`, or `#rrggbbaa` to {@link Rgba}; returns `undefined` otherwise.
195
- *
196
- * @example
197
- * ```ts
198
- * import { parseHexColor } from "@pantoken/utils";
199
- *
200
- * parseHexColor("#fff"); // → { r: 255, g: 255, b: 255, a: 1 }
201
- * parseHexColor("#0374B5"); // → { r: 3, g: 116, b: 181, a: 1 }
202
- * parseHexColor("#00000080"); // → { r: 0, g: 0, b: 0, a: 0.5019… }
203
- * parseHexColor("nope"); // → undefined
204
- * ```
205
- */
206
- declare function parseHexColor(hex: string): Rgba | undefined;
151
+ //#endregion
152
+ //#region src/drift.d.ts
207
153
  /**
208
154
  * Every `--instui-*` custom-property name that appears anywhere in `text`.
209
155
  *
@@ -277,6 +223,101 @@ declare function unknownReferences(text: string, ir: readonly Token[]): string[]
277
223
  * ```
278
224
  */
279
225
  declare function danglingReferences(css: string): string[];
226
+ //#endregion
227
+ //#region src/case.d.ts
228
+ /**
229
+ * Convert a kebab-case string to camelCase (`color-background-brand` → `colorBackgroundBrand`).
230
+ *
231
+ * @module
232
+ */
233
+ /**
234
+ * Convert a kebab-case string to camelCase (`color-background-brand` → `colorBackgroundBrand`).
235
+ *
236
+ * @example
237
+ * ```ts
238
+ * import { camelCase } from "@pantoken/utils";
239
+ *
240
+ * camelCase("color-background-brand"); // → "colorBackgroundBrand"
241
+ * ```
242
+ */
243
+ declare function camelCase(kebab: string): string;
244
+ //#endregion
245
+ //#region src/color.d.ts
246
+ /**
247
+ * Hex-colour parsing (`#rgb`/`#rrggbb`/`#rrggbbaa` → {@link Rgba}).
248
+ *
249
+ * @module
250
+ */
251
+ /** An RGBA colour: `r`/`g`/`b` are 0–255 integers, `a` is 0–1 (defaults to 1). */
252
+ interface Rgba {
253
+ r: number;
254
+ g: number;
255
+ b: number;
256
+ a: number;
257
+ }
258
+ /**
259
+ * Parse `#rgb`, `#rrggbb`, or `#rrggbbaa` to {@link Rgba}; returns `undefined` otherwise.
260
+ *
261
+ * @example
262
+ * ```ts
263
+ * import { parseHexColor } from "@pantoken/utils";
264
+ *
265
+ * parseHexColor("#fff"); // → { r: 255, g: 255, b: 255, a: 1 }
266
+ * parseHexColor("#0374B5"); // → { r: 3, g: 116, b: 181, a: 1 }
267
+ * parseHexColor("#00000080"); // → { r: 0, g: 0, b: 0, a: 0.5019… }
268
+ * parseHexColor("nope"); // → undefined
269
+ * ```
270
+ */
271
+ declare function parseHexColor(hex: string): Rgba | undefined;
272
+ //#endregion
273
+ //#region src/svg.d.ts
274
+ /**
275
+ * SVG sanitization for glyphs decoded from vendored data URIs or contributed by plugins.
276
+ *
277
+ * @module
278
+ */
279
+ /**
280
+ * Strip `<script>` elements and event-handler attributes from SVG markup.
281
+ *
282
+ * Defense-in-depth for SVG decoded from vendored data URIs or contributed by plugins.
283
+ * Not a full HTML parser — relies on the upstream source being trusted and pinned.
284
+ */
285
+ declare function sanitizeSvg(svg: string): string;
286
+ //#endregion
287
+ //#region src/spacing.d.ts
288
+ /**
289
+ * The pantoken spacing scale — the single shared source for `@pantoken/components`' spacing/gap
290
+ * utilities and `@pantoken/interactions`' `resolveSpace`, so the two never drift apart.
291
+ *
292
+ * @module
293
+ */
294
+ /** One entry of the pantoken spacing scale: the short key, the long (word) key, and the shared CSS value. */
295
+ interface SpacingStep {
296
+ /** The short scale key, e.g. `"sm"` (the zero step's short key is `"0"`). */
297
+ short: string;
298
+ /** The long, word-spelled key, e.g. `"small"` (the zero step's long key is `"none"`). */
299
+ long: string;
300
+ /** The CSS value — a token reference, or a plain `"0"`/`"auto"`. */
301
+ value: string;
302
+ }
303
+ /**
304
+ * The pantoken spacing scale, `none`/`0` → `2xl` — the single shared source for
305
+ * `@pantoken/components`' spacing/gap utilities and `@pantoken/interactions`' `resolveSpace`, so the
306
+ * two never drift apart.
307
+ */
308
+ declare const SPACING_STEPS: readonly SpacingStep[];
309
+ /** The `auto` step — margin only, spelled the same both ways. */
310
+ declare const SPACING_AUTO_STEP: SpacingStep;
311
+ //#endregion
312
+ //#region src/utility-css.d.ts
313
+ /**
314
+ * Token → utility-class emitters. Pure string transforms (token name → `.class { prop: var(--token) }`),
315
+ * with no InstUI-look opinion — the caller supplies which token names to render. `@pantoken/components`
316
+ * feeds them a curated *semantic* allowlist; `@pantoken/plugin-primitives` feeds the raw palette. They
317
+ * live here (not in a format) so the primitive tier doesn't have to reach up into the component library.
318
+ *
319
+ * @module
320
+ */
280
321
  /** Options shared by the utility-class emitters. */
281
322
  interface UtilityOptions {
282
323
  /**
@@ -284,18 +325,30 @@ interface UtilityOptions {
284
325
  * falsy value (`null`/`undefined`/`""`) drops the prefix entirely (`.bg-…`).
285
326
  */
286
327
  prefix?: string | null;
328
+ /**
329
+ * Component base-class names (e.g. `"button"`, `"view"`) to also emit a chained alias for, per
330
+ * rule — `.<prefix>-bg-danger` (bare) plus `.<prefix>-button.-bg-danger` (chained), so the same
331
+ * modifier works standalone or attached to any component. Omit for bare-only output (unchanged
332
+ * behavior).
333
+ */
334
+ chainTargets?: readonly string[];
287
335
  }
288
- /** The semantic colour token names (without the `--instui-color-<family>-` prefix) per family. */
336
+ /** One semantic colour family entry: a bare name (resolved against `--instui-color-<family>-<name>`)
337
+ * or an explicit `[name, value]` pair — the second element is `var()`-wrapped if it's a `--custom-prop`
338
+ * name, or used verbatim otherwise (a `light-dark(…)` expression, a raw keyword like `transparent`). */
339
+ type ColorUtilityEntry = string | readonly [name: string, value: string];
340
+ /** The semantic colour token names (or explicit `[name, token]` pairs) per family. */
289
341
  interface ColorUtilityNames {
290
- background: readonly string[];
291
- text: readonly string[];
292
- stroke: readonly string[];
342
+ background: readonly ColorUtilityEntry[];
343
+ text: readonly ColorUtilityEntry[];
344
+ stroke: readonly ColorUtilityEntry[];
293
345
  }
294
346
  /**
295
347
  * Build the semantic-colour utility stylesheet: `.<prefix>-bg-<name>` (background),
296
- * `.<prefix>-fg-<name>` (text colour), `.<prefix>-border-<name>` (border colour), one per semantic
348
+ * `.<prefix>-text-<name>` (text colour), `.<prefix>-border-<name>` (border colour), one per semantic
297
349
  * colour token. Overrides are therefore only ever token-valid — no primitives, no arbitrary hex. Pass
298
- * the token names per family (e.g. from `@pantoken/tokens`).
350
+ * the token names per family (e.g. from `@pantoken/tokens`), or an explicit `[name, token]` pair to
351
+ * source a name from a different token than the family's own scale.
299
352
  *
300
353
  * @param names - {@link ColorUtilityNames}.
301
354
  * @param options - {@link UtilityOptions}.
@@ -328,4 +381,4 @@ interface TokenUtilityGroup {
328
381
  */
329
382
  declare function tokenUtilitiesCss(groups: readonly TokenUtilityGroup[], options?: UtilityOptions): string;
330
383
  //#endregion
331
- export { ColorUtilityNames, ELEVATION_NAMES, FOCUSABLE_SELECTOR, LIGHT_DARK_RE, Mode, ResolveOptions, Rgba, SPACING_AUTO_STEP, SPACING_STEPS, SpacingStep, TokenUtilityGroup, UtilityOptions, VAR_RE, camelCase, colorUtilitiesCss, danglingReferences, elevationDeclarations, extractInstuiRefs, focusOutlineDeclarations, focusOutlineRules, makeResolver, parseHexColor, resolveTokens, sanitizeSvg, tokenNames, tokenUtilitiesCss, unknownReferences };
384
+ export { type ColorUtilityEntry, type ColorUtilityNames, ELEVATION_NAMES, FOCUSABLE_SELECTOR, LIGHT_DARK_RE, type Mode, type ResolveOptions, type Rgba, SPACING_AUTO_STEP, SPACING_STEPS, type SpacingStep, type TokenUtilityGroup, type UtilityOptions, VAR_RE, camelCase, colorUtilitiesCss, danglingReferences, elevationDeclarations, extractInstuiRefs, focusOutlineDeclarations, focusOutlineRules, makeResolver, parseHexColor, resolveTokens, sanitizeSvg, tokenNames, tokenUtilitiesCss, unknownReferences };
package/dist/index.mjs CHANGED
@@ -117,91 +117,13 @@ function focusOutlineRules(selector = FOCUSABLE_SELECTOR) {
117
117
  ].join("\n");
118
118
  }
119
119
  //#endregion
120
- //#region src/index.ts
120
+ //#region src/resolver.ts
121
121
  /**
122
- * `@pantoken/utils` — shared, upstream-free helpers used across the pantoken packages: the token
123
- * reference resolver (with `light-dark()` handling), the two token regexes (typed via `arkregex`),
124
- * kebab→camel case, hex-colour parsing, the shared spacing scale ({@link SPACING_STEPS}), and the
125
- * generic token→utility-class emitters ({@link colorUtilitiesCss}, {@link tokenUtilitiesCss}) that
126
- * both `@pantoken/components` (semantic tier) and `@pantoken/plugin-primitives` (raw-palette tier)
127
- * build on. Depends only on `@pantoken/model` (types) + `arkregex`, so any package can use it without
128
- * pulling the GitHub-only upstream.
122
+ * The token reference resolver: expands `var(--x)` chains to concrete leaf values, with optional
123
+ * `light-dark()` collapsing.
129
124
  *
130
125
  * @module
131
- * @beta
132
126
  */
133
- /**
134
- * The pantoken spacing scale, `none`/`0` → `2xl` — the single shared source for
135
- * `@pantoken/components`' spacing/gap utilities and `@pantoken/interactions`' `resolveSpace`, so the
136
- * two never drift apart.
137
- */
138
- const SPACING_STEPS = [
139
- {
140
- short: "0",
141
- long: "none",
142
- value: "var(--instui-component-shared-tokens-spacing-general-space-none)"
143
- },
144
- {
145
- short: "2xs",
146
- long: "xx-small",
147
- value: "var(--instui-spacing-space2xs)"
148
- },
149
- {
150
- short: "xs",
151
- long: "x-small",
152
- value: "var(--instui-spacing-space-xs)"
153
- },
154
- {
155
- short: "sm",
156
- long: "small",
157
- value: "var(--instui-spacing-space-sm)"
158
- },
159
- {
160
- short: "md",
161
- long: "medium",
162
- value: "var(--instui-spacing-space-md)"
163
- },
164
- {
165
- short: "lg",
166
- long: "large",
167
- value: "var(--instui-spacing-space-lg)"
168
- },
169
- {
170
- short: "xl",
171
- long: "x-large",
172
- value: "var(--instui-spacing-space-xl)"
173
- },
174
- {
175
- short: "2xl",
176
- long: "xx-large",
177
- value: "var(--instui-spacing-space2xl)"
178
- }
179
- ];
180
- /** The `auto` step — margin only, spelled the same both ways. */
181
- const SPACING_AUTO_STEP = {
182
- short: "auto",
183
- long: "auto",
184
- value: "auto"
185
- };
186
- /**
187
- * Strip `<script>` elements and event-handler attributes from SVG markup.
188
- *
189
- * Defense-in-depth for SVG decoded from vendored data URIs or contributed by plugins.
190
- * Not a full HTML parser — relies on the upstream source being trusted and pinned.
191
- */
192
- function sanitizeSvg(svg) {
193
- let sanitized = svg;
194
- let previous;
195
- do {
196
- previous = sanitized;
197
- sanitized = sanitized.replace(/<script\b[^>]*>[\s\S]*?<\/script\b[^>]*>/gi, "");
198
- } while (sanitized !== previous);
199
- do {
200
- previous = sanitized;
201
- sanitized = sanitized.replace(/\son\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "");
202
- } while (sanitized !== previous);
203
- return sanitized;
204
- }
205
127
  const VAR_SOURCE = "var\\(\\s*(--[\\w-]+)[^)]*\\)";
206
128
  const LIGHT_DARK_SOURCE = "^light-dark\\(\\s*(.+?)\\s*,\\s*(.+?)\\s*\\)$";
207
129
  /** A `var(--custom-prop)` reference (optional fallback); capture group 1 is the property name. */
@@ -303,44 +225,8 @@ function resolveTokens(base, options = {}) {
303
225
  const resolve = makeResolver(base, options);
304
226
  return new Map(base.map((t) => [t.name, resolve(t.value)]));
305
227
  }
306
- /**
307
- * Convert a kebab-case string to camelCase (`color-background-brand` → `colorBackgroundBrand`).
308
- *
309
- * @example
310
- * ```ts
311
- * import { camelCase } from "@pantoken/utils";
312
- *
313
- * camelCase("color-background-brand"); // → "colorBackgroundBrand"
314
- * ```
315
- */
316
- function camelCase(kebab) {
317
- return kebab.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
318
- }
319
- /**
320
- * Parse `#rgb`, `#rrggbb`, or `#rrggbbaa` to {@link Rgba}; returns `undefined` otherwise.
321
- *
322
- * @example
323
- * ```ts
324
- * import { parseHexColor } from "@pantoken/utils";
325
- *
326
- * parseHexColor("#fff"); // → { r: 255, g: 255, b: 255, a: 1 }
327
- * parseHexColor("#0374B5"); // → { r: 3, g: 116, b: 181, a: 1 }
328
- * parseHexColor("#00000080"); // → { r: 0, g: 0, b: 0, a: 0.5019… }
329
- * parseHexColor("nope"); // → undefined
330
- * ```
331
- */
332
- function parseHexColor(hex) {
333
- const m = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(hex.trim());
334
- if (!m) return void 0;
335
- let h = m[1];
336
- if (h.length === 3) h = h.replace(/(.)/g, "$1$1");
337
- return {
338
- r: Number.parseInt(h.slice(0, 2), 16),
339
- g: Number.parseInt(h.slice(2, 4), 16),
340
- b: Number.parseInt(h.slice(4, 6), 16),
341
- a: h.length === 8 ? Number.parseInt(h.slice(6, 8), 16) / 255 : 1
342
- };
343
- }
228
+ //#endregion
229
+ //#region src/drift.ts
344
230
  /**
345
231
  * Every `--instui-*` custom-property name that appears anywhere in `text`.
346
232
  *
@@ -427,11 +313,142 @@ function danglingReferences(css) {
427
313
  for (const m of css.matchAll(/(--instui-[\w]+(?:-[\w]+)*)\s*:/g)) defined.add(m[1]);
428
314
  return [...referenced].filter((name) => !defined.has(name)).sort();
429
315
  }
316
+ //#endregion
317
+ //#region src/case.ts
318
+ /**
319
+ * Convert a kebab-case string to camelCase (`color-background-brand` → `colorBackgroundBrand`).
320
+ *
321
+ * @module
322
+ */
323
+ /**
324
+ * Convert a kebab-case string to camelCase (`color-background-brand` → `colorBackgroundBrand`).
325
+ *
326
+ * @example
327
+ * ```ts
328
+ * import { camelCase } from "@pantoken/utils";
329
+ *
330
+ * camelCase("color-background-brand"); // → "colorBackgroundBrand"
331
+ * ```
332
+ */
333
+ function camelCase(kebab) {
334
+ return kebab.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
335
+ }
336
+ //#endregion
337
+ //#region src/color.ts
338
+ /**
339
+ * Parse `#rgb`, `#rrggbb`, or `#rrggbbaa` to {@link Rgba}; returns `undefined` otherwise.
340
+ *
341
+ * @example
342
+ * ```ts
343
+ * import { parseHexColor } from "@pantoken/utils";
344
+ *
345
+ * parseHexColor("#fff"); // → { r: 255, g: 255, b: 255, a: 1 }
346
+ * parseHexColor("#0374B5"); // → { r: 3, g: 116, b: 181, a: 1 }
347
+ * parseHexColor("#00000080"); // → { r: 0, g: 0, b: 0, a: 0.5019… }
348
+ * parseHexColor("nope"); // → undefined
349
+ * ```
350
+ */
351
+ function parseHexColor(hex) {
352
+ const m = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(hex.trim());
353
+ if (!m) return void 0;
354
+ let h = m[1];
355
+ if (h.length === 3) h = h.replace(/(.)/g, "$1$1");
356
+ return {
357
+ r: Number.parseInt(h.slice(0, 2), 16),
358
+ g: Number.parseInt(h.slice(2, 4), 16),
359
+ b: Number.parseInt(h.slice(4, 6), 16),
360
+ a: h.length === 8 ? Number.parseInt(h.slice(6, 8), 16) / 255 : 1
361
+ };
362
+ }
363
+ //#endregion
364
+ //#region src/svg.ts
365
+ /**
366
+ * SVG sanitization for glyphs decoded from vendored data URIs or contributed by plugins.
367
+ *
368
+ * @module
369
+ */
370
+ /**
371
+ * Strip `<script>` elements and event-handler attributes from SVG markup.
372
+ *
373
+ * Defense-in-depth for SVG decoded from vendored data URIs or contributed by plugins.
374
+ * Not a full HTML parser — relies on the upstream source being trusted and pinned.
375
+ */
376
+ function sanitizeSvg(svg) {
377
+ let sanitized = svg;
378
+ let previous;
379
+ do {
380
+ previous = sanitized;
381
+ sanitized = sanitized.replace(/<script\b[^>]*>[\s\S]*?<\/script\b[^>]*>/gi, "");
382
+ } while (sanitized !== previous);
383
+ do {
384
+ previous = sanitized;
385
+ sanitized = sanitized.replace(/\son\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "");
386
+ } while (sanitized !== previous);
387
+ return sanitized;
388
+ }
389
+ //#endregion
390
+ //#region src/spacing.ts
391
+ /**
392
+ * The pantoken spacing scale, `none`/`0` → `2xl` — the single shared source for
393
+ * `@pantoken/components`' spacing/gap utilities and `@pantoken/interactions`' `resolveSpace`, so the
394
+ * two never drift apart.
395
+ */
396
+ const SPACING_STEPS = [
397
+ {
398
+ short: "0",
399
+ long: "none",
400
+ value: "var(--instui-component-shared-tokens-spacing-general-space-none)"
401
+ },
402
+ {
403
+ short: "2xs",
404
+ long: "xx-small",
405
+ value: "var(--instui-spacing-space2xs)"
406
+ },
407
+ {
408
+ short: "xs",
409
+ long: "x-small",
410
+ value: "var(--instui-spacing-space-xs)"
411
+ },
412
+ {
413
+ short: "sm",
414
+ long: "small",
415
+ value: "var(--instui-spacing-space-sm)"
416
+ },
417
+ {
418
+ short: "md",
419
+ long: "medium",
420
+ value: "var(--instui-spacing-space-md)"
421
+ },
422
+ {
423
+ short: "lg",
424
+ long: "large",
425
+ value: "var(--instui-spacing-space-lg)"
426
+ },
427
+ {
428
+ short: "xl",
429
+ long: "x-large",
430
+ value: "var(--instui-spacing-space-xl)"
431
+ },
432
+ {
433
+ short: "2xl",
434
+ long: "xx-large",
435
+ value: "var(--instui-spacing-space2xl)"
436
+ }
437
+ ];
438
+ /** The `auto` step — margin only, spelled the same both ways. */
439
+ const SPACING_AUTO_STEP = {
440
+ short: "auto",
441
+ long: "auto",
442
+ value: "auto"
443
+ };
444
+ //#endregion
445
+ //#region src/utility-css.ts
430
446
  /**
431
447
  * Build the semantic-colour utility stylesheet: `.<prefix>-bg-<name>` (background),
432
- * `.<prefix>-fg-<name>` (text colour), `.<prefix>-border-<name>` (border colour), one per semantic
448
+ * `.<prefix>-text-<name>` (text colour), `.<prefix>-border-<name>` (border colour), one per semantic
433
449
  * colour token. Overrides are therefore only ever token-valid — no primitives, no arbitrary hex. Pass
434
- * the token names per family (e.g. from `@pantoken/tokens`).
450
+ * the token names per family (e.g. from `@pantoken/tokens`), or an explicit `[name, token]` pair to
451
+ * source a name from a different token than the family's own scale.
435
452
  *
436
453
  * @param names - {@link ColorUtilityNames}.
437
454
  * @param options - {@link UtilityOptions}.
@@ -442,10 +459,17 @@ function danglingReferences(css) {
442
459
  function colorUtilitiesCss(names, options = {}) {
443
460
  const prefix = options.prefix || "";
444
461
  const p = prefix ? `${prefix}-` : "";
445
- const emit = (family, util, prop, list) => list.map((n) => `.${p}${util}-${n} { ${prop}: var(--instui-color-${family}-${n}); }`).join("\n");
462
+ const chainTargets = options.chainTargets ?? [];
463
+ const emit = (family, util, prop, list) => list.map((entry) => {
464
+ const isPair = (e) => typeof e !== "string";
465
+ const [n, token] = isPair(entry) ? entry : [entry, `--instui-color-${family}-${entry}`];
466
+ const value = token.startsWith("--") ? `var(${token})` : token;
467
+ const modifier = `.-${util}-${n}`;
468
+ return `${[`.${p}${util}-${n}`, ...chainTargets.map((target) => `.${p}${target}${modifier}`)].join(", ")} { ${prop}: ${value}; }`;
469
+ }).join("\n");
446
470
  return `/* InstUI semantic colour utilities (@pantoken/utils) — prefix: ${prefix} */\n${[
447
471
  emit("background", "bg", "background", names.background),
448
- emit("text", "fg", "color", names.text),
472
+ emit("text", "text", "color", names.text),
449
473
  emit("stroke", "border", "border-color", names.stroke)
450
474
  ].join("\n")}\n`;
451
475
  }
@@ -467,9 +491,11 @@ function colorUtilitiesCss(names, options = {}) {
467
491
  function tokenUtilitiesCss(groups, options = {}) {
468
492
  const prefix = options.prefix || "";
469
493
  const p = prefix ? `${prefix}-` : "";
494
+ const chainTargets = options.chainTargets ?? [];
470
495
  return `/* InstUI token utilities (@pantoken/utils) — prefix: ${prefix} */\n${groups.flatMap(({ property, tokens }) => tokens.map((token) => {
471
496
  const name = token.replace(/^--instui-/, "");
472
- return `.${p}${name} { ${property}: var(${token}); }`;
497
+ const modifier = `.-${name}`;
498
+ return `${[`.${p}${name}`, ...chainTargets.map((target) => `.${p}${target}${modifier}`)].join(", ")} { ${property}: var(${token}); }`;
473
499
  })).join("\n")}\n`;
474
500
  }
475
501
  //#endregion
@@ -0,0 +1,119 @@
1
+ import { Token } from "@pantoken/model";
2
+ //#region src/token-syntax.d.ts
3
+ /** A registered, real CSS property name (e.g. `"font-weight"`), matched via `matchProperty()`. */
4
+ type PropertyName = string & {
5
+ readonly __brand: "PropertyName";
6
+ };
7
+ /**
8
+ * A CSS Value Definition Syntax grammar string (CSS Values 4 §2.1: `|` `||` `&&` `[]` `?` `{a,b}`
9
+ * `#`, e.g. `"<length>"` or `"none | <shadow>#"`), matched via `match()`.
10
+ */
11
+ type Grammar = string & {
12
+ readonly __brand: "Grammar";
13
+ };
14
+ /** An ordered token-name-pattern → value table, checked first-match-wins via {@link firstMatch}. */
15
+ type SyntaxRule<T> = readonly (readonly [RegExp, T])[];
16
+ /**
17
+ * Non-standard property grammars `css-tree`/`mdn-data` don't know about —
18
+ * matched by token-name substring, most-specific first, as raw CSS Value Definition Syntax
19
+ * (CSS Values 4 §2.1: `|` `||` `&&` `[]` `?` `{a,b}` `#`) rather than a real property name. Only
20
+ * `<...>` references to types `mdn-data` registers as standalone generic types belong here (e.g.
21
+ * NOT `<font-family-name>`, which only exists inline inside the real `font-family` property's own
22
+ * definition — that's matched via `TOKEN_NAME_TO_PROPERTY` instead).
23
+ */
24
+ declare const BESPOKE_SYNTAX: SyntaxRule<Grammar>;
25
+ /**
26
+ * Real CSS properties, matched by token-name substring — the property name to validate against
27
+ * via `css-tree`'s built-in (`mdn-data`-sourced) lexer. Intentionally conservative: a name pattern
28
+ * not listed here is left unmodeled rather than guessed at, since a wrong mapping could fail the
29
+ * build on a legitimate value. Every entry here was verified with {@link candidatePropertyCoverage}
30
+ * against the real IR (all themes) before being added.
31
+ *
32
+ * Order matters: `color` is LAST and end-anchored (`color$`, not a bare `/color/` substring)
33
+ * because several UNRELATED components merely have "color" in their own name (`ColorPicker`,
34
+ * `ColorContrast`, `ColorIndicator`, `ColorPreset`) with an unrelated leaf property afterwards —
35
+ * e.g. `--instui-component-color-picker-hash-mark-container-left-padding` (a `padding`, caught
36
+ * earlier) or `--instui-component-color-indicator-background-size` (a `background-size`, not
37
+ * modeled at all — left `unmodeled` rather than misrouted to `color`). Anchoring to the literal
38
+ * end trades some coverage (a few genuine `color-<role>` tokens fall to `unmodeled`) for zero
39
+ * false failures, verified empirically: a bare `/color/` matched 937 tokens with 62 invalid, an
40
+ * end-anchored `/color$/` matched only 64 but 0 real invalids (its few reported "invalids" were
41
+ * icon glyphs already resolved by the bespoke `--instui-icon-` pattern above, which runs first).
42
+ * `opacity` excludes a digit suffix so a colour-with-baked-in-alpha primitive
43
+ * (`--instui-primitive-color-grey-opacity10`, a colour, not a CSS `opacity` value) isn't misrouted
44
+ * either way (`primitive-opacity\d+$` below is a separate, narrower pattern for the genuine
45
+ * standalone opacity primitives, e.g. `--instui-primitive-opacity50`). `gap` was tried and rejected
46
+ * naively (it also matches `--instui-icon-megaphone`), but that collision never actually reaches
47
+ * here in practice — the bespoke icon-glyph rule above already claims every `instui-icon-*` name
48
+ * first, so `gap` IS included, verified safe once that precedence is accounted for.
49
+ */
50
+ declare const TOKEN_NAME_TO_PROPERTY: SyntaxRule<PropertyName>;
51
+ /** A `namePattern`'s concrete, non-contextual matches against a candidate property, from the real IR. */
52
+ interface PatternCoverage {
53
+ /** How many concrete (non-contextual) tokens matched `namePattern`. */
54
+ matched: number;
55
+ /** The matches that would FAIL the candidate property's real CSS grammar. */
56
+ invalid: readonly {
57
+ name: string;
58
+ value: string;
59
+ }[];
60
+ }
61
+ /**
62
+ * Empirically checks a candidate `TOKEN_NAME_TO_PROPERTY` entry against the real IR before it's
63
+ * added, rather than guessing: does every concrete value whose name matches `namePattern` actually
64
+ * validate against `candidateProperty`'s real CSS grammar? (e.g. "every
65
+ * `--instui-primitive-color-*-opacity\d` token is a valid `<color>`.") `matched === 0` means the
66
+ * pattern hit nothing in this IR — not evidence of safety either way. Contextual (`var()`/
67
+ * `light-dark()`) values are skipped, same as {@link isSyntaxValid}.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { candidatePropertyCoverage } from "@pantoken/utils";
72
+ * import { tokens } from "@pantoken/tokens";
73
+ *
74
+ * const coverage = candidatePropertyCoverage(tokens, /-opacity\d/u, "color");
75
+ * coverage.invalid; // → [] once every matching token is confirmed to be a valid <color>
76
+ * ```
77
+ */
78
+ declare function candidatePropertyCoverage(tokens: readonly {
79
+ name: string;
80
+ value: string;
81
+ }[], namePattern: RegExp, candidateProperty: string): PatternCoverage;
82
+ /** One token whose value doesn't satisfy its property's CSS grammar, or whose name maps to none. */
83
+ interface TokenSyntaxIssue {
84
+ /** The token's `--instui-*` name. */
85
+ name: string;
86
+ /** The token's resolved value. */
87
+ value: string;
88
+ /** `"mismatch"` fails a known property's real CSS grammar; `"unmodeled"` maps to no known property. */
89
+ kind: "mismatch" | "unmodeled";
90
+ }
91
+ /**
92
+ * Type-integrity check: every concrete token value validated against the REAL CSS grammar for the
93
+ * property its name implies (via `css-tree`'s `mdn-data`-backed lexer, plus the shared
94
+ * `BESPOKE_SYNTAX` table for pantoken-only properties) — catches upstream data corruption (e.g. a
95
+ * `font-weight` token whose value ends up as a non-numeric string) that a value-shape sniff alone
96
+ * would silently accept. Contextual values (`var()` / `light-dark()`) are skipped: their real type
97
+ * lives at the reference target. Sorted by name; empty means every modeled token is valid.
98
+ *
99
+ * @param tokens - The IR.
100
+ * @returns Tokens that fail their property's grammar (`"mismatch"`) or map to no known property
101
+ * (`"unmodeled"`).
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * import { syntaxMismatches } from "@pantoken/utils";
106
+ * import type { Token } from "@pantoken/model";
107
+ *
108
+ * const ir: Token[] = [
109
+ * { name: "--instui-font-weight-base", syntax: "<integer>", inherits: true, value: "400" },
110
+ * { name: "--instui-content-quote-font-weight", syntax: "*", inherits: true, value: "Medium Italic" },
111
+ * ];
112
+ *
113
+ * syntaxMismatches(ir);
114
+ * // → [{ name: "--instui-content-quote-font-weight", value: "Medium Italic", kind: "mismatch" }]
115
+ * ```
116
+ */
117
+ declare function syntaxMismatches(tokens: readonly Token[]): TokenSyntaxIssue[];
118
+ //#endregion
119
+ export { BESPOKE_SYNTAX, Grammar, PatternCoverage, PropertyName, SyntaxRule, TOKEN_NAME_TO_PROPERTY, TokenSyntaxIssue, candidatePropertyCoverage, syntaxMismatches };
@@ -0,0 +1,256 @@
1
+ import * as csstree from "css-tree";
2
+ //#region src/token-syntax.ts
3
+ /**
4
+ * Validates a resolved token value against the REAL CSS grammar for the property its name
5
+ * implies, using `css-tree`'s spec-driven lexer (backed by `mdn-data`, the same W3C-spec dataset
6
+ * browsers/`stylelint` use) as the authoritative source — not a hand-rolled value-shape sniffer.
7
+ * `BESPOKE_SYNTAX` is the one shared table of pantoken-invented, non-standard property grammars
8
+ * (also consumed by `docs/scripts/build-css-api.ts` for human-readable type display), so the docs
9
+ * and this validator can never drift apart.
10
+ *
11
+ * @module
12
+ */
13
+ /** The registered `css-tree` property descriptor for `name`, or throws — never silently unmatched. */
14
+ function requireProperty(name) {
15
+ const property = csstree.lexer.getProperty(name);
16
+ if (!property) throw new Error(`css-tree has no registered property named "${name}"`);
17
+ return property;
18
+ }
19
+ /**
20
+ * The REAL CSS grammar string for a property, generated from `css-tree`'s own lexer (the same
21
+ * instance {@link isSyntaxValid} matches against) rather than hand-typed — guaranteed byte-identical
22
+ * to what the lexer actually validates, so it can never drift out of sync with itself.
23
+ */
24
+ function propertyGrammar(name) {
25
+ const syntax = requireProperty(name).syntax;
26
+ if (!syntax) throw new Error(`css-tree property "${name}" has no generatable grammar`);
27
+ return csstree.definitionSyntax.generate(syntax);
28
+ }
29
+ /**
30
+ * A real CSS property name for {@link TOKEN_NAME_TO_PROPERTY}, validated against `css-tree`'s
31
+ * registry at module load — the same fail-fast safety net as {@link propertyGrammar}, so a typo'd
32
+ * property name throws immediately with a clear message instead of silently flagging every
33
+ * matching token as a mismatch at build time.
34
+ */
35
+ function realProperty(name) {
36
+ requireProperty(name);
37
+ return name;
38
+ }
39
+ /**
40
+ * A `<type>` reference for {@link BESPOKE_SYNTAX}, validated against `css-tree`'s registry at
41
+ * module load — the same fail-fast safety net as {@link realProperty}, for the terminal/generic
42
+ * types (`length`, `color`, `url`, `time`, …) that don't belong to any single real property.
43
+ */
44
+ function typeReference(name) {
45
+ if (!csstree.lexer.getType(name)) throw new Error(`css-tree has no registered type named "${name}"`);
46
+ return `<${name}>`;
47
+ }
48
+ /**
49
+ * Non-standard property grammars `css-tree`/`mdn-data` don't know about —
50
+ * matched by token-name substring, most-specific first, as raw CSS Value Definition Syntax
51
+ * (CSS Values 4 §2.1: `|` `||` `&&` `[]` `?` `{a,b}` `#`) rather than a real property name. Only
52
+ * `<...>` references to types `mdn-data` registers as standalone generic types belong here (e.g.
53
+ * NOT `<font-family-name>`, which only exists inline inside the real `font-family` property's own
54
+ * definition — that's matched via `TOKEN_NAME_TO_PROPERTY` instead).
55
+ */
56
+ const BESPOKE_SYNTAX = [
57
+ [/drop-shadow-(blur|spread|x|y)-/u, typeReference("length")],
58
+ [/elevation/u, propertyGrammar("box-shadow")],
59
+ [/text-decoration/u, propertyGrammar("text-decoration-line")],
60
+ [/focus-outline-color/u, propertyGrammar("outline-color")],
61
+ [/focus-outline-style/u, propertyGrammar("outline-style")],
62
+ [/focus-outline-width/u, propertyGrammar("outline-width")],
63
+ [/focus-outline-offset/u, propertyGrammar("outline-offset")],
64
+ [/focus-outline-radius/u, propertyGrammar("border-radius")],
65
+ [/--instui-icon-color-/u, typeReference("color")],
66
+ [/glyph|instui-icon/u, typeReference("url")],
67
+ [/-filter\b/u, propertyGrammar("filter")],
68
+ [/box-shadow-(blur|spread|x|y)$/u, typeReference("length")],
69
+ [/box-shadow-color$/u, typeReference("color")],
70
+ [/(?<!background)-size(?:-|\d|$)/u, typeReference("length")],
71
+ [/(?<!letter)-spacing/u, typeReference("length")],
72
+ [/offset$/u, typeReference("length")],
73
+ [/thickness$/u, typeReference("length")],
74
+ [/circumference$/u, typeReference("length")],
75
+ [/(?:transition-timing)|(?:toggle-transition$)/u, typeReference("time")],
76
+ [/(?:breakpoints-)|(?:byline-(?:large|medium|small)$)/u, typeReference("length")],
77
+ [/progress-circle-.*-transform$/u, typeReference("length")],
78
+ [/(?:icon-illu-)|(?:img-image-blur-amount$)/u, typeReference("length")],
79
+ [/tray-fix-top-position$/u, typeReference("length")]
80
+ ];
81
+ /**
82
+ * Real CSS properties, matched by token-name substring — the property name to validate against
83
+ * via `css-tree`'s built-in (`mdn-data`-sourced) lexer. Intentionally conservative: a name pattern
84
+ * not listed here is left unmodeled rather than guessed at, since a wrong mapping could fail the
85
+ * build on a legitimate value. Every entry here was verified with {@link candidatePropertyCoverage}
86
+ * against the real IR (all themes) before being added.
87
+ *
88
+ * Order matters: `color` is LAST and end-anchored (`color$`, not a bare `/color/` substring)
89
+ * because several UNRELATED components merely have "color" in their own name (`ColorPicker`,
90
+ * `ColorContrast`, `ColorIndicator`, `ColorPreset`) with an unrelated leaf property afterwards —
91
+ * e.g. `--instui-component-color-picker-hash-mark-container-left-padding` (a `padding`, caught
92
+ * earlier) or `--instui-component-color-indicator-background-size` (a `background-size`, not
93
+ * modeled at all — left `unmodeled` rather than misrouted to `color`). Anchoring to the literal
94
+ * end trades some coverage (a few genuine `color-<role>` tokens fall to `unmodeled`) for zero
95
+ * false failures, verified empirically: a bare `/color/` matched 937 tokens with 62 invalid, an
96
+ * end-anchored `/color$/` matched only 64 but 0 real invalids (its few reported "invalids" were
97
+ * icon glyphs already resolved by the bespoke `--instui-icon-` pattern above, which runs first).
98
+ * `opacity` excludes a digit suffix so a colour-with-baked-in-alpha primitive
99
+ * (`--instui-primitive-color-grey-opacity10`, a colour, not a CSS `opacity` value) isn't misrouted
100
+ * either way (`primitive-opacity\d+$` below is a separate, narrower pattern for the genuine
101
+ * standalone opacity primitives, e.g. `--instui-primitive-opacity50`). `gap` was tried and rejected
102
+ * naively (it also matches `--instui-icon-megaphone`), but that collision never actually reaches
103
+ * here in practice — the bespoke icon-glyph rule above already claims every `instui-icon-*` name
104
+ * first, so `gap` IS included, verified safe once that precedence is accounted for.
105
+ */
106
+ const TOKEN_NAME_TO_PROPERTY = [
107
+ [/font-family/u, realProperty("font-family")],
108
+ [/font-weight/u, realProperty("font-weight")],
109
+ [/font-style/u, realProperty("font-style")],
110
+ [/font-size/u, realProperty("font-size")],
111
+ [/line-height/u, realProperty("line-height")],
112
+ [/letter-spacing/u, realProperty("letter-spacing")],
113
+ [/opacity(?!\d)/u, realProperty("opacity")],
114
+ [/primitive-opacity\d+$/u, realProperty("opacity")],
115
+ [/z-index|stacking/u, realProperty("z-index")],
116
+ [/transition-duration|-duration\b/u, realProperty("transition-duration")],
117
+ [/border-width/u, realProperty("border-width")],
118
+ [/-radius/u, realProperty("border-radius")],
119
+ [/border-style/u, realProperty("border-style")],
120
+ [/bottom-border/u, realProperty("border-bottom")],
121
+ [/box-shadow/u, realProperty("box-shadow")],
122
+ [/margin/u, realProperty("margin")],
123
+ [/padding/u, realProperty("padding")],
124
+ [/height/u, realProperty("height")],
125
+ [/width/u, realProperty("width")],
126
+ [/gap/u, realProperty("gap")],
127
+ [/inset/u, realProperty("inset")],
128
+ [/overflow-x$/u, realProperty("overflow-x")],
129
+ [/overflow-y$/u, realProperty("overflow-y")],
130
+ [/text-transform$/u, realProperty("text-transform")],
131
+ [/base-button-(hover-)?transform$/u, realProperty("transform")],
132
+ [/background-image$/u, realProperty("background-image")],
133
+ [/background-position$/u, realProperty("background-position")],
134
+ [/background-size$/u, realProperty("background-size")],
135
+ [/background$/u, realProperty("background")],
136
+ [/border-color/u, realProperty("border-color")],
137
+ [/primitive-color-/u, realProperty("color")],
138
+ [/color(?:\d*|-inverse)$/u, realProperty("color")],
139
+ [/position$/u, realProperty("position")]
140
+ ];
141
+ function firstMatch(name, table) {
142
+ return table.find(([re]) => re.test(name))?.[1];
143
+ }
144
+ /** True when a value cannot be a typed CSS value at all (`var()` / `light-dark()`). */
145
+ function isContextualValue(value) {
146
+ return /var\(|light-dark\(/.test(value);
147
+ }
148
+ /** Whether `name` maps to a modeled property/grammar at all (real or bespoke). */
149
+ function hasModeledSyntax(name) {
150
+ return firstMatch(name, BESPOKE_SYNTAX) !== void 0 || TOKEN_NAME_TO_PROPERTY.some(([re]) => re.test(name));
151
+ }
152
+ /**
153
+ * Whether a concrete value satisfies the real (or bespoke) CSS grammar for `name`'s property.
154
+ * Returns `true` for any name with no modeled expectation — call {@link hasModeledSyntax} first
155
+ * to distinguish "valid" from "not checked".
156
+ */
157
+ function isSyntaxValid(name, value) {
158
+ const bespoke = firstMatch(name, BESPOKE_SYNTAX);
159
+ const property = bespoke ? void 0 : firstMatch(name, TOKEN_NAME_TO_PROPERTY);
160
+ if (!bespoke && !property) return true;
161
+ let ast;
162
+ try {
163
+ ast = csstree.parse(value, { context: "value" });
164
+ } catch {
165
+ return false;
166
+ }
167
+ return !(bespoke ? csstree.lexer.match(bespoke, ast) : csstree.lexer.matchProperty(property, ast)).error;
168
+ }
169
+ /**
170
+ * Empirically checks a candidate `TOKEN_NAME_TO_PROPERTY` entry against the real IR before it's
171
+ * added, rather than guessing: does every concrete value whose name matches `namePattern` actually
172
+ * validate against `candidateProperty`'s real CSS grammar? (e.g. "every
173
+ * `--instui-primitive-color-*-opacity\d` token is a valid `<color>`.") `matched === 0` means the
174
+ * pattern hit nothing in this IR — not evidence of safety either way. Contextual (`var()`/
175
+ * `light-dark()`) values are skipped, same as {@link isSyntaxValid}.
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * import { candidatePropertyCoverage } from "@pantoken/utils";
180
+ * import { tokens } from "@pantoken/tokens";
181
+ *
182
+ * const coverage = candidatePropertyCoverage(tokens, /-opacity\d/u, "color");
183
+ * coverage.invalid; // → [] once every matching token is confirmed to be a valid <color>
184
+ * ```
185
+ */
186
+ function candidatePropertyCoverage(tokens, namePattern, candidateProperty) {
187
+ let matched = 0;
188
+ const invalid = [];
189
+ for (const { name, value } of tokens) {
190
+ if (!namePattern.test(name) || isContextualValue(value)) continue;
191
+ matched++;
192
+ let ast;
193
+ try {
194
+ ast = csstree.parse(value, { context: "value" });
195
+ } catch {
196
+ invalid.push({
197
+ name,
198
+ value
199
+ });
200
+ continue;
201
+ }
202
+ if (csstree.lexer.matchProperty(candidateProperty, ast).error) invalid.push({
203
+ name,
204
+ value
205
+ });
206
+ }
207
+ return {
208
+ matched,
209
+ invalid
210
+ };
211
+ }
212
+ /**
213
+ * Type-integrity check: every concrete token value validated against the REAL CSS grammar for the
214
+ * property its name implies (via `css-tree`'s `mdn-data`-backed lexer, plus the shared
215
+ * `BESPOKE_SYNTAX` table for pantoken-only properties) — catches upstream data corruption (e.g. a
216
+ * `font-weight` token whose value ends up as a non-numeric string) that a value-shape sniff alone
217
+ * would silently accept. Contextual values (`var()` / `light-dark()`) are skipped: their real type
218
+ * lives at the reference target. Sorted by name; empty means every modeled token is valid.
219
+ *
220
+ * @param tokens - The IR.
221
+ * @returns Tokens that fail their property's grammar (`"mismatch"`) or map to no known property
222
+ * (`"unmodeled"`).
223
+ *
224
+ * @example
225
+ * ```ts
226
+ * import { syntaxMismatches } from "@pantoken/utils";
227
+ * import type { Token } from "@pantoken/model";
228
+ *
229
+ * const ir: Token[] = [
230
+ * { name: "--instui-font-weight-base", syntax: "<integer>", inherits: true, value: "400" },
231
+ * { name: "--instui-content-quote-font-weight", syntax: "*", inherits: true, value: "Medium Italic" },
232
+ * ];
233
+ *
234
+ * syntaxMismatches(ir);
235
+ * // → [{ name: "--instui-content-quote-font-weight", value: "Medium Italic", kind: "mismatch" }]
236
+ * ```
237
+ */
238
+ function syntaxMismatches(tokens) {
239
+ const issues = [];
240
+ for (const { name, value } of tokens) {
241
+ if (isContextualValue(value)) continue;
242
+ if (!hasModeledSyntax(name)) issues.push({
243
+ name,
244
+ value,
245
+ kind: "unmodeled"
246
+ });
247
+ else if (!isSyntaxValid(name, value)) issues.push({
248
+ name,
249
+ value,
250
+ kind: "mismatch"
251
+ });
252
+ }
253
+ return issues.sort((a, b) => a.name.localeCompare(b.name));
254
+ }
255
+ //#endregion
256
+ export { BESPOKE_SYNTAX, TOKEN_NAME_TO_PROPERTY, candidatePropertyCoverage, syntaxMismatches };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pantoken/utils",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Shared pantoken helpers: token reference resolver, typed regexes (arkregex), name-case, and hex-colour parsing.",
5
5
  "homepage": "https://pantoken.iywahl.com",
6
6
  "bugs": "https://github.com/thedannywahl/pantoken/issues",
@@ -17,6 +17,7 @@
17
17
  "sideEffects": false,
18
18
  "exports": {
19
19
  ".": "./dist/index.mjs",
20
+ "./token-syntax": "./dist/token-syntax.mjs",
20
21
  "./package.json": "./package.json"
21
22
  },
22
23
  "publishConfig": {
@@ -25,9 +26,11 @@
25
26
  },
26
27
  "dependencies": {
27
28
  "arkregex": "^0.0.8",
29
+ "css-tree": "^3.2.1",
28
30
  "@pantoken/model": "0.3.0"
29
31
  },
30
32
  "devDependencies": {
33
+ "@types/css-tree": "^3.2.0",
31
34
  "@types/node": "^24.13.3",
32
35
  "typescript": "^7.0.2",
33
36
  "vite": "npm:@voidzero-dev/vite-plus-core@0.2.9",