rainbowindex 0.4.1 → 0.5.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.
@@ -12,8 +12,6 @@ import {
12
12
  DEFAULT_EASING,
13
13
  DEFAULT_FLUID,
14
14
  DEFAULT_LEADING,
15
- DEFAULT_ROUNDED,
16
- DEFAULT_ROUNDED_ROOF,
17
15
  DEFAULT_SHADOWS,
18
16
  DEFAULT_SUPERELLIPSE_SCALE,
19
17
  DEFAULT_TEXT,
@@ -42,7 +40,7 @@ import {
42
40
  registerCustomTextSizes,
43
41
  registerCustomUtility,
44
42
  snapshotCompilationContext
45
- } from "./chunk-KRZL4IDK.mjs";
43
+ } from "./chunk-4UKFK2GE.mjs";
46
44
 
47
45
  // src/shared.ts
48
46
  function isRIDebug() {
@@ -1319,7 +1317,14 @@ function stripLeadingClassDot(name) {
1319
1317
  return name.startsWith(".") ? name.slice(1) : name;
1320
1318
  }
1321
1319
  function isValidUtilityName(name, warnings) {
1322
- if (IDENT_KEY_RE.test(name)) return true;
1320
+ if (IDENT_KEY_RE.test(name)) {
1321
+ if (/[A-Z]/.test(name)) {
1322
+ warnings?.push(
1323
+ `[RI-1038] @utility name "${name}" contains uppercase letters \u2014 the markup scanner only matches lowercase tokens, so class="${name}" will never generate it. It still works via @a/@apply and inline @source. Prefer a lowercase-hyphen name.`
1324
+ );
1325
+ }
1326
+ return true;
1327
+ }
1323
1328
  warnings?.push(
1324
1329
  `[RI-1035] Invalid @utility name "${name}" \u2014 names may only contain letters, numbers, hyphens, and underscores (plus an optional trailing "-*" for functional utilities). The utility was skipped.`
1325
1330
  );
@@ -1736,8 +1741,6 @@ function resolveDirectives(directives, attribution) {
1736
1741
  text: { ...DEFAULT_TEXT },
1737
1742
  spacing: { base: "0.25rem" },
1738
1743
  breakpoints: { ...DEFAULT_BREAKPOINTS },
1739
- rounded: { ...DEFAULT_ROUNDED },
1740
- roundedRoof: DEFAULT_ROUNDED_ROOF,
1741
1744
  roundedShape: null,
1742
1745
  roundedShapeScale: 1,
1743
1746
  shadows: { ...DEFAULT_SHADOWS },
@@ -1859,20 +1862,15 @@ function resolveDirectives(directives, attribution) {
1859
1862
  theme.roundedShapeScale = defaultScaleForShape(shape);
1860
1863
  }
1861
1864
  if (directive.body) {
1862
- const { entries, removals } = parseKeyValueBody(
1863
- directive.body,
1864
- theme.warnings,
1865
- "rounded"
1866
- );
1867
- let roofValue;
1865
+ const { entries } = parseKeyValueBody(directive.body, theme.warnings, "rounded");
1868
1866
  let scaleValue;
1869
- const overrides = {};
1870
1867
  for (const [k, v] of entries) {
1871
- if (k === "--roof") roofValue = v;
1872
- else if (k === "--corner-scale") scaleValue = v;
1873
- else overrides[k] = v;
1868
+ if (k === "--corner-scale") scaleValue = v;
1869
+ else
1870
+ theme.warnings.push(
1871
+ `[RI-1122] Unknown @rounded option "${k}" \u2014 supported: --corner-scale. Radii are numeric: rounded-{n} is n * --spacing.`
1872
+ );
1874
1873
  }
1875
- if (roofValue !== void 0) theme.roundedRoof = roofValue;
1876
1874
  if (scaleValue !== void 0) {
1877
1875
  const n = Number.parseFloat(scaleValue);
1878
1876
  if (!Number.isNaN(n) && n > 0) {
@@ -1883,13 +1881,6 @@ function resolveDirectives(directives, attribution) {
1883
1881
  );
1884
1882
  }
1885
1883
  }
1886
- theme.rounded = mergeWithRemovals(
1887
- theme.rounded,
1888
- overrides,
1889
- removals,
1890
- theme.warnings,
1891
- "rounded"
1892
- );
1893
1884
  }
1894
1885
  break;
1895
1886
  }
@@ -3094,21 +3085,80 @@ function findClosest(input, candidates, maxDistance) {
3094
3085
  return best;
3095
3086
  }
3096
3087
 
3088
+ // src/utilities/helpers.ts
3089
+ var INTEGER_RE = /^\d+$/;
3090
+ var DECIMAL_RE = /^\d+(?:[._]\d+)?$/;
3091
+ function single(property, value) {
3092
+ return { declarations: [{ property, value }] };
3093
+ }
3094
+ function multi(...pairs) {
3095
+ return {
3096
+ declarations: pairs.map(([property, value]) => ({ property, value }))
3097
+ };
3098
+ }
3099
+ function extractArbitrary(value) {
3100
+ if (!value) return null;
3101
+ return value.startsWith("[") && value.endsWith("]") ? decodeArbitraryValue(value.slice(1, -1)) : null;
3102
+ }
3103
+ function normalizeDecimalToken(value) {
3104
+ return value.replaceAll("_", ".");
3105
+ }
3106
+ function spacingLookup(value, negative = false) {
3107
+ if (value === "px") return negative ? "-1px" : "1px";
3108
+ if (!DECIMAL_RE.test(value)) return null;
3109
+ const normalized = normalizeDecimalToken(value);
3110
+ const num = Number(normalized);
3111
+ if (!Number.isFinite(num) || num < 0) return null;
3112
+ if (num === 0) return "0px";
3113
+ const expr = `calc(${normalized} * var(--spacing))`;
3114
+ return negative ? `calc(${normalized} * var(--spacing) * -1)` : expr;
3115
+ }
3116
+ function deepFreezeUtilityMap(map) {
3117
+ for (const value of Object.values(map)) {
3118
+ Object.freeze(value.declarations);
3119
+ Object.freeze(value);
3120
+ }
3121
+ return Object.freeze(map);
3122
+ }
3123
+
3097
3124
  // src/utilities/custom.ts
3098
- var customUtilityMapCache = /* @__PURE__ */ new WeakMap();
3099
- function getCustomUtility(theme, name) {
3100
- return getCustomUtilityMap(theme).get(name);
3101
- }
3102
- function getCustomUtilityMap(theme) {
3103
- let map = customUtilityMapCache.get(theme);
3104
- if (!map) {
3105
- map = /* @__PURE__ */ new Map();
3125
+ var customUtilityIndexCache = /* @__PURE__ */ new WeakMap();
3126
+ function getCustomUtilityIndex(theme) {
3127
+ let index = customUtilityIndexCache.get(theme);
3128
+ if (!index) {
3129
+ const statics = /* @__PURE__ */ new Map();
3130
+ const functional = /* @__PURE__ */ new Map();
3106
3131
  for (const cu of theme.customUtilities) {
3107
- map.set(cu.name, cu);
3132
+ (cu.functional ? functional : statics).set(cu.name, cu);
3108
3133
  }
3109
- customUtilityMapCache.set(theme, map);
3134
+ index = {
3135
+ statics,
3136
+ functional: [...functional.values()].sort((a, b) => b.name.length - a.name.length)
3137
+ };
3138
+ customUtilityIndexCache.set(theme, index);
3110
3139
  }
3111
- return map;
3140
+ return index;
3141
+ }
3142
+ var VALUE_PLACEHOLDER = "var(--value)";
3143
+ var UNSAFE_SUFFIX_RE = /[;{}]/;
3144
+ function functionalValue(suffix) {
3145
+ const arbitrary = extractArbitrary(suffix);
3146
+ if (arbitrary !== null) return arbitrary;
3147
+ return UNSAFE_SUFFIX_RE.test(suffix) ? null : suffix;
3148
+ }
3149
+ function matchCustomUtility(utility, value, negative, theme) {
3150
+ const index = getCustomUtilityIndex(theme);
3151
+ const full = value === null ? utility : `${utility}-${value}`;
3152
+ const exact = index.statics.get(full);
3153
+ if (exact) return { cu: exact, substitution: null };
3154
+ if (negative || value === null) return null;
3155
+ for (const cu of index.functional) {
3156
+ if (full.length <= cu.name.length + 1) continue;
3157
+ if (!full.startsWith(cu.name) || full.charCodeAt(cu.name.length) !== 45) continue;
3158
+ const substitution = functionalValue(full.slice(cu.name.length + 1));
3159
+ return substitution === null ? null : { cu, substitution };
3160
+ }
3161
+ return null;
3112
3162
  }
3113
3163
  var SELECTOR_WS_COLLAPSE_RE = /\s+/g;
3114
3164
  function parseCustomUtilityBody(body) {
@@ -3240,10 +3290,10 @@ function buildNestedBlock(node, theme, expansion, resolve) {
3240
3290
  if (declarations.length === 0 && nested.length === 0) return null;
3241
3291
  return { selector: node.selector, declarations, nested };
3242
3292
  }
3243
- function resolveCustomUtility(utility, value, theme, resolve, visiting) {
3244
- const map = getCustomUtilityMap(theme);
3245
- const cu = value === null ? map.get(utility) : map.get(`${utility}-${value}`);
3246
- if (!cu || cu.functional) return null;
3293
+ function resolveCustomUtility(utility, value, negative, theme, resolve, visiting) {
3294
+ const match = matchCustomUtility(utility, value, negative, theme);
3295
+ if (!match) return null;
3296
+ const { cu, substitution } = match;
3247
3297
  const tree = getParsedBody(cu.body);
3248
3298
  let expansion = null;
3249
3299
  if (hasApplyLikeDirective(cu.body)) {
@@ -3263,10 +3313,23 @@ function resolveCustomUtility(utility, value, theme, resolve, visiting) {
3263
3313
  if (block) nested.push(block);
3264
3314
  }
3265
3315
  if (expansion) expansion.delete(cu.name);
3316
+ if (substitution !== null) substituteValue(declarations, nested, substitution);
3266
3317
  if (declarations.length === 0 && nested.length === 0) return null;
3267
3318
  if (nested.length === 0) return { declarations };
3268
3319
  return { declarations, nested };
3269
3320
  }
3321
+ function substituteValue(declarations, nested, value) {
3322
+ for (let i = 0; i < declarations.length; i++) {
3323
+ const d = declarations[i];
3324
+ if (d.value.includes(VALUE_PLACEHOLDER)) {
3325
+ declarations[i] = {
3326
+ property: d.property,
3327
+ value: d.value.replaceAll(VALUE_PLACEHOLDER, value)
3328
+ };
3329
+ }
3330
+ }
3331
+ for (const block of nested) substituteValue(block.declarations, block.nested, value);
3332
+ }
3270
3333
  function extractCustomUtilityRootInfo(body) {
3271
3334
  const tree = getParsedBody(body);
3272
3335
  const seen = /* @__PURE__ */ new Set();
@@ -3280,42 +3343,6 @@ function extractCustomUtilityRootInfo(body) {
3280
3343
  return { properties, applyClasses: [...tree.applyClasses] };
3281
3344
  }
3282
3345
 
3283
- // src/utilities/helpers.ts
3284
- var INTEGER_RE = /^\d+$/;
3285
- var DECIMAL_RE = /^\d+(?:[._]\d+)?$/;
3286
- function single(property, value) {
3287
- return { declarations: [{ property, value }] };
3288
- }
3289
- function multi(...pairs) {
3290
- return {
3291
- declarations: pairs.map(([property, value]) => ({ property, value }))
3292
- };
3293
- }
3294
- function extractArbitrary(value) {
3295
- if (!value) return null;
3296
- return value.startsWith("[") && value.endsWith("]") ? decodeArbitraryValue(value.slice(1, -1)) : null;
3297
- }
3298
- function normalizeDecimalToken(value) {
3299
- return value.replaceAll("_", ".");
3300
- }
3301
- function spacingLookup(value, negative = false) {
3302
- if (value === "px") return negative ? "-1px" : "1px";
3303
- if (!DECIMAL_RE.test(value)) return null;
3304
- const normalized = normalizeDecimalToken(value);
3305
- const num = Number(normalized);
3306
- if (!Number.isFinite(num) || num < 0) return null;
3307
- if (num === 0) return "0px";
3308
- const expr = `calc(${normalized} * var(--spacing))`;
3309
- return negative ? `calc(${normalized} * var(--spacing) * -1)` : expr;
3310
- }
3311
- function deepFreezeUtilityMap(map) {
3312
- for (const value of Object.values(map)) {
3313
- Object.freeze(value.declarations);
3314
- Object.freeze(value);
3315
- }
3316
- return Object.freeze(map);
3317
- }
3318
-
3319
3346
  // src/utilities/spacing.ts
3320
3347
  function resolveSpacing(val, negative, warnings, allowAuto = true) {
3321
3348
  if (val === null) return null;
@@ -5236,13 +5263,9 @@ var ROUNDED_CORNER = Object.freeze({
5236
5263
  var ROUNDED_CORNER_ENTRIES = Object.entries(ROUNDED_CORNER);
5237
5264
  var ROUNDED_SIDE_NAMES = Object.freeze(Object.keys(ROUNDED_SIDE));
5238
5265
  var ROUNDED_CORNER_NAMES = Object.freeze(Object.keys(ROUNDED_CORNER));
5239
- function resolveRadius(name, theme) {
5266
+ function resolveRadius(name) {
5240
5267
  if (name === "none") return "0px";
5241
5268
  if (name === "full") return "calc(infinity * 1px)";
5242
- if (Object.hasOwn(theme.rounded, name)) {
5243
- const base = `var(--rounded-${name})`;
5244
- return `calc(${base} * var(--ri-rounded-scale, 1))`;
5245
- }
5246
5269
  if (DECIMAL_RE.test(name)) {
5247
5270
  const n = Number.parseFloat(normalizeDecimalToken(name));
5248
5271
  if (n === 0) return "0px";
@@ -5289,7 +5312,7 @@ function resolveDivide(full, axis) {
5289
5312
  var BORDER_DIR_PROPS_ENTRIES = Object.entries(
5290
5313
  BORDER_DIR_PROPS
5291
5314
  ).map(([prefix, props]) => [`${prefix}-`, props[0]]);
5292
- function borderGenerator(_utility, _value, full, negative, theme, _warnings, dataType) {
5315
+ function borderGenerator(_utility, _value, full, negative, _theme, _warnings, dataType) {
5293
5316
  if (dataType === "color") return null;
5294
5317
  if (Object.hasOwn(STATIC_BORDER, full)) return STATIC_BORDER[full];
5295
5318
  if (full.startsWith("corner-")) {
@@ -5298,31 +5321,19 @@ function borderGenerator(_utility, _value, full, negative, theme, _warnings, dat
5298
5321
  return multi(["corner-shape", arb], ["--ri-rounded-scale", "1"]);
5299
5322
  }
5300
5323
  }
5301
- if (full === "rounded") {
5302
- const r = resolveRadius("sm", theme) ?? "0.25rem";
5303
- return single("border-radius", r);
5304
- }
5305
5324
  if (full.startsWith("rounded-")) {
5306
5325
  const rest = full.slice(8);
5307
5326
  for (const [suffix, prop] of ROUNDED_CORNER_ENTRIES) {
5308
- if (rest === suffix) {
5309
- const r2 = resolveRadius("sm", theme);
5310
- if (r2) return single(prop, r2);
5311
- }
5312
5327
  if (rest.startsWith(suffix) && rest.charCodeAt(suffix.length) === 45) {
5313
5328
  const size = rest.slice(suffix.length + 1);
5314
- const r2 = resolveRadius(size, theme);
5329
+ const r2 = resolveRadius(size);
5315
5330
  if (r2) return single(prop, r2);
5316
5331
  }
5317
5332
  }
5318
5333
  for (const [suffix, props] of ROUNDED_SIDE_ENTRIES) {
5319
- if (rest === suffix) {
5320
- const r2 = resolveRadius("sm", theme);
5321
- if (r2) return multi(...props.map((p) => [p, r2]));
5322
- }
5323
5334
  if (rest.startsWith(suffix) && rest.charCodeAt(suffix.length) === 45) {
5324
5335
  const size = rest.slice(suffix.length + 1);
5325
- const r2 = resolveRadius(size, theme);
5336
+ const r2 = resolveRadius(size);
5326
5337
  if (r2) return multi(...props.map((p) => [p, r2]));
5327
5338
  }
5328
5339
  }
@@ -5336,7 +5347,7 @@ function borderGenerator(_utility, _value, full, negative, theme, _warnings, dat
5336
5347
  }
5337
5348
  return null;
5338
5349
  }
5339
- const r = resolveRadius(rest, theme);
5350
+ const r = resolveRadius(rest);
5340
5351
  if (r) return single("border-radius", r);
5341
5352
  }
5342
5353
  if (full.startsWith("border-")) {
@@ -7331,7 +7342,14 @@ function resolveUtility(utility, value, negative, theme, warnings, visiting, dat
7331
7342
  if (result) return result;
7332
7343
  }
7333
7344
  }
7334
- const customResult = resolveCustomUtility(utility, value, theme, resolveUtility, visiting);
7345
+ const customResult = resolveCustomUtility(
7346
+ utility,
7347
+ value,
7348
+ negative,
7349
+ theme,
7350
+ resolveUtility,
7351
+ visiting
7352
+ );
7335
7353
  if (customResult) return customResult;
7336
7354
  return null;
7337
7355
  }
@@ -7997,7 +8015,6 @@ var COLOR_STOP_REF_RE = /var\(--color-([a-z][a-z0-9]*(?:-[a-z][a-z0-9]*)*)(?:-(\
7997
8015
  var SHADOW_VAR_REF_RE = /var\(--shadow-([a-z0-9]+(?:-[a-z0-9]+)*)\)/g;
7998
8016
  var TEXT_VAR_REF_RE = /var\(--text-([a-z0-9]+(?:-[a-z0-9]+)*?)(?:-leading)?\)/g;
7999
8017
  var FONT_VAR_REF_RE = /var\(--font-([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\)/g;
8000
- var ROUNDED_VAR_REF_RE = /var\(--rounded-([a-z0-9]+(?:-[a-z0-9]+)*)\)/g;
8001
8018
  var ANIMATE_VAR_REF_RE = /var\(--animate-([a-z0-9]+(?:-[a-z0-9]+)*)\)/g;
8002
8019
 
8003
8020
  // src/engine/support-blocks.ts
@@ -8305,7 +8322,6 @@ var RE_COLOR_SINGLE = /^color-([\w-]+)$/;
8305
8322
  var RE_ANIMATE = /^animate-([\w-]+)$/;
8306
8323
  var RE_TEXT = /^text-([\w-]+)$/;
8307
8324
  var RE_TEXT_LEADING = /^text-([\w-]+)-leading$/;
8308
- var RE_ROUNDED = /^rounded-([\w-]+)$/;
8309
8325
  var RE_SHADOW = /^shadow-([\w-]+)$/;
8310
8326
  var RE_BREAKPOINT = /^breakpoint-([\w-]+)$/;
8311
8327
  var RE_FONT_SUB_VAR = /--(?:features|variations)$/;
@@ -8351,10 +8367,6 @@ function lookupThemeValue(varName, theme, inline = false) {
8351
8367
  if (getFontSlots(theme).has(baseSlot)) return inline ? null : `var(${varName})`;
8352
8368
  return null;
8353
8369
  }
8354
- if (name === "rounded-roof") return theme.roundedRoof;
8355
- const roundedMatch = name.startsWith("rounded-") ? name.match(RE_ROUNDED) : null;
8356
- if (roundedMatch && Object.hasOwn(theme.rounded, roundedMatch[1]))
8357
- return theme.rounded[roundedMatch[1]];
8358
8370
  const shadowMatch = name.startsWith("shadow-") ? name.match(RE_SHADOW) : null;
8359
8371
  if (shadowMatch && Object.hasOwn(theme.shadows, shadowMatch[1]))
8360
8372
  return theme.shadows[shadowMatch[1]];
@@ -8403,10 +8415,6 @@ function collectThemeVariableNames(theme) {
8403
8415
  names.push(`--text-${size}`);
8404
8416
  names.push(`--text-${size}-leading`);
8405
8417
  }
8406
- names.push("--rounded-roof");
8407
- for (const name of Object.keys(theme.rounded)) {
8408
- names.push(`--rounded-${name}`);
8409
- }
8410
8418
  for (const name of Object.keys(theme.shadows)) {
8411
8419
  names.push(`--shadow-${name}`);
8412
8420
  }
@@ -8567,11 +8575,6 @@ function scanStringForTokenUsage(str, result) {
8567
8575
  result.usedFonts.add(m[1]);
8568
8576
  }
8569
8577
  }
8570
- if (str.includes("var(--rounded-")) {
8571
- for (const m of str.matchAll(ROUNDED_VAR_REF_RE)) {
8572
- result.usedRounded.add(m[1]);
8573
- }
8574
- }
8575
8578
  if (str.includes("var(--shadow-")) {
8576
8579
  for (const m of str.matchAll(SHADOW_VAR_REF_RE)) {
8577
8580
  result.usedShadows.add(m[1]);
@@ -8623,13 +8626,13 @@ function createEmptyCompilationResult() {
8623
8626
  usedColorStops: /* @__PURE__ */ new Map(),
8624
8627
  usedTextSizes: /* @__PURE__ */ new Set(),
8625
8628
  usedFonts: /* @__PURE__ */ new Set(),
8626
- usedRounded: /* @__PURE__ */ new Set(),
8627
8629
  usedShadows: /* @__PURE__ */ new Set(),
8628
8630
  usedAnimations: /* @__PURE__ */ new Set(),
8629
8631
  warnings: []
8630
8632
  };
8631
8633
  }
8632
8634
  var classCompileMemo = /* @__PURE__ */ new WeakMap();
8635
+ var CLASS_COMPILE_MEMO_CAP = 5e4;
8633
8636
  function compileClassEntry(raw, theme, customVariantMap, breakpointWeights, variantMemo) {
8634
8637
  const scratch = createEmptyCompilationResult();
8635
8638
  const parsed = parseUtility(raw);
@@ -8643,6 +8646,7 @@ function compileClassEntry(raw, theme, customVariantMap, breakpointWeights, vari
8643
8646
  variantMemo
8644
8647
  );
8645
8648
  const support = new Array(SUPPORT_BLOCKS.length).fill(false);
8649
+ let arbitraryWarning = null;
8646
8650
  if (rule) {
8647
8651
  for (let i = 0; i < SUPPORT_BLOCKS.length; i++) {
8648
8652
  const block = SUPPORT_BLOCKS[i];
@@ -8654,18 +8658,16 @@ function compileClassEntry(raw, theme, customVariantMap, breakpointWeights, vari
8654
8658
  const bracketContent = parsed.value.replace(/^\[|\]$/g, "");
8655
8659
  if (shouldWarnUnresolvedArbitrary(bracketContent)) {
8656
8660
  const truncated = parsed.raw.length > 100 ? `${parsed.raw.slice(0, 100)}...` : parsed.raw;
8657
- scratch.warnings.push(
8658
- `[RI-1002] Could not resolve arbitrary utility "${truncated}". Arbitrary utilities use \`[property:value]\` syntax \u2014 e.g. \`[padding:1rem]\` or \`[mask-type:luminance]\`. Check that the property name is a known CSS property and the value is well-formed (no stray spaces, quoted strings escaped). If you meant to set a CSS variable, use \`[--my-var:value]\`.`
8659
- );
8661
+ arbitraryWarning = `[RI-1002] Could not resolve arbitrary utility "${truncated}". Arbitrary utilities use \`[property:value]\` syntax \u2014 e.g. \`[padding:1rem]\` or \`[mask-type:luminance]\`. Check that the property name is a known CSS property and the value is well-formed (no stray spaces, quoted strings escaped). If you meant to set a CSS variable, use \`[--my-var:value]\`.`;
8660
8662
  }
8661
8663
  }
8662
8664
  return {
8663
8665
  rule,
8664
8666
  warnings: scratch.warnings,
8667
+ arbitraryWarning,
8665
8668
  usedColorStops: scratch.usedColorStops,
8666
8669
  usedTextSizes: scratch.usedTextSizes,
8667
8670
  usedFonts: scratch.usedFonts,
8668
- usedRounded: scratch.usedRounded,
8669
8671
  usedShadows: scratch.usedShadows,
8670
8672
  usedAnimations: scratch.usedAnimations,
8671
8673
  support
@@ -8700,7 +8702,7 @@ function registerThemeOnContext(ctx, theme) {
8700
8702
  }
8701
8703
  }
8702
8704
  if (properties.length > 0) {
8703
- registerCustomUtility(ctx, cu.name, properties);
8705
+ registerCustomUtility(ctx, cu.name, properties, cu.functional);
8704
8706
  }
8705
8707
  }
8706
8708
  }
@@ -8709,7 +8711,7 @@ function createThemeSnapshot(theme) {
8709
8711
  registerThemeOnContext(ctx, theme);
8710
8712
  return snapshotCompilationContext(ctx);
8711
8713
  }
8712
- function compileInternal(classNames, theme, variantMapCache) {
8714
+ function compileInternal(classNames, theme, variantMapCache, authored) {
8713
8715
  const ctx = createCompilationContext();
8714
8716
  registerThemeOnContext(ctx, theme);
8715
8717
  const customVariantMap = theme.customVariants.length === 0 ? _emptyVariantMap : variantMapCache.get(theme) ?? (() => {
@@ -8733,12 +8735,16 @@ function compileInternal(classNames, theme, variantMapCache) {
8733
8735
  seen.add(raw);
8734
8736
  let entry = classMemo.get(raw);
8735
8737
  if (!entry) {
8738
+ if (classMemo.size >= CLASS_COMPILE_MEMO_CAP) classMemo.clear();
8736
8739
  entry = compileClassEntry(raw, theme, customVariantMap, breakpointWeights, variantMemo);
8737
8740
  classMemo.set(raw, entry);
8738
8741
  }
8739
8742
  if (entry.warnings.length > 0) {
8740
8743
  pushWarningsDeduped(result.warnings, entry.warnings, warnSeen);
8741
8744
  }
8745
+ if (entry.arbitraryWarning !== null && (authored === void 0 || authored.has(raw))) {
8746
+ pushWarningsDeduped(result.warnings, [entry.arbitraryWarning], warnSeen);
8747
+ }
8742
8748
  for (const [hue, stops] of entry.usedColorStops) {
8743
8749
  let set = result.usedColorStops.get(hue);
8744
8750
  if (!set) {
@@ -8749,7 +8755,6 @@ function compileInternal(classNames, theme, variantMapCache) {
8749
8755
  }
8750
8756
  for (const v of entry.usedTextSizes) result.usedTextSizes.add(v);
8751
8757
  for (const v of entry.usedFonts) result.usedFonts.add(v);
8752
- for (const v of entry.usedRounded) result.usedRounded.add(v);
8753
8758
  for (const v of entry.usedShadows) result.usedShadows.add(v);
8754
8759
  for (const v of entry.usedAnimations) result.usedAnimations.add(v);
8755
8760
  if (!entry.rule) continue;
@@ -8806,7 +8811,7 @@ function createCompiler() {
8806
8811
  const variantMapCache = /* @__PURE__ */ new WeakMap();
8807
8812
  return {
8808
8813
  fontOutputCache,
8809
- compile(classNames, theme) {
8814
+ compile(classNames, theme, authored) {
8810
8815
  const list = typeof classNames === "string" ? classNames.split(WS_RE3).filter(Boolean) : classNames;
8811
8816
  if (list == null || typeof list[Symbol.iterator] !== "function") {
8812
8817
  throw new TypeError(
@@ -8818,7 +8823,7 @@ function createCompiler() {
8818
8823
  `[RI-2007] compile() expected theme to be a ResolvedTheme object, got ${typeof theme}.`
8819
8824
  );
8820
8825
  }
8821
- const { result, ctx } = compileInternal(list, theme, variantMapCache);
8826
+ const { result, ctx } = compileInternal(list, theme, variantMapCache, authored);
8822
8827
  latestSnapshot = snapshotCompilationContext(ctx);
8823
8828
  fontOutputCache.clear();
8824
8829
  return result;
@@ -9205,7 +9210,9 @@ function readAssignedValue(source, start) {
9205
9210
  return {
9206
9211
  value: source.slice(i + 1, end2),
9207
9212
  end: end2,
9208
- valueStart: i + 1
9213
+ valueStart: i + 1,
9214
+ quoted: true,
9215
+ bare: false
9209
9216
  };
9210
9217
  }
9211
9218
  if (ch === "`") {
@@ -9213,7 +9220,9 @@ function readAssignedValue(source, start) {
9213
9220
  return {
9214
9221
  value: source.slice(i + 1, end2),
9215
9222
  end: end2,
9216
- valueStart: i + 1
9223
+ valueStart: i + 1,
9224
+ quoted: false,
9225
+ bare: false
9217
9226
  };
9218
9227
  }
9219
9228
  if (ch in BRACKET_PAIRS) {
@@ -9222,7 +9231,9 @@ function readAssignedValue(source, start) {
9222
9231
  return {
9223
9232
  value: source.slice(i + 1, end2),
9224
9233
  end: end2,
9225
- valueStart: i + 1
9234
+ valueStart: i + 1,
9235
+ quoted: false,
9236
+ bare: false
9226
9237
  };
9227
9238
  }
9228
9239
  let end = i;
@@ -9230,7 +9241,9 @@ function readAssignedValue(source, start) {
9230
9241
  return {
9231
9242
  value: source.slice(i, end),
9232
9243
  end: end - 1,
9233
- valueStart: i
9244
+ valueStart: i,
9245
+ quoted: false,
9246
+ bare: true
9234
9247
  };
9235
9248
  }
9236
9249
  function splitTopLevelArgs(source) {
@@ -9313,12 +9326,22 @@ function collectTokensInLiteral(source, start, quoteCode, out) {
9313
9326
  }
9314
9327
 
9315
9328
  // src/scanner/sinks.ts
9329
+ function isClassListOrigin(origin) {
9330
+ return origin !== "plain" && origin !== "expression";
9331
+ }
9316
9332
  var SetSink = class {
9317
9333
  constructor(classes) {
9318
9334
  this.classes = classes;
9319
9335
  }
9320
9336
  classes;
9321
9337
  wantsPositions = false;
9338
+ origin = "plain";
9339
+ get inClassList() {
9340
+ return isClassListOrigin(this.origin);
9341
+ }
9342
+ setOrigin(origin) {
9343
+ this.origin = origin;
9344
+ }
9322
9345
  add(value) {
9323
9346
  this.classes.add(value);
9324
9347
  }
@@ -9334,6 +9357,12 @@ var CandidateCollector = class {
9334
9357
  /** value -> byCandidate keys, so delete() is O(occurrences of the value). */
9335
9358
  keysByValue = /* @__PURE__ */ new Map();
9336
9359
  contexts = [];
9360
+ /** Candidates a context-aware collector tokenized itself — the only ones
9361
+ * eligible to inherit a context's origin (see finish()). */
9362
+ contextual = /* @__PURE__ */ new WeakSet();
9363
+ get inClassList() {
9364
+ return isClassListOrigin(this.origin);
9365
+ }
9337
9366
  setOrigin(origin) {
9338
9367
  this.origin = origin;
9339
9368
  this.helperName = null;
@@ -9351,10 +9380,26 @@ var CandidateCollector = class {
9351
9380
  id: this.contexts.length
9352
9381
  });
9353
9382
  }
9383
+ markExpression(start, end) {
9384
+ if (end <= start) return;
9385
+ this.contexts.push({
9386
+ start,
9387
+ end,
9388
+ origin: "expression",
9389
+ helper: null,
9390
+ id: this.contexts.length
9391
+ });
9392
+ }
9354
9393
  add(value, start, end, prefixStart, prefixEnd) {
9355
9394
  const key = `${start}:${end}:${value}`;
9356
- if (this.byCandidate.has(key)) return;
9395
+ const contextual = this.origin !== "plain";
9396
+ const existing = this.byCandidate.get(key);
9397
+ if (existing) {
9398
+ if (contextual) this.contextual.add(existing);
9399
+ return;
9400
+ }
9357
9401
  const candidate = { value, start, end, origin: "plain" };
9402
+ if (contextual) this.contextual.add(candidate);
9358
9403
  if (prefixStart >= 0) candidate.groupPrefix = { start: prefixStart, end: prefixEnd };
9359
9404
  this.byCandidate.set(key, candidate);
9360
9405
  let keys = this.keysByValue.get(value);
@@ -9400,7 +9445,7 @@ var CandidateCollector = class {
9400
9445
  }
9401
9446
  }
9402
9447
  }
9403
- if (best) {
9448
+ if (best && this.contextual.has(candidate)) {
9404
9449
  candidate.origin = best.origin;
9405
9450
  if (best.helper) candidate.helperName = best.helper;
9406
9451
  if (best.origin === "helper" || best.origin === "safelist") {
@@ -9425,7 +9470,7 @@ var ARBITRARY_PROPERTY = /\[[a-z-][^\]]*:[^\]]+\](?:\/(?:[\w.]+|\[[^\]]*\]|\([^)
9425
9470
  var CLASS_RE_SOURCE = `${BOUNDARY}(${NEGATIVE}${VARIANT_PREFIX}(?:${UTILITY_VALUE}|${VARIANT_GROUP}|${ARBITRARY_PROPERTY})${IMPORTANT_SUFFIX})`;
9426
9471
  var CLASS_RE = new RegExp(CLASS_RE_SOURCE, "g");
9427
9472
  var VARIANT_STRIP_RE = new RegExp(`^(?:${VARIANT_SEGMENT})+`);
9428
- var MAX_LINE_LENGTH = 2e3;
9473
+ var MAX_LINE_LENGTH = 1e4;
9429
9474
  var CLASS_HELPERS = [
9430
9475
  "clsx",
9431
9476
  "cn",
@@ -9448,6 +9493,10 @@ var HAS_UPPERCASE_RE = /[A-Z]/;
9448
9493
  var BRACKET_WHITESPACE_RE = /\[[^\]]*\s+[^\]]*\]/;
9449
9494
  var INDEX_ACCESS_RE = /\[\d*\]$/;
9450
9495
  var PROPERTY_ACCESS_RE = /^[\w.@]+\[[^\]]+\]$/;
9496
+ function warnBracketWhitespace(warnings, cls) {
9497
+ const message = `[RI-1412] Class "${cls}" has whitespace inside its arbitrary value, so it can never match an element \u2014 class attributes, @a/@apply, and safelist() all split on whitespace. Use "_" for a space (\`bg-[url('a_b')]\` emits \`url('a b')\`) and "\\_" for a literal underscore. The class was skipped.`;
9498
+ if (!warnings.includes(message)) warnings.push(message);
9499
+ }
9451
9500
  function scanClassTokens(sink, source, baseOffset, warnings) {
9452
9501
  const wantsPositions = sink.wantsPositions;
9453
9502
  let filteredSource = source;
@@ -9503,10 +9552,17 @@ function scanClassTokens(sink, source, baseOffset, warnings) {
9503
9552
  if (!cls) continue;
9504
9553
  const unbanged = cls.endsWith("!") ? cls.slice(0, -1) : cls;
9505
9554
  const base = unbanged.replace(VARIANT_STRIP_RE, "");
9506
- if (HAS_UPPERCASE_RE.test(base.replace(BRACKET_SPAN_RE, ""))) continue;
9507
- if (BRACKET_WHITESPACE_RE.test(base)) continue;
9508
- if (INDEX_ACCESS_RE.test(base)) continue;
9509
- if (PROPERTY_ACCESS_RE.test(base)) continue;
9555
+ if (!base.includes("[")) {
9556
+ if (HAS_UPPERCASE_RE.test(base)) continue;
9557
+ } else {
9558
+ if (HAS_UPPERCASE_RE.test(base.replace(BRACKET_SPAN_RE, ""))) continue;
9559
+ if (INDEX_ACCESS_RE.test(base)) continue;
9560
+ if (PROPERTY_ACCESS_RE.test(base)) continue;
9561
+ if (BRACKET_WHITESPACE_RE.test(base)) {
9562
+ if (warnings && sink.inClassList) warnBracketWhitespace(warnings, cls);
9563
+ continue;
9564
+ }
9565
+ }
9510
9566
  if (!wantsPositions) {
9511
9567
  sink.add(cls, 0, 0, -1, -1);
9512
9568
  continue;
@@ -9591,6 +9647,16 @@ function collectTemplateLiteralClasses(sink, source, start, base, warnings) {
9591
9647
  }
9592
9648
  });
9593
9649
  }
9650
+ function isEqualityOperand(source, open, close) {
9651
+ let before = open - 1;
9652
+ while (before >= 0 && /\s/.test(source[before])) before--;
9653
+ if (before >= 1 && source[before] === "=" && (source[before - 1] === "=" || source[before - 1] === "!")) {
9654
+ return true;
9655
+ }
9656
+ let after = close + 1;
9657
+ while (after < source.length && /\s/.test(source[after])) after++;
9658
+ return (source[after] === "=" || source[after] === "!") && source[after + 1] === "=";
9659
+ }
9594
9660
  function collectStringLiteralClasses(sink, source, base, warnings) {
9595
9661
  let i = 0;
9596
9662
  while (i < source.length) {
@@ -9600,6 +9666,7 @@ function collectStringLiteralClasses(sink, source, base, warnings) {
9600
9666
  const raw = source.slice(i + 1, end);
9601
9667
  const value = raw.trim();
9602
9668
  if (value) {
9669
+ if (isEqualityOperand(source, i, end)) sink.markExpression?.(base + i, base + end + 1);
9603
9670
  scanClassTokens(
9604
9671
  sink,
9605
9672
  value,
@@ -9628,12 +9695,11 @@ function collectAssignedValues(sink, source, regex, visitor = scanClassTokens, b
9628
9695
  const value = raw.trim();
9629
9696
  if (!value) continue;
9630
9697
  sink.markContext?.(base + parsed.valueStart, base + parsed.valueStart + raw.length);
9631
- visitor(
9632
- sink,
9633
- value,
9634
- base + parsed.valueStart + (raw.length - raw.trimStart().length),
9635
- warnings
9636
- );
9698
+ const valueOffset = base + parsed.valueStart + (raw.length - raw.trimStart().length);
9699
+ if ((parsed.quoted || parsed.bare) && visitor !== scanClassTokens) {
9700
+ scanClassTokens(sink, value, valueOffset, warnings);
9701
+ }
9702
+ visitor(sink, value, valueOffset, warnings);
9637
9703
  regex.lastIndex = Math.max(regex.lastIndex, parsed.end + 1);
9638
9704
  }
9639
9705
  }
@@ -9807,10 +9873,8 @@ var NOOP_VISITOR = () => void 0;
9807
9873
  function extractHTML(sink, context, warnings) {
9808
9874
  sink.setOrigin?.("plain");
9809
9875
  scanClassTokens(sink, context.content, 0, warnings);
9810
- if (sink.wantsPositions) {
9811
- sink.setOrigin?.("attribute");
9812
- collectAssignedValues(sink, context.content, /\bclass\s*=/g, NOOP_VISITOR, 0, warnings);
9813
- }
9876
+ sink.setOrigin?.("attribute");
9877
+ collectAssignedValues(sink, context.content, /\bclass\s*=/g, NOOP_VISITOR, 0, warnings);
9814
9878
  }
9815
9879
  function extractJSXTSX(sink, context, warnings) {
9816
9880
  const content = context.content;
@@ -9854,16 +9918,7 @@ function extractVue(sink, context, warnings) {
9854
9918
  0,
9855
9919
  warnings
9856
9920
  );
9857
- if (sink.wantsPositions) {
9858
- collectAssignedValues(
9859
- sink,
9860
- context.content,
9861
- /(?<![:\w-])class\s*=/g,
9862
- NOOP_VISITOR,
9863
- 0,
9864
- warnings
9865
- );
9866
- }
9921
+ collectAssignedValues(sink, context.content, /(?<![:\w-])class\s*=/g, NOOP_VISITOR, 0, warnings);
9867
9922
  }
9868
9923
  function extractSvelte(sink, context, warnings) {
9869
9924
  sink.setOrigin?.("plain");
@@ -9897,24 +9952,54 @@ var EXTRACTORS = [
9897
9952
  extract: extractJSXTSX
9898
9953
  }
9899
9954
  ];
9955
+ var SVG_GEOMETRY_VALUE_RE = /((?<![\w-])(?:d|points)\s*=\s*["'])([^"']*)/g;
9956
+ function blankSvgGeometry(content) {
9957
+ return content.replace(
9958
+ SVG_GEOMETRY_VALUE_RE,
9959
+ (_match, head, value) => head + " ".repeat(value.length)
9960
+ );
9961
+ }
9962
+ function warnOverLongLines(input, warnings) {
9963
+ const content = input.content;
9964
+ if (!warnings || content.length <= MAX_LINE_LENGTH) return;
9965
+ if (input.path?.includes("node_modules")) return;
9966
+ let count = 0;
9967
+ let start = 0;
9968
+ for (; ; ) {
9969
+ const idx = content.indexOf("\n", start);
9970
+ const end = idx === -1 ? content.length : idx;
9971
+ if (end - start > MAX_LINE_LENGTH) count++;
9972
+ if (idx === -1) break;
9973
+ start = idx + 1;
9974
+ }
9975
+ if (count === 0) return;
9976
+ warnings.push(
9977
+ `[RI-1411] ${input.path ?? "<source>"}: ${count} line(s) longer than ${MAX_LINE_LENGTH} characters were skipped by the class scanner (minified-input guard). Quoted class attributes on those lines are still read; other class references there are not \u2014 split the long lines.`
9978
+ );
9979
+ }
9900
9980
  function extractInto(sink, input, warnings) {
9981
+ warnOverLongLines(input, warnings);
9982
+ const scanned = {
9983
+ path: input.path,
9984
+ content: blankSvgGeometry(input.content)
9985
+ };
9901
9986
  let handled = false;
9902
9987
  for (const extractor of EXTRACTORS) {
9903
- if (extractor.test(input)) {
9904
- extractor.extract(sink, input, warnings);
9988
+ if (extractor.test(scanned)) {
9989
+ extractor.extract(sink, scanned, warnings);
9905
9990
  handled = true;
9906
9991
  break;
9907
9992
  }
9908
9993
  }
9909
9994
  if (!handled) {
9910
9995
  sink.setOrigin?.("plain");
9911
- scanClassTokens(sink, input.content, 0, warnings);
9996
+ scanClassTokens(sink, scanned.content, 0, warnings);
9912
9997
  }
9913
- if (input.content.includes("safelist")) {
9998
+ if (scanned.content.includes("safelist")) {
9914
9999
  sink.setOrigin?.("safelist");
9915
10000
  collectCallArguments(
9916
10001
  sink,
9917
- input.content,
10002
+ scanned.content,
9918
10003
  SAFELIST_CALL_RE,
9919
10004
  collectStringLiteralClasses,
9920
10005
  0,
@@ -10007,7 +10092,7 @@ export {
10007
10092
  MAX_DIRECTIVE_INPUT_SIZE,
10008
10093
  extractDirectives,
10009
10094
  parseUtility,
10010
- getCustomUtility,
10095
+ matchCustomUtility,
10011
10096
  forEachApplyClass,
10012
10097
  BACKGROUND_STATIC_NAMES,
10013
10098
  TYPOGRAPHY_STATIC_NAMES,