@bamboocss/generator 1.16.0 → 1.17.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.
Files changed (3) hide show
  1. package/dist/index.cjs +126 -31
  2. package/dist/index.mjs +126 -31
  3. package/package.json +8 -8
package/dist/index.cjs CHANGED
@@ -226,8 +226,12 @@ function generateCssFn(ctx) {
226
226
  }
227
227
 
228
228
  const cssFn = createCss(context)
229
- export const css = /* @__PURE__ */ memo((...styles) => cssFn(mergeCss(...styles)))
230
- // The merged result is cached and shared, so a caller mutating a nested
229
+ // \`mergeCssUncached\` rather than \`mergeCss\`: this callback runs only when the memo
230
+ // above it missed, and a miss means these arguments have not been seen — so a second
231
+ // cache keyed on the same arguments can only miss too, after paying for the lookup.
232
+ export const css = /* @__PURE__ */ memo((...styles) => cssFn(mergeCssUncached(...styles)))
233
+ // The cached merge here, since \`raw\` is called straight from user code with no memo
234
+ // above it. The merged result is cached and shared, so a caller mutating a nested
231
235
  // condition object would otherwise poison it for everyone after them.
232
236
  css.raw = (...styles) => cloneStyles(mergeCss(...styles))
233
237
 
@@ -251,7 +255,7 @@ function generateCssFn(ctx) {
251
255
  // still returns a class, exactly as \`css()\` does for a value it never saw.
252
256
  export const viewTransition = (options) => viewTransitionClassName(options, ${JSON.stringify(prefix.className ?? "")})
253
257
 
254
- export const { mergeCss, assignCss } = createMergeCss(context)
258
+ export const { mergeCss, assignCss, mergeCssUncached } = createMergeCss(context)
255
259
  `
256
260
  };
257
261
  }
@@ -264,12 +268,13 @@ function generateCvaFn(ctx) {
264
268
  js: outdent.outdent`
265
269
  ${ctx.file.import("cloneStyles, compact, getRecipeClassNames, getRecipeIdentity, mergeProps, memo, splitProps, toHash, uniq", "../helpers")}
266
270
  ${ctx.file.import("mergeCss", "./css")}
271
+ ${ctx.file.import("cx", "./cx")}
267
272
 
268
273
  // What \`createCss\` does to a class name, for the recipe path: prefix it, and hash it
269
274
  // when \`hash.className\` is set. The build applies the same two steps to the rules it
270
275
  // emits — see \`checkNamingAgreement\`, which compares the results.
271
276
  const withPrefix = ${withPrefix}
272
- const formatRecipeClass = ${hash.className ? `(className) => withPrefix((${utility.toHash})([className], toHash))` : `withPrefix`}
277
+ export const formatRecipeClass = ${hash.className ? `(className) => withPrefix((${utility.toHash})([className], toHash))` : `withPrefix`}
273
278
 
274
279
  const defaults = (conf) => ({
275
280
  base: {},
@@ -307,19 +312,6 @@ function generateCvaFn(ctx) {
307
312
  return mergeCss(variantCss, compoundVariantCss)
308
313
  }
309
314
 
310
- function merge(__cva) {
311
- const override = defaults(__cva.config)
312
- const variantKeys = uniq(__cva.variantKeys, Object.keys(variants))
313
- return cva({
314
- base: mergeCss(base, override.base),
315
- variants: Object.fromEntries(
316
- variantKeys.map((key) => [key, mergeCss(variants[key], override.variants[key])]),
317
- ),
318
- defaultVariants: mergeProps(defaultVariants, override.defaultVariants),
319
- compoundVariants: [...compoundVariants, ...override.compoundVariants],
320
- })
321
- }
322
-
323
315
  // \`raw\` runs per element per render — the JSX factory calls it to build the styles it
324
316
  // merges with style props — and \`resolve\` is not cheap: a \`mergeCss\` per active variant
325
317
  // plus a scan of every compound variant. Memoizing it keys that work on the variant
@@ -356,16 +348,79 @@ function generateCvaFn(ctx) {
356
348
 
357
349
  const variantMap = Object.fromEntries(Object.entries(variants).map(([key, value]) => [key, Object.keys(value)]))
358
350
 
359
- return Object.assign(memo(cvaFn), {
351
+ const self = Object.assign(memo(cvaFn), {
360
352
  __cva__: true,
361
353
  variantMap,
362
354
  variantKeys,
363
355
  raw: (...args) => cloneStyles(resolveVariants(...args)),
364
356
  config,
365
- merge,
357
+ // Composed against \`self\`, not against this closure, so \`a.merge(b).merge(c)\`
358
+ // composes the *result* with \`c\` rather than recomposing \`a\` with \`c\` and
359
+ // dropping \`b\`.
360
+ merge: (other) => composeRecipes(self, other),
366
361
  splitVariantProps,
367
362
  getVariantProps
368
363
  })
364
+
365
+ return self
366
+ }
367
+
368
+ /**
369
+ * Compose two recipes into one.
370
+ *
371
+ * The class names come from both parents joined, not from a merged config. A recipe's
372
+ * classes are named from the config the *build* saw, and the build only ever sees the
373
+ * literal \`cva(...)\` call sites — a config synthesised here at runtime has no rules
374
+ * behind it, so naming classes off it returned classes that styled nothing. This is the
375
+ * shape \`mergeRecipes\` already uses for config recipes.
376
+ *
377
+ * The selection is resolved once and handed to both parents. Passing the raw props
378
+ * instead let each parent apply *its own* defaults, so \`m()\` and
379
+ * \`m(m.getVariantProps())\` disagreed and \`raw()\` contradicted the \`config\` the same
380
+ * object publishes.
381
+ *
382
+ * \`raw\` still deep-merges, so per-property override survives where it can be expressed:
383
+ * \`css(a.merge(b).raw(props))\` resolves before any class name exists. Through the class
384
+ * path both parents land in the \`recipes\` layer, so a collision there is decided by
385
+ * stylesheet order rather than by which parent came second.
386
+ */
387
+ function composeRecipes(left, right) {
388
+ const leftConfig = defaults(left.config)
389
+ const rightConfig = defaults(right.config)
390
+ const variantKeys = uniq(left.variantKeys, right.variantKeys)
391
+
392
+ const config = {
393
+ base: mergeCss(leftConfig.base, rightConfig.base),
394
+ variants: Object.fromEntries(
395
+ variantKeys.map((key) => [key, mergeCss(leftConfig.variants[key], rightConfig.variants[key])]),
396
+ ),
397
+ defaultVariants: mergeProps(leftConfig.defaultVariants, rightConfig.defaultVariants),
398
+ compoundVariants: [...leftConfig.compoundVariants, ...rightConfig.compoundVariants],
399
+ }
400
+
401
+ const select = (props) => ({ ...config.defaultVariants, ...compact(props) })
402
+
403
+ const composed = Object.assign(
404
+ memo((props) => {
405
+ const selection = select(props)
406
+ return cx(left(selection), right(selection))
407
+ }),
408
+ {
409
+ __cva__: true,
410
+ variantMap: Object.fromEntries(variantKeys.map((key) => [key, Object.keys(config.variants[key] ?? {})])),
411
+ variantKeys,
412
+ raw: (props) => {
413
+ const selection = select(props)
414
+ return cloneStyles(mergeCss(left.raw(selection), right.raw(selection)))
415
+ },
416
+ config,
417
+ merge: (other) => composeRecipes(composed, other),
418
+ splitVariantProps: (props) => splitProps(props, variantKeys),
419
+ getVariantProps: select,
420
+ },
421
+ )
422
+
423
+ return composed
369
424
  }
370
425
 
371
426
  export function getCompoundVariantCss(compoundVariants, variantMap) {
@@ -468,7 +523,7 @@ function generateCx() {
468
523
  }
469
524
  //#endregion
470
525
  //#region src/artifacts/generated/helpers.mjs.json
471
- var content$8 = "//#region src/assert.ts\nfunction isObject(value) {\n return typeof value === \"object\" && value != null && !Array.isArray(value);\n}\nconst isObjectOrArray = (obj) => typeof obj === \"object\" && obj !== null;\n//#endregion\n//#region src/condition.ts\nconst isBaseCondition = (v) => v === \"base\";\nfunction filterBaseConditions(c) {\n return c.slice().filter((v) => !isBaseCondition(v));\n}\n//#endregion\n//#region src/hash.ts\nfunction toChar(code) {\n return String.fromCharCode(code + (code > 25 ? 39 : 97));\n}\nfunction toName(code) {\n let name = \"\";\n let x;\n for (x = Math.abs(code); x > 52; x = x / 52 | 0) name = toChar(x % 52) + name;\n return toChar(x % 52) + name;\n}\nfunction toPhash(h, x) {\n let i = x.length;\n while (i) h = h * 33 ^ x.charCodeAt(--i);\n return h;\n}\nfunction toHash(value) {\n return toName(toPhash(5381, value) >>> 0);\n}\n//#endregion\n//#region src/important.ts\nconst importantRegex = /\\s*!(important)?/i;\nconst whitespaceRegex = /\\s/;\n/**\n* Collapse every run of whitespace to a single space, which is what the class name is\n* built from. Exported because `leafClass` has to reproduce this exact pipeline, and a\n* second copy of it would be free to drift from the one `createCss` runs.\n*/\nfunction sanitize(value) {\n if (typeof value !== \"string\") return value;\n const collapsed = whitespaceRegex.test(value) ? value.replaceAll(/[\\n\\s]+/g, \" \") : value;\n return collapsed.includes(\"\\0\") ? collapsed.replaceAll(\"\\0\", \"\") : collapsed;\n}\nfunction isImportant(value) {\n if (typeof value !== \"string\") return false;\n return value.includes(\"!\") && importantRegex.test(value);\n}\nfunction withoutImportant(value) {\n if (typeof value !== \"string\") return value;\n if (!value.includes(\"!\")) return value.trim();\n return value.replace(importantRegex, \"\").trim();\n}\nfunction withoutSpace(str) {\n if (typeof str !== \"string\") return str;\n return str.includes(\" \") ? str.replaceAll(\" \", \"_\") : str;\n}\n//#endregion\n//#region src/memo.ts\n/**\n* Bounded argument memo used by the generated runtime (`css`, patterns, `cva`, recipes).\n*\n* Two regimes, picked per call:\n*\n* - Arguments that are flat (objects of primitives) take a cheap structural hash\n* and are confirmed with an exact comparison, so a hash collision can never\n* serve the wrong result. This is the shape `css({ ... })` has.\n* - Anything nested falls back to `JSON.stringify`, which V8 does faster than a\n* JS walk.\n*\n* The second point is the counter-intuitive one, and it has been measured rather\n* than assumed. Extending the structural hash to recurse — so nested styles could\n* take the fast path too — is *slower*, because it trades one native serialization\n* for two JS walks (hash, then the deep equality that confirms it). Over 10k\n* iterations per shape:\n*\n* shape stringify recursive hash + deep equal\n* flat 1.06ms 2.09ms\n* _hover 1.00ms 2.16ms\n* responsive 1.23ms 2.15ms\n* realistic 2.32ms 5.84ms\n* nested 3 deep 1.22ms 2.35ms\n*\n* So a nested `css()` call costing several times a flat one is not a defect here.\n* It is the floor for a value-keyed memo in JS, and the way to avoid it is to not\n* make the call — see the build-time fold in `@bamboocss/vite`.\n*\n* Both regimes key on *values*, never on object identity: mutating a style object\n* between calls changes its hash, so the next call misses and recomputes rather\n* than serving a stale class. Keying nested arguments on the identity of the inner\n* objects would skip serialization entirely, but it cannot detect a mutation, and\n* \"same object, different contents\" is exactly what a style object built per render\n* looks like.\n*\n* Both caches are bounded. An unbounded memo is a leak in any long-lived process\n* (SSR), where the set of distinct style objects grows without limit.\n*/\n/**\n* Distinct hashes held per memoized function before the cache rotates.\n*\n* This bounds *buckets*, not entries: a bucket keeps up to `MAX_BUCKET` colliding\n* argument lists, so the ceiling is `MAX_ENTRIES * MAX_BUCKET` live entries, and\n* twice that across both generations, since the previous one is retained until the\n* next rotation. Collisions are rare in practice, so the realistic figure is close\n* to `MAX_ENTRIES` — but the worst case is what matters when sizing a long-lived\n* process, so state it plainly.\n*\n* Rotation beats evicting the oldest key: single-key eviction is worst-case for a\n* working set that cycles, because it drops exactly the entry about to be needed.\n* Measured on a cycling set of 20k styles, one-at-a-time eviction cost ~719ns/op\n* against ~189ns unbounded, while rotation holds ~274ns. On realistic skewed\n* access rotation is at or below the unbounded cost.\n*/\nconst MAX_ENTRIES = 1e3;\n/** Entries kept per hash bucket, to bound the cost of a collision scan. */\nconst MAX_BUCKET = 8;\n/**\n* DJB2 over the arguments' own keys and primitive values.\n* Returns `null` for anything nested, which routes the call to the string key.\n*/\nconst flatHashOrNull = (args) => {\n let h = 5381;\n for (let a = 0; a < args.length; a++) {\n const obj = args[a];\n if (obj === null || typeof obj !== \"object\") {\n const t = typeof obj;\n if (t === \"string\") for (let i = 0; i < obj.length; i++) h = h * 33 ^ obj.charCodeAt(i);\n else if (t === \"number\") h = h * 33 ^ (obj | 0);\n else if (t === \"boolean\") h = h * 33 ^ (obj ? 991 : 997);\n else h = h * 33 ^ 3;\n continue;\n }\n if (Array.isArray(obj)) h = h * 33 ^ 7;\n else {\n const proto = Object.getPrototypeOf(obj);\n if (proto !== Object.prototype && proto !== null) return null;\n }\n for (const k in obj) {\n const v = obj[k];\n const tv = typeof v;\n if (v !== null && tv === \"object\") return null;\n for (let i = 0; i < k.length; i++) h = h * 33 ^ k.charCodeAt(i);\n if (tv === \"string\") for (let i = 0; i < v.length; i++) h = h * 33 ^ v.charCodeAt(i);\n else if (tv === \"number\") h = h * 33 ^ (v | 0);\n else if (tv === \"boolean\") h = h * 33 ^ (v ? 991 : 997);\n else h = h * 33 ^ 2;\n }\n }\n return h >>> 0;\n};\n/**\n* Value snapshot of the arguments, taken once at insert.\n*\n* The cache must not hold the caller's objects: a style object can capture a much\n* larger graph, and keeping it alive until the cache rotates changes GC behaviour\n* for code that never asked to be cached. Only the flat path reaches here, so a\n* shallow copy contains primitives only and retains nothing.\n*\n* Comparing against a copy also removes the last way a mutation could be missed.\n* Were the caller's own object stored, `oa === ob` would short-circuit the value\n* comparison, and a mutation that happened to preserve the hash would return the\n* stale entry. Against a copy that check can only ever be true for equal\n* primitives.\n*/\nconst snapshotArgs = (args) => {\n const values = [];\n const counts = [];\n for (let i = 0; i < args.length; i++) {\n const o = args[i];\n if (o !== null && typeof o === \"object\") {\n const copy = Array.isArray(o) ? [] : {};\n let n = 0;\n for (const k in o) {\n copy[k] = o[k];\n n++;\n }\n values.push(copy);\n counts.push(n);\n } else {\n values.push(o);\n counts.push(0);\n }\n }\n return {\n values,\n counts\n };\n};\n/**\n* Exact match, so a `flatHashOrNull` collision is resolved rather than trusted.\n* `bCounts` is the cached side's key count; comparing against it avoids the\n* `Object.keys()` allocation this would otherwise make on every cache hit.\n*/\nconst flatArgsEqual = (a, b, bCounts) => {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n const oa = a[i];\n const ob = b[i];\n if (oa === ob) continue;\n if (oa === null || ob === null || typeof oa !== \"object\" || typeof ob !== \"object\") return false;\n if (Array.isArray(oa) !== Array.isArray(ob)) return false;\n let n = 0;\n for (const k in oa) {\n if (oa[k] !== ob[k]) return false;\n n++;\n }\n if (n !== bCounts[i]) return false;\n }\n return true;\n};\nconst memo = (fn) => {\n let buckets = /* @__PURE__ */ new Map();\n let priorBuckets = /* @__PURE__ */ new Map();\n let strings = /* @__PURE__ */ new Map();\n let priorStrings = /* @__PURE__ */ new Map();\n /**\n * One scalar argument, keyed directly.\n *\n * This is the shape of the hottest callers — `isCssProperty(prop)` runs per prop\n * per render — and a plain map lookup beats hashing, bucket scanning and\n * snapshotting for it. Distinct types stay distinct keys, so `1` and `'1'` do not\n * share an entry.\n */\n let scalars = /* @__PURE__ */ new Map();\n let priorScalars = /* @__PURE__ */ new Map();\n const scan = (bucket, args) => {\n if (bucket) for (let i = 0; i < bucket.length; i++) {\n const entry = bucket[i];\n if (flatArgsEqual(args, entry.values, entry.counts)) return entry;\n }\n };\n const get = (...args) => {\n if (args.length === 1) {\n const only = args[0];\n if (only === null || typeof only !== \"object\") {\n if (scalars.has(only)) return scalars.get(only);\n if (priorScalars.has(only)) {\n const promoted = priorScalars.get(only);\n scalars.set(only, promoted);\n return promoted;\n }\n const out = fn(only);\n scalars.set(only, out);\n if (scalars.size > MAX_ENTRIES) {\n priorScalars = scalars;\n scalars = /* @__PURE__ */ new Map();\n }\n return out;\n }\n }\n const hash = flatHashOrNull(args);\n if (hash !== null) {\n let bucket = buckets.get(hash);\n const hit = scan(bucket, args);\n if (hit) return hit.out;\n const priorHit = scan(priorBuckets.get(hash), args);\n if (priorHit) {\n if (!bucket) {\n bucket = [];\n buckets.set(hash, bucket);\n }\n bucket.push(priorHit);\n if (bucket.length > MAX_BUCKET) bucket.shift();\n return priorHit.out;\n }\n const snap = snapshotArgs(args);\n const out = fn(...args);\n if (!bucket) {\n bucket = [];\n buckets.set(hash, bucket);\n }\n bucket.push({\n values: snap.values,\n counts: snap.counts,\n out\n });\n if (bucket.length > MAX_BUCKET) bucket.shift();\n if (buckets.size > MAX_ENTRIES) {\n priorBuckets = buckets;\n buckets = /* @__PURE__ */ new Map();\n }\n return out;\n }\n const key = JSON.stringify(args);\n if (strings.has(key)) return strings.get(key);\n if (priorStrings.has(key)) {\n const promoted = priorStrings.get(key);\n strings.set(key, promoted);\n return promoted;\n }\n const out = fn(...args);\n strings.set(key, out);\n if (strings.size > MAX_ENTRIES) {\n priorStrings = strings;\n strings = /* @__PURE__ */ new Map();\n }\n return out;\n };\n return get;\n};\n//#endregion\n//#region src/merge-props.ts\nconst MERGE_OMIT = new Set([\n \"__proto__\",\n \"constructor\",\n \"prototype\"\n]);\nfunction mergeProps(...sources) {\n return sources.reduce((prev, obj) => {\n if (!obj) return prev;\n Object.keys(obj).forEach((key) => {\n if (MERGE_OMIT.has(key)) return;\n const prevValue = prev[key];\n const value = obj[key];\n if (isObject(prevValue) && isObject(value)) prev[key] = mergeProps(prevValue, value);\n else prev[key] = value;\n });\n return prev;\n }, {});\n}\n//#endregion\n//#region src/walk-object.ts\nconst isNotNullish = (element) => element != null;\nfunction walkObject(target, predicate, options = {}) {\n const { stop, getKey } = options;\n function inner(value, path = []) {\n if (isObjectOrArray(value)) {\n const result = {};\n for (const [prop, child] of Object.entries(value)) {\n const key = getKey?.(prop, child) ?? prop;\n const childPath = [...path, key];\n if (stop?.(value, childPath)) return predicate(value, path);\n const next = inner(child, childPath);\n if (isNotNullish(next)) result[key] = next;\n }\n return result;\n }\n return predicate(value, path);\n }\n return inner(target);\n}\nfunction mapObject(obj, fn) {\n if (Array.isArray(obj)) return obj.map((value) => fn(value));\n if (!isObject(obj)) return fn(obj);\n return walkObject(obj, (value) => fn(value));\n}\n//#endregion\n//#region src/normalize-style-object.ts\nfunction toResponsiveObject(values, breakpoints) {\n return values.reduce((acc, current, index) => {\n const key = breakpoints[index];\n if (current != null) acc[key] = current;\n return acc;\n }, {});\n}\n/**\n* Whether walking the object would only rebuild it.\n*\n* Normalizing does three things: it renames a shorthand to its longhand, expands a responsive\n* array into a breakpoint object, and drops nullish leaves. A flat object of plain values\n* written in longhand needs none of them, and that is most of what `css()` is handed — but it\n* still paid for a full rebuild plus a path array per key.\n*\n* Every clause has to be exact, since a false positive returns an object the walk would have\n* changed. Nullish is one of them: a leaf the walk removes must not survive, or a later merge\n* would see it override the value beneath it. The array check is another, and it is on the\n* container as well as the values — `stop` is handed the container, so an array arriving at\n* the top level becomes a breakpoint object rather than being walked into.\n*\n* `for...in` reads inherited keys the walk ignores, which is safe in the only direction it can\n* be wrong — an extra key can send this to the slow path, never past it.\n*\n* It does read every value, as `compactStyles` and the argument memo already do, so an\n* accessor prop is read once more than before. Style props are values by the time they get\n* here and reading one has no effect, but it is the reason this cannot be reordered to read\n* lazily.\n*/\nfunction needsNoNormalizing(styles, resolveShorthand) {\n if (Array.isArray(styles)) return false;\n for (const key in styles) {\n const value = styles[key];\n if (value == null || typeof value === \"object\") return false;\n if (resolveShorthand !== void 0 && resolveShorthand(key) !== key) return false;\n }\n return true;\n}\n/**\n* The result may be the argument itself rather than a fresh object, so callers have to treat\n* it as read-only. Every one of them does today: merging accumulates into its own object and\n* the two `raw()` helpers clone at the boundary.\n*/\nfunction normalizeStyleObject(styles, context, shorthand = true) {\n const { utility, conditions } = context;\n const { hasShorthand, resolveShorthand } = utility;\n if (needsNoNormalizing(styles, shorthand && hasShorthand ? resolveShorthand : void 0)) return styles;\n return walkObject(styles, (value) => {\n return Array.isArray(value) ? toResponsiveObject(value, conditions.breakpoints.keys) : value;\n }, {\n stop: (value) => Array.isArray(value),\n getKey: shorthand ? (prop) => hasShorthand ? resolveShorthand(prop) : prop : void 0\n });\n}\n//#endregion\n//#region src/classname.ts\nconst fallbackCondition = {\n shift: (v) => v,\n finalize: (v) => v,\n breakpoints: { keys: [] }\n};\nconst ENTRY_SEP = \"]___[\";\nconst COND_SEP = \"<___>\";\n/**\n* The class a whole grouped `css()` call resolves to, given its group id.\n*\n* Shared with `StyleDecoder.collectGrouped` on purpose: both sides name this class, and\n* deriving it twice is what let `hash.className` re-hash on the build side only, leaving\n* every grouped element carrying a class no rule was emitted for.\n*\n* A group id already digests every declaration in the call, so it is hashed exactly once\n* and `hash.className` is deliberately not consulted — that option shortens *utility*\n* class names, and a grouped class is not one. The build `esc()`s the result for a\n* selector; the runtime does not. That asymmetry belongs to the callers.\n*/\nfunction groupClassName(groupId, toHashFn, formatClassName) {\n return formatClassName(toHashFn([\"grouped\", groupId], toHash));\n}\nfunction createCss(context) {\n const { utility, hash, grouped, conditions: conds = fallbackCondition } = context;\n const { prefix } = utility;\n const formatClassName = prefix ? (str) => str ? `${prefix}-${str}` : prefix : (str) => str || \"\";\n const hashFn = (conditions, className) => {\n if (hash) {\n const baseArray = [...conds.finalize(conditions), className];\n return formatClassName(utility.toHash(baseArray, toHash));\n }\n const finalized = conds.finalize(conditions);\n if (finalized.length === 0) return formatClassName(className);\n return [...finalized, formatClassName(className)].join(\":\");\n };\n /** One declaration, kept only when there is a fallback that might need to name it. */\n const atomicName = (prop, value, conditions) => {\n const important = isImportant(value);\n const className = hashFn(conditions, utility.transform(prop, withoutImportant(sanitize(value))).className);\n return important ? `${className}!` : className;\n };\n if (grouped) {\n const { knownGroups } = context;\n return memo(({ base, ...styles } = {}) => {\n const normalizedObject = normalizeStyleObject(Object.assign(styles, base), context);\n const hashes = [];\n const leaves = knownGroups ? [] : void 0;\n walkObject(normalizedObject, (value, paths) => {\n if (value == null) return;\n const [prop, ...allConditions] = conds.shift(paths);\n const conditions = filterBaseConditions(allConditions);\n const parts = [`${prop}${ENTRY_SEP}value:${value}`];\n if (conditions.length) parts.push(`cond:${conditions.join(COND_SEP)}`);\n hashes.push(parts.join(ENTRY_SEP));\n leaves?.push([\n prop,\n value,\n conditions\n ]);\n });\n if (hashes.length === 0) return \"\";\n hashes.sort();\n const className = groupClassName(hashes.join(\"|\"), utility.toHash, formatClassName);\n if (!leaves || knownGroups.has(className)) return className;\n const classNames = new Set([className]);\n for (const [prop, value, conditions] of leaves) classNames.add(atomicName(prop, value, conditions));\n return Array.from(classNames).join(\" \");\n });\n }\n return memo(({ base, ...styles } = {}) => {\n const normalizedObject = normalizeStyleObject(Object.assign(styles, base), context);\n const classNames = /* @__PURE__ */ new Set();\n walkObject(normalizedObject, (value, paths) => {\n if (value == null) return;\n const [prop, ...allConditions] = conds.shift(paths);\n classNames.add(atomicName(prop, value, filterBaseConditions(allConditions)));\n });\n return Array.from(classNames).join(\" \");\n });\n}\n/**\n* Whether a style object carries anything `compact` would have kept.\n*\n* The question `compactStyles` asks is only ever \"is this empty once undefined values are\n* dropped\", but it used to answer it by building the compacted object and then a key array\n* for it, then throwing both away. `Object.keys` enumerates exactly what `compact`'s\n* `Object.entries` did — own, enumerable, string-keyed — so this is the same predicate\n* without the two allocations, and it stops at the first value that settles it.\n*/\nfunction hasDefinedValue(style) {\n const keys = Object.keys(style);\n for (let i = 0; i < keys.length; i++) if (style[keys[i]] !== void 0) return true;\n return false;\n}\nfunction compactStyles(...styles) {\n return styles.flat().filter((style) => isObject(style) && hasDefinedValue(style));\n}\nfunction createMergeCss(context) {\n function resolve(styles) {\n const allStyles = compactStyles(...styles);\n if (allStyles.length === 1) return allStyles;\n return allStyles.map((style) => normalizeStyleObject(style, context));\n }\n function mergeCss(...styles) {\n return mergeProps(...resolve(styles));\n }\n function assignCss(...styles) {\n return Object.assign({}, ...resolve(styles));\n }\n return {\n mergeCss: memo(mergeCss),\n assignCss\n };\n}\n//#endregion\n//#region src/clone-styles.ts\nconst OMIT = new Set([\n \"__proto__\",\n \"constructor\",\n \"prototype\"\n]);\n/**\n* Independent copy of a style object, nested condition blocks included.\n*\n* Merged style objects are cached, so anything handed to user code has to be\n* copied first: a caller mutating what it received would otherwise change what\n* every later caller reads back. `css.raw()` and `cva.raw()` are those boundaries.\n*\n* Kept separate from `mergeProps` deliberately. Merging is on the hot path — it\n* runs on every `css()` cache miss and on every render of a pattern component\n* under `jsxStyleProps: 'minimal'` — and copying there charges every caller for a\n* guarantee only the two `raw()` helpers need. Measured on a realistic style\n* object (5 base properties, 4 condition blocks) that was roughly twice the cost\n* of merging alone.\n*/\nfunction cloneStyles(styles) {\n if (Array.isArray(styles)) return styles.map((value) => cloneStyles(value));\n if (!isObject(styles)) return styles;\n const out = {};\n for (const key of Object.keys(styles)) {\n if (OMIT.has(key)) continue;\n out[key] = cloneStyles(styles[key]);\n }\n return out;\n}\n//#endregion\n//#region src/compact.ts\nfunction compact(value) {\n return Object.fromEntries(Object.entries(value ?? {}).filter(([_, value]) => value !== void 0));\n}\n//#endregion\n//#region src/leaf-class.ts\n/**\n* The class a single dynamic style leaf resolves to, given the prefix its property and\n* condition path produce.\n*\n* ## Why this can exist at all\n*\n* `css()` builds a class from the value alone — `utility.transform` is string\n* construction over a static map, and nothing consults which rules were actually emitted.\n* So `css({ color: tone })` already returns `c_<tone>` for a value the extractor never\n* saw, with no CSS behind it. Reproducing that string here cannot be less correct than\n* the call it replaces; it just skips the object literal, the merge and the memo.\n*\n* ## Why it is not a template literal\n*\n* Three shapes do not reduce to `prefix + value`, and all three return `undefined` so the\n* caller runs `css()` instead:\n*\n* - An array is expanded to a responsive object by `normalizeStyleObject`, so it produces\n* one class per breakpoint rather than one class.\n* - An object is a condition block, walked into for the same reason.\n* - `null` and `undefined` are skipped by the walk entirely, which is an empty string\n* rather than a class — that one is answered here, since it needs no `css()` call.\n*\n* ## Why the character scan\n*\n* The remaining work — collapsing whitespace, stripping `!important`, turning spaces into\n* underscores — is three regexes, and paying them per call makes this *slower* than a\n* memo hit. Almost no token value contains whitespace or `!`, so one scan for the\n* characters that make any of it necessary sends the common value straight to a\n* concatenation. A false positive only costs the slow path, so the scan errs wide.\n*/\nfunction leafClass(prefix, value) {\n if (value == null) return \"\";\n const type = typeof value;\n if (type === \"number\" || type === \"boolean\") return `${prefix}${value}`;\n if (type !== \"string\") return void 0;\n const str = value;\n for (let index = 0; index < str.length; index++) {\n const code = str.charCodeAt(index);\n if (code <= 33 || code === 160 || code === 5760 || code >= 8192) return slowLeaf(prefix, str);\n }\n return `${prefix}${str}`;\n}\n/** The full pipeline `createCss` runs, for a value that needs it. */\nfunction slowLeaf(prefix, value) {\n const important = isImportant(value);\n const className = `${prefix}${withoutSpace(withoutImportant(sanitize(value)))}`;\n return important ? `${className}!` : className;\n}\n//#endregion\n//#region src/hypenate-property.ts\nconst wordRegex = /([A-Z])/g;\nconst msRegex = /^ms-/;\nconst hypenateProperty = memo((property) => {\n if (property.startsWith(\"--\")) return property;\n return property.replace(wordRegex, \"-$1\").replace(msRegex, \"-ms-\").toLowerCase();\n});\n//#endregion\n//#region src/recipe-identity.ts\n/** The fields that decide what CSS a recipe produces. Anything else is metadata. */\nconst STYLE_FIELDS = [\n \"base\",\n \"variants\",\n \"compoundVariants\",\n \"defaultVariants\"\n];\n/**\n* A serialization that depends on the config's *content* and not on how it was written.\n*\n* Object keys are sorted, so reordering two variants in the source does not rename every\n* class the recipe emits. Arrays keep their order, because `compoundVariants` is precedence\n* ordered and two orderings are two different recipes.\n*\n* A function serializes to `null` rather than to its source. `JSON.stringify` already drops\n* them, and stringifying instead would key the name on whether the bundle was minified —\n* the same build-dependent divergence that `cx` was changed to avoid. Nothing that survives\n* static extraction is a function, so there is no real config this loses information about.\n*/\nconst stable = (value) => {\n if (value === null || typeof value !== \"object\") return JSON.stringify(value) ?? \"null\";\n if (Array.isArray(value)) return `[${value.map(stable).join(\",\")}]`;\n const source = value;\n return `{${Object.keys(source).sort().map((key) => `${JSON.stringify(key)}:${stable(source[key])}`).join(\",\")}}`;\n};\n/**\n* The name an inline `cva`/`sva` emits its classes under — `button--size_sm`, where this\n* returns the `button`.\n*\n* A config recipe gets its name from the key it is declared under. An inline one has no\n* such key, and the two places that need the name never meet: the build derives it while\n* emitting the stylesheet, the runtime derives it again in the browser. So it has to come\n* from something both of them see, which leaves the config object itself.\n*\n* Deriving it from the *binding* — `const button = cva(...)` — was the obvious alternative\n* and does not work. Only the build can see that binding; handing it to the runtime means\n* rewriting the call, and then a pipeline without that transform names classes differently\n* from one with it. An optional `className` gets the same readable output with none of\n* that, because it travels inside the config to both sides.\n*\n* `className` is the field a config recipe already names itself with, and it means the same\n* thing here — the prefix every class the recipe emits is built from. An inline recipe that\n* declares one is indistinguishable in the stylesheet from a recipe declared in config.\n*/\nconst getRecipeIdentity = (config, prefix = \"cva\") => {\n const declared = config?.className;\n if (typeof declared === \"string\" && declared) return declared;\n const styles = {};\n for (const field of STYLE_FIELDS) {\n const value = config?.[field];\n if (value !== void 0) styles[field] = value;\n }\n return `${prefix}_${toHash(stable(styles))}`;\n};\n/**\n* The classes a recipe puts on an element: its own, plus one per selected variant.\n*\n* Lives here rather than in the generated `cva` because the build has to be able to check\n* it. `checkNamingAgreement` derives class names both ways and compares them, and it can\n* only do that against the code the browser actually runs — a second implementation written\n* to match would agree with itself and prove nothing.\n*\n* Compound variants are absent by design. Their rule selects on the variant classes already\n* in this list, so it applies without a class of its own.\n*/\nconst getRecipeClassNames = (name, variants, selection, separator = \"_\", format = (className) => className) => {\n let result = format(name);\n for (const variant of Object.keys(variants ?? {})) {\n const value = selection[variant];\n if (value == null) continue;\n if (variants?.[variant]?.[value] == null) continue;\n result += ` ${format(`${name}--${variant}${separator}${withoutSpace(value)}`)}`;\n }\n return result;\n};\n//#endregion\n//#region src/is-css-function.ts\nconst fnRegExp = new RegExp(`^(${[\n \"min\",\n \"max\",\n \"clamp\",\n \"calc\"\n].join(\"|\")})\\\\(.*\\\\)`);\nconst isCssFunction = (v) => typeof v === \"string\" && fnRegExp.test(v);\n//#endregion\n//#region src/is-css-unit.ts\nconst lengthUnitsPattern = `(?:${\"cm,mm,Q,in,pc,pt,px,em,ex,ch,rem,lh,rlh,vw,vh,vmin,vmax,vb,vi,svw,svh,lvw,lvh,dvw,dvh,cqw,cqh,cqi,cqb,cqmin,cqmax,%\".split(\",\").join(\"|\")})`;\nconst lengthRegExp = new RegExp(`^[+-]?[0-9]*.?[0-9]+(?:[eE][+-]?[0-9]+)?${lengthUnitsPattern}$`);\nconst isCssUnit = (v) => typeof v === \"string\" && lengthRegExp.test(v);\n//#endregion\n//#region src/is-css-var.ts\nconst isCssVar = (v) => typeof v === \"string\" && /^var\\(--.+\\)$/.test(v);\n//#endregion\n//#region src/pattern-fns.ts\nconst patternFns = {\n map: mapObject,\n isCssFunction,\n isCssVar,\n isCssUnit\n};\nconst getPatternStyles = (pattern, styles) => {\n if (!pattern?.defaultValues) return styles;\n const defaults = typeof pattern.defaultValues === \"function\" ? pattern.defaultValues(styles) : pattern.defaultValues;\n return Object.assign({}, defaults, compact(styles));\n};\n//#endregion\n//#region src/slot.ts\nconst getSlotRecipes = (recipe = {}) => {\n const init = (slot) => ({\n className: [recipe.className, slot].filter(Boolean).join(\"__\"),\n base: recipe.base?.[slot] ?? {},\n variants: {},\n defaultVariants: recipe.defaultVariants ?? {},\n compoundVariants: recipe.compoundVariants ? getSlotCompoundVariant(recipe.compoundVariants, slot) : []\n });\n const recipeParts = (recipe.slots ?? []).map((slot) => [slot, init(slot)]);\n for (const [variantsKey, variantsSpec] of Object.entries(recipe.variants ?? {})) for (const [variantKey, variantSpec] of Object.entries(variantsSpec)) recipeParts.forEach(([slot, slotRecipe]) => {\n slotRecipe.variants[variantsKey] ??= {};\n slotRecipe.variants[variantsKey][variantKey] = variantSpec[slot] ?? {};\n });\n return Object.fromEntries(recipeParts);\n};\nconst getSlotCompoundVariant = (compoundVariants, slotName) => compoundVariants.filter((compoundVariant) => compoundVariant.css[slotName]).map((compoundVariant) => ({\n ...compoundVariant,\n css: compoundVariant.css[slotName]\n}));\n//#endregion\n//#region src/split-props.ts\n/**\n* Deal a props object into one bucket per key group, plus a final bucket for the rest.\n* A key goes to the first group that claims it.\n*\n* ## Why the descriptor is read per key rather than in bulk\n*\n* This used to call `Object.getOwnPropertyDescriptors` for the whole object and\n* `defineProperty` for every key it moved. Copying plain values instead is 2.4–2.9x faster\n* on the shapes that allow it, but it is only correct where props are data — and they are\n* not always. Solid compiles props to accessors, so reading one eagerly runs whatever it\n* wraps: splitting a component's props would construct its children before the surrounding\n* provider exists.\n*\n* So the descriptor is fetched per key, and the value path is taken only when it changes\n* nothing observable. An accessor keeps its laziness, a non-enumerable key keeps its\n* invisibility, and `__proto__` is defined rather than assigned so it stays an own\n* property instead of reaching the prototype setter.\n*\n* The one thing the value path drops is `writable`/`configurable`, so a bucket key taken\n* from frozen props is writable where it used to be frozen. Nothing here relies on that,\n* and preserving it would mean `defineProperty` on the common path — the cost this exists\n* to avoid. Keys that take the descriptor path keep theirs, so a bucket can be\n* inconsistent in that one respect.\n*\n* Key order within a bucket is preserved exactly. It is not cosmetic: `cva` merges\n* variant props in iteration order, and the parser reads the rest bucket as the style\n* props it encodes, so order reaches the emitted CSS.\n*/\nfunction splitProps(props, ...keys) {\n const allKeys = Object.getOwnPropertyNames(props);\n const own = new Set(allKeys);\n const taken = /* @__PURE__ */ new Set();\n const split = (group) => {\n const clone = {};\n for (let i = 0; i < group.length; i++) {\n const key = group[i];\n if (taken.has(key) || !own.has(key)) continue;\n const descriptor = Object.getOwnPropertyDescriptor(props, key);\n if (!descriptor) continue;\n if (\"get\" in descriptor || \"set\" in descriptor || !descriptor.enumerable || key === \"__proto__\") Object.defineProperty(clone, key, descriptor);\n else clone[key] = descriptor.value;\n taken.add(key);\n }\n return clone;\n };\n /**\n * The predicate is called with the key alone.\n *\n * Handing it to `filter` passes `(key, index, allKeys)`. A one-parameter predicate cannot\n * see the extras, but a memoized one reads its whole argument list — and the predicates\n * that arrive here are memoized, `isCssProperty` among them. So the memo hashed the entire\n * key array once per prop, and keyed its cache on it: two elements with different prop sets\n * shared no entry even for the same prop name.\n *\n * Worth ~9.7x on that path, and nothing at all on a plain predicate — which is why the\n * bench below it needs a memoized case to see this at all.\n *\n * A loop rather than `filter((k) => key(k))` because the wrapper allocates a closure per\n * group. The two measure the same to within noise; the loop just does not need one.\n */\n const matching = (predicate) => {\n const group = [];\n for (let i = 0; i < allKeys.length; i++) {\n const key = allKeys[i];\n if (predicate(key)) group.push(key);\n }\n return group;\n };\n return keys.map((key) => split(Array.isArray(key) ? key : matching(key))).concat(split(allKeys));\n}\n//#endregion\n//#region src/uniq.ts\nconst uniq = (...items) => {\n const set = items.reduce((acc, currItems) => {\n if (currItems) currItems.forEach((item) => acc.add(item));\n return acc;\n }, /* @__PURE__ */ new Set([]));\n return Array.from(set);\n};\n//#endregion\n//#region src/view-transition.ts\n/**\n* The slots a `viewTransition()` bag accepts, in the order they are emitted.\n*\n* Anything else in the options object is ignored — including by the hash, so adding a\n* comment key or spreading an unrelated object does not fork the class name.\n*/\nconst viewTransitionSlots = [\n \"group\",\n \"imagePair\",\n \"old\",\n \"new\"\n];\n/**\n* Serialize a value with object keys sorted, so two style objects that differ only in\n* authoring order hash to the same class.\n*\n* Values JSON has no encoding for (`undefined`, functions, symbols) serialize as `null`\n* rather than being dropped. Nothing reaches here holding one — `filterSlots` removes\n* nullish slots, and the extractor drops nullish leaves — so this is only a floor: it\n* keeps a hole in an array from shifting the elements after it.\n*\n* Not exported. The name promises a general-purpose stable serializer, and this is not\n* one; `viewTransitionClassName` is the whole reason it exists.\n*/\nfunction stableStringify(value) {\n if (value === null) return \"null\";\n const type = typeof value;\n if (type === \"boolean\") return value ? \"true\" : \"false\";\n if (type === \"number\") return Number.isFinite(value) ? String(value) : \"null\";\n if (type === \"string\") return JSON.stringify(value);\n if (Array.isArray(value)) {\n let out = \"[\";\n for (let index = 0; index < value.length; index++) {\n if (index) out += \",\";\n out += stableStringify(value[index]);\n }\n return out + \"]\";\n }\n if (type === \"object\") {\n const keys = Object.keys(value).sort();\n let out = \"{\";\n for (let index = 0; index < keys.length; index++) {\n if (index) out += \",\";\n const key = keys[index];\n out += JSON.stringify(key) + \":\" + stableStringify(value[key]);\n }\n return out + \"}\";\n }\n return \"null\";\n}\n/**\n* Keep the four known slots, dropping any that is nullish.\n*\n* Both halves of the contract have to agree on what an empty slot is, and only one of\n* them gets a choice. The extractor evaluates the source, and a nullish property is gone\n* from what it hands over — `{ new: undefined }` and `{}` reach the build identically.\n* So absent, `undefined` and `null` collapse here too, and `new: enabled ? {…} : null`\n* hashes to the bag it actually styles.\n*/\nfunction filterSlots(options) {\n const filtered = {};\n if (!options || typeof options !== \"object\") return filtered;\n for (const slot of viewTransitionSlots) {\n const value = options[slot];\n if (value != null) filtered[slot] = value;\n }\n return filtered;\n}\n/**\n* The class a `viewTransition()` call resolves to.\n*\n* This is the whole contract between the build and the runtime: the extractor hashes the\n* options it found in the source, the generated `viewTransition()` hashes the options it\n* is called with, and the CSS only reaches the element if the two agree. They agree by\n* construction — both call this function — but only over what the extractor can see. A\n* value it cannot resolve statically is absent from its side and present on the runtime's,\n* which is why a bag has to be a static object literal to be styled at all.\n*\n* The class is used twice, as the `view-transition-class` value and as the argument to\n* `::view-transition-*(.cls)`, so the prefix applies to both.\n*/\nfunction viewTransitionClassName(options, prefix = \"\") {\n const base = \"vt_\" + toHash(stableStringify(filterSlots(options)));\n return prefix ? prefix + \"-\" + base : base;\n}\n//#endregion\nexport { cloneStyles, compact, createCss, createMergeCss, filterBaseConditions, getPatternStyles, getRecipeClassNames, getRecipeIdentity, getSlotCompoundVariant, getSlotRecipes, hypenateProperty, isBaseCondition, isObject, leafClass, mapObject, memo, mergeProps, patternFns, splitProps, toHash, uniq, viewTransitionClassName, walkObject, withoutSpace };\n";
526
+ var content$8 = "//#region src/assert.ts\nfunction isObject(value) {\n return typeof value === \"object\" && value != null && !Array.isArray(value);\n}\nconst isObjectOrArray = (obj) => typeof obj === \"object\" && obj !== null;\n//#endregion\n//#region src/condition.ts\nconst isBaseCondition = (v) => v === \"base\";\nfunction filterBaseConditions(c) {\n return c.slice().filter((v) => !isBaseCondition(v));\n}\n//#endregion\n//#region src/hash.ts\nfunction toChar(code) {\n return String.fromCharCode(code + (code > 25 ? 39 : 97));\n}\nfunction toName(code) {\n let name = \"\";\n let x;\n for (x = Math.abs(code); x > 52; x = x / 52 | 0) name = toChar(x % 52) + name;\n return toChar(x % 52) + name;\n}\nfunction toPhash(h, x) {\n let i = x.length;\n while (i) h = h * 33 ^ x.charCodeAt(--i);\n return h;\n}\nfunction toHash(value) {\n return toName(toPhash(5381, value) >>> 0);\n}\n//#endregion\n//#region src/important.ts\nconst importantRegex = /\\s*!(important)?/i;\nconst whitespaceRegex = /\\s/;\n/**\n* Collapse every run of whitespace to a single space, which is what the class name is\n* built from. Exported because `leafClass` has to reproduce this exact pipeline, and a\n* second copy of it would be free to drift from the one `createCss` runs.\n*/\nfunction sanitize(value) {\n if (typeof value !== \"string\") return value;\n const collapsed = whitespaceRegex.test(value) ? value.replaceAll(/[\\n\\s]+/g, \" \") : value;\n return collapsed.includes(\"\\0\") ? collapsed.replaceAll(\"\\0\", \"\") : collapsed;\n}\nfunction isImportant(value) {\n if (typeof value !== \"string\") return false;\n return value.includes(\"!\") && importantRegex.test(value);\n}\nfunction withoutImportant(value) {\n if (typeof value !== \"string\") return value;\n if (!value.includes(\"!\")) return value.trim();\n return value.replace(importantRegex, \"\").trim();\n}\nfunction withoutSpace(str) {\n if (typeof str !== \"string\") return str;\n return str.includes(\" \") ? str.replaceAll(\" \", \"_\") : str;\n}\n//#endregion\n//#region src/memo.ts\n/**\n* Bounded argument memo used by the generated runtime (`css`, patterns, `cva`, recipes).\n*\n* Two regimes, picked per call:\n*\n* - Arguments that are flat (objects of primitives) take a cheap structural hash\n* and are confirmed with an exact comparison, so a hash collision can never\n* serve the wrong result. This is the shape `css({ ... })` has.\n* - Anything nested falls back to `JSON.stringify`, which V8 does faster than a\n* JS walk.\n*\n* The second point is the counter-intuitive one, and it has been measured rather\n* than assumed. Extending the structural hash to recurse — so nested styles could\n* take the fast path too — is *slower*, because it trades one native serialization\n* for two JS walks (hash, then the deep equality that confirms it). Over 10k\n* iterations per shape:\n*\n* shape stringify recursive hash + deep equal\n* flat 1.06ms 2.09ms\n* _hover 1.00ms 2.16ms\n* responsive 1.23ms 2.15ms\n* realistic 2.32ms 5.84ms\n* nested 3 deep 1.22ms 2.35ms\n*\n* So a nested `css()` call costing several times a flat one is not a defect here.\n* It is the floor for a value-keyed memo in JS, and the way to avoid it is to not\n* make the call — see the build-time fold in `@bamboocss/vite`.\n*\n* Both regimes key on *values*, never on object identity: mutating a style object\n* between calls changes its hash, so the next call misses and recomputes rather\n* than serving a stale class. Keying nested arguments on the identity of the inner\n* objects would skip serialization entirely, but it cannot detect a mutation, and\n* \"same object, different contents\" is exactly what a style object built per render\n* looks like.\n*\n* Both caches are bounded. An unbounded memo is a leak in any long-lived process\n* (SSR), where the set of distinct style objects grows without limit.\n*/\n/**\n* Distinct hashes held per memoized function before the cache rotates.\n*\n* This bounds *buckets*, not entries: a bucket keeps up to `MAX_BUCKET` colliding\n* argument lists, so the ceiling is `MAX_ENTRIES * MAX_BUCKET` live entries, and\n* twice that across both generations, since the previous one is retained until the\n* next rotation. Collisions are rare in practice, so the realistic figure is close\n* to `MAX_ENTRIES` — but the worst case is what matters when sizing a long-lived\n* process, so state it plainly.\n*\n* Rotation beats evicting the oldest key: single-key eviction is worst-case for a\n* working set that cycles, because it drops exactly the entry about to be needed.\n* Measured on a cycling set of 20k styles, one-at-a-time eviction cost ~719ns/op\n* against ~189ns unbounded, while rotation holds ~274ns. On realistic skewed\n* access rotation is at or below the unbounded cost.\n*/\nconst MAX_ENTRIES = 1e3;\n/** Entries kept per hash bucket, to bound the cost of a collision scan. */\nconst MAX_BUCKET = 8;\n/**\n* DJB2 over the arguments' own keys and primitive values.\n* Returns `null` for anything nested, which routes the call to the string key.\n*/\nconst flatHashOrNull = (args) => {\n let h = 5381;\n for (let a = 0; a < args.length; a++) {\n const obj = args[a];\n if (obj === null || typeof obj !== \"object\") {\n const t = typeof obj;\n if (t === \"string\") for (let i = 0; i < obj.length; i++) h = h * 33 ^ obj.charCodeAt(i);\n else if (t === \"number\") h = h * 33 ^ (obj | 0);\n else if (t === \"boolean\") h = h * 33 ^ (obj ? 991 : 997);\n else h = h * 33 ^ 3;\n continue;\n }\n if (Array.isArray(obj)) h = h * 33 ^ 7;\n else {\n const proto = Object.getPrototypeOf(obj);\n if (proto !== Object.prototype && proto !== null) return null;\n }\n for (const k in obj) {\n const v = obj[k];\n const tv = typeof v;\n if (v !== null && tv === \"object\") return null;\n for (let i = 0; i < k.length; i++) h = h * 33 ^ k.charCodeAt(i);\n if (tv === \"string\") for (let i = 0; i < v.length; i++) h = h * 33 ^ v.charCodeAt(i);\n else if (tv === \"number\") h = h * 33 ^ (v | 0);\n else if (tv === \"boolean\") h = h * 33 ^ (v ? 991 : 997);\n else h = h * 33 ^ 2;\n }\n }\n return h >>> 0;\n};\n/**\n* Value snapshot of the arguments, taken once at insert.\n*\n* The cache must not hold the caller's objects: a style object can capture a much\n* larger graph, and keeping it alive until the cache rotates changes GC behaviour\n* for code that never asked to be cached. Only the flat path reaches here, so a\n* shallow copy contains primitives only and retains nothing.\n*\n* Comparing against a copy also removes the last way a mutation could be missed.\n* Were the caller's own object stored, `oa === ob` would short-circuit the value\n* comparison, and a mutation that happened to preserve the hash would return the\n* stale entry. Against a copy that check can only ever be true for equal\n* primitives.\n*/\nconst snapshotArgs = (args) => {\n const values = [];\n const counts = [];\n for (let i = 0; i < args.length; i++) {\n const o = args[i];\n if (o !== null && typeof o === \"object\") {\n const copy = Array.isArray(o) ? [] : {};\n let n = 0;\n for (const k in o) {\n copy[k] = o[k];\n n++;\n }\n values.push(copy);\n counts.push(n);\n } else {\n values.push(o);\n counts.push(0);\n }\n }\n return {\n values,\n counts\n };\n};\n/**\n* Exact match, so a `flatHashOrNull` collision is resolved rather than trusted.\n* `bCounts` is the cached side's key count; comparing against it avoids the\n* `Object.keys()` allocation this would otherwise make on every cache hit.\n*/\nconst flatArgsEqual = (a, b, bCounts) => {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n const oa = a[i];\n const ob = b[i];\n if (oa === ob) continue;\n if (oa === null || ob === null || typeof oa !== \"object\" || typeof ob !== \"object\") return false;\n if (Array.isArray(oa) !== Array.isArray(ob)) return false;\n let n = 0;\n for (const k in oa) {\n if (oa[k] !== ob[k]) return false;\n n++;\n }\n if (n !== bCounts[i]) return false;\n }\n return true;\n};\nconst memo = (fn) => {\n let buckets = /* @__PURE__ */ new Map();\n let priorBuckets = /* @__PURE__ */ new Map();\n let strings = /* @__PURE__ */ new Map();\n let priorStrings = /* @__PURE__ */ new Map();\n /**\n * One scalar argument, keyed directly.\n *\n * This is the shape of the hottest callers — `isCssProperty(prop)` runs per prop\n * per render — and a plain map lookup beats hashing, bucket scanning and\n * snapshotting for it. Distinct types stay distinct keys, so `1` and `'1'` do not\n * share an entry.\n */\n let scalars = /* @__PURE__ */ new Map();\n let priorScalars = /* @__PURE__ */ new Map();\n const scan = (bucket, args) => {\n if (bucket) for (let i = 0; i < bucket.length; i++) {\n const entry = bucket[i];\n if (flatArgsEqual(args, entry.values, entry.counts)) return entry;\n }\n };\n const get = (...args) => {\n if (args.length === 1) {\n const only = args[0];\n if (only === null || typeof only !== \"object\") {\n if (scalars.has(only)) return scalars.get(only);\n if (priorScalars.has(only)) {\n const promoted = priorScalars.get(only);\n scalars.set(only, promoted);\n return promoted;\n }\n const out = fn(only);\n scalars.set(only, out);\n if (scalars.size > MAX_ENTRIES) {\n priorScalars = scalars;\n scalars = /* @__PURE__ */ new Map();\n }\n return out;\n }\n }\n const hash = flatHashOrNull(args);\n if (hash !== null) {\n let bucket = buckets.get(hash);\n const hit = scan(bucket, args);\n if (hit) return hit.out;\n const priorHit = scan(priorBuckets.get(hash), args);\n if (priorHit) {\n if (!bucket) {\n bucket = [];\n buckets.set(hash, bucket);\n }\n bucket.push(priorHit);\n if (bucket.length > MAX_BUCKET) bucket.shift();\n return priorHit.out;\n }\n const snap = snapshotArgs(args);\n const out = fn(...args);\n if (!bucket) {\n bucket = [];\n buckets.set(hash, bucket);\n }\n bucket.push({\n values: snap.values,\n counts: snap.counts,\n out\n });\n if (bucket.length > MAX_BUCKET) bucket.shift();\n if (buckets.size > MAX_ENTRIES) {\n priorBuckets = buckets;\n buckets = /* @__PURE__ */ new Map();\n }\n return out;\n }\n const key = JSON.stringify(args);\n if (strings.has(key)) return strings.get(key);\n if (priorStrings.has(key)) {\n const promoted = priorStrings.get(key);\n strings.set(key, promoted);\n return promoted;\n }\n const out = fn(...args);\n strings.set(key, out);\n if (strings.size > MAX_ENTRIES) {\n priorStrings = strings;\n strings = /* @__PURE__ */ new Map();\n }\n return out;\n };\n return get;\n};\n//#endregion\n//#region src/merge-props.ts\nconst MERGE_OMIT = new Set([\n \"__proto__\",\n \"constructor\",\n \"prototype\"\n]);\nfunction mergeProps(...sources) {\n return sources.reduce((prev, obj) => {\n if (!obj) return prev;\n Object.keys(obj).forEach((key) => {\n if (MERGE_OMIT.has(key)) return;\n const prevValue = prev[key];\n const value = obj[key];\n if (isObject(prevValue) && isObject(value)) prev[key] = mergeProps(prevValue, value);\n else prev[key] = value;\n });\n return prev;\n }, {});\n}\n//#endregion\n//#region src/walk-object.ts\nconst isNotNullish = (element) => element != null;\nfunction walkObject(target, predicate, options = {}) {\n const { stop, getKey } = options;\n function inner(value, path = []) {\n if (isObjectOrArray(value)) {\n const result = {};\n for (const [prop, child] of Object.entries(value)) {\n const key = getKey?.(prop, child) ?? prop;\n const childPath = [...path, key];\n if (stop?.(value, childPath)) return predicate(value, path);\n const next = inner(child, childPath);\n if (isNotNullish(next)) result[key] = next;\n }\n return result;\n }\n return predicate(value, path);\n }\n return inner(target);\n}\nfunction mapObject(obj, fn) {\n if (Array.isArray(obj)) return obj.map((value) => fn(value));\n if (!isObject(obj)) return fn(obj);\n return walkObject(obj, (value) => fn(value));\n}\n//#endregion\n//#region src/normalize-style-object.ts\nfunction toResponsiveObject(values, breakpoints) {\n return values.reduce((acc, current, index) => {\n const key = breakpoints[index];\n if (current != null) acc[key] = current;\n return acc;\n }, {});\n}\n/**\n* Whether walking the object would only rebuild it.\n*\n* Normalizing does three things: it renames a shorthand to its longhand, expands a responsive\n* array into a breakpoint object, and drops nullish leaves. A flat object of plain values\n* written in longhand needs none of them, and that is most of what `css()` is handed — but it\n* still paid for a full rebuild plus a path array per key.\n*\n* Every clause has to be exact, since a false positive returns an object the walk would have\n* changed. Nullish is one of them: a leaf the walk removes must not survive, or a later merge\n* would see it override the value beneath it. The array check is another, and it is on the\n* container as well as the values — `stop` is handed the container, so an array arriving at\n* the top level becomes a breakpoint object rather than being walked into.\n*\n* `for...in` reads inherited keys the walk ignores, which is safe in the only direction it can\n* be wrong — an extra key can send this to the slow path, never past it.\n*\n* It does read every value, as `compactStyles` and the argument memo already do, so an\n* accessor prop is read once more than before. Style props are values by the time they get\n* here and reading one has no effect, but it is the reason this cannot be reordered to read\n* lazily.\n*/\nfunction needsNoNormalizing(styles, resolveShorthand) {\n if (Array.isArray(styles)) return false;\n for (const key in styles) {\n const value = styles[key];\n if (value == null || typeof value === \"object\") return false;\n if (resolveShorthand !== void 0 && resolveShorthand(key) !== key) return false;\n }\n return true;\n}\n/**\n* The result may be the argument itself rather than a fresh object, so callers have to treat\n* it as read-only. Every one of them does today: merging accumulates into its own object and\n* the two `raw()` helpers clone at the boundary.\n*/\nfunction normalizeStyleObject(styles, context, shorthand = true) {\n const { utility, conditions } = context;\n const { hasShorthand, resolveShorthand } = utility;\n if (needsNoNormalizing(styles, shorthand && hasShorthand ? resolveShorthand : void 0)) return styles;\n return walkObject(styles, (value) => {\n return Array.isArray(value) ? toResponsiveObject(value, conditions.breakpoints.keys) : value;\n }, {\n stop: (value) => Array.isArray(value),\n getKey: shorthand ? (prop) => hasShorthand ? resolveShorthand(prop) : prop : void 0\n });\n}\n//#endregion\n//#region src/classname.ts\nconst fallbackCondition = {\n shift: (v) => v,\n finalize: (v) => v,\n breakpoints: { keys: [] }\n};\nconst ENTRY_SEP = \"]___[\";\nconst COND_SEP = \"<___>\";\n/**\n* The class a whole grouped `css()` call resolves to, given its group id.\n*\n* Shared with `StyleDecoder.collectGrouped` on purpose: both sides name this class, and\n* deriving it twice is what let `hash.className` re-hash on the build side only, leaving\n* every grouped element carrying a class no rule was emitted for.\n*\n* A group id already digests every declaration in the call, so it is hashed exactly once\n* and `hash.className` is deliberately not consulted — that option shortens *utility*\n* class names, and a grouped class is not one. The build `esc()`s the result for a\n* selector; the runtime does not. That asymmetry belongs to the callers.\n*/\nfunction groupClassName(groupId, toHashFn, formatClassName) {\n return formatClassName(toHashFn([\"grouped\", groupId], toHash));\n}\nfunction createCss(context) {\n const { utility, hash, grouped, conditions: conds = fallbackCondition } = context;\n const { prefix } = utility;\n const formatClassName = prefix ? (str) => str ? `${prefix}-${str}` : prefix : (str) => str || \"\";\n const hashFn = (conditions, className) => {\n if (hash) {\n const baseArray = [...conds.finalize(conditions), className];\n return formatClassName(utility.toHash(baseArray, toHash));\n }\n const finalized = conds.finalize(conditions);\n if (finalized.length === 0) return formatClassName(className);\n return [...finalized, formatClassName(className)].join(\":\");\n };\n /** One declaration, kept only when there is a fallback that might need to name it. */\n const atomicName = (prop, value, conditions) => {\n const important = isImportant(value);\n const className = hashFn(conditions, utility.transform(prop, withoutImportant(sanitize(value))).className);\n return important ? `${className}!` : className;\n };\n if (grouped) {\n const { knownGroups } = context;\n return memo(({ base, ...styles } = {}) => {\n const normalizedObject = normalizeStyleObject(Object.assign(styles, base), context);\n const hashes = [];\n const leaves = knownGroups ? [] : void 0;\n walkObject(normalizedObject, (value, paths) => {\n if (value == null) return;\n const [prop, ...allConditions] = conds.shift(paths);\n const conditions = filterBaseConditions(allConditions);\n const parts = [`${prop}${ENTRY_SEP}value:${value}`];\n if (conditions.length) parts.push(`cond:${conditions.join(COND_SEP)}`);\n hashes.push(parts.join(ENTRY_SEP));\n leaves?.push([\n prop,\n value,\n conditions\n ]);\n });\n if (hashes.length === 0) return \"\";\n hashes.sort();\n const className = groupClassName(hashes.join(\"|\"), utility.toHash, formatClassName);\n if (!leaves || knownGroups.has(className)) return className;\n const classNames = new Set([className]);\n for (const [prop, value, conditions] of leaves) classNames.add(atomicName(prop, value, conditions));\n return Array.from(classNames).join(\" \");\n });\n }\n return memo(({ base, ...styles } = {}) => {\n const normalizedObject = normalizeStyleObject(Object.assign(styles, base), context);\n const classNames = /* @__PURE__ */ new Set();\n walkObject(normalizedObject, (value, paths) => {\n if (value == null) return;\n const [prop, ...allConditions] = conds.shift(paths);\n classNames.add(atomicName(prop, value, filterBaseConditions(allConditions)));\n });\n return Array.from(classNames).join(\" \");\n });\n}\n/**\n* Whether a style object carries anything `compact` would have kept.\n*\n* The question `compactStyles` asks is only ever \"is this empty once undefined values are\n* dropped\", but it used to answer it by building the compacted object and then a key array\n* for it, then throwing both away. `Object.keys` enumerates exactly what `compact`'s\n* `Object.entries` did — own, enumerable, string-keyed — so this is the same predicate\n* without the two allocations, and it stops at the first value that settles it.\n*/\nfunction hasDefinedValue(style) {\n const keys = Object.keys(style);\n for (let i = 0; i < keys.length; i++) if (style[keys[i]] !== void 0) return true;\n return false;\n}\nfunction compactStyles(...styles) {\n return styles.flat().filter((style) => isObject(style) && hasDefinedValue(style));\n}\nfunction createMergeCss(context) {\n function resolve(styles) {\n const allStyles = compactStyles(...styles);\n if (allStyles.length === 1) return allStyles;\n return allStyles.map((style) => normalizeStyleObject(style, context));\n }\n function mergeCss(...styles) {\n return mergeProps(...resolve(styles));\n }\n function assignCss(...styles) {\n return Object.assign({}, ...resolve(styles));\n }\n return {\n mergeCss: memo(mergeCss),\n assignCss,\n mergeCssUncached: mergeCss\n };\n}\n//#endregion\n//#region src/clone-styles.ts\nconst OMIT = new Set([\n \"__proto__\",\n \"constructor\",\n \"prototype\"\n]);\n/**\n* Independent copy of a style object, nested condition blocks included.\n*\n* Merged style objects are cached, so anything handed to user code has to be\n* copied first: a caller mutating what it received would otherwise change what\n* every later caller reads back. `css.raw()` and `cva.raw()` are those boundaries.\n*\n* Kept separate from `mergeProps` deliberately. Merging is on the hot path — it\n* runs on every `css()` cache miss and on every render of a pattern component\n* under `jsxStyleProps: 'minimal'` — and copying there charges every caller for a\n* guarantee only the two `raw()` helpers need. Measured on a realistic style\n* object (5 base properties, 4 condition blocks) that was roughly twice the cost\n* of merging alone.\n*/\nfunction cloneStyles(styles) {\n if (Array.isArray(styles)) return styles.map((value) => cloneStyles(value));\n if (!isObject(styles)) return styles;\n const out = {};\n for (const key of Object.keys(styles)) {\n if (OMIT.has(key)) continue;\n out[key] = cloneStyles(styles[key]);\n }\n return out;\n}\n//#endregion\n//#region src/compact.ts\nfunction compact(value) {\n return Object.fromEntries(Object.entries(value ?? {}).filter(([_, value]) => value !== void 0));\n}\n//#endregion\n//#region src/leaf-class.ts\n/**\n* The class a single dynamic style leaf resolves to, given the prefix its property and\n* condition path produce.\n*\n* ## Why this can exist at all\n*\n* `css()` builds a class from the value alone — `utility.transform` is string\n* construction over a static map, and nothing consults which rules were actually emitted.\n* So `css({ color: tone })` already returns `c_<tone>` for a value the extractor never\n* saw, with no CSS behind it. Reproducing that string here cannot be less correct than\n* the call it replaces; it just skips the object literal, the merge and the memo.\n*\n* ## Why it is not a template literal\n*\n* Three shapes do not reduce to `prefix + value`, and all three return `undefined` so the\n* caller runs `css()` instead:\n*\n* - An array is expanded to a responsive object by `normalizeStyleObject`, so it produces\n* one class per breakpoint rather than one class.\n* - An object is a condition block, walked into for the same reason.\n* - `null` and `undefined` are skipped by the walk entirely, which is an empty string\n* rather than a class — that one is answered here, since it needs no `css()` call.\n*\n* ## Why the character scan\n*\n* The remaining work — collapsing whitespace, stripping `!important`, turning spaces into\n* underscores — is three regexes, and paying them per call makes this *slower* than a\n* memo hit. Almost no token value contains whitespace or `!`, so one scan for the\n* characters that make any of it necessary sends the common value straight to a\n* concatenation. A false positive only costs the slow path, so the scan errs wide.\n*/\nfunction leafClass(prefix, value) {\n if (value == null) return \"\";\n const type = typeof value;\n if (type === \"number\" || type === \"boolean\") return `${prefix}${value}`;\n if (type !== \"string\") return void 0;\n const str = value;\n for (let index = 0; index < str.length; index++) {\n const code = str.charCodeAt(index);\n if (code <= 33 || code === 160 || code === 5760 || code >= 8192) return slowLeaf(prefix, str);\n }\n return `${prefix}${str}`;\n}\n/** The full pipeline `createCss` runs, for a value that needs it. */\nfunction slowLeaf(prefix, value) {\n const important = isImportant(value);\n const className = `${prefix}${withoutSpace(withoutImportant(sanitize(value)))}`;\n return important ? `${className}!` : className;\n}\n//#endregion\n//#region src/hypenate-property.ts\nconst wordRegex = /([A-Z])/g;\nconst msRegex = /^ms-/;\nconst hypenateProperty = memo((property) => {\n if (property.startsWith(\"--\")) return property;\n return property.replace(wordRegex, \"-$1\").replace(msRegex, \"-ms-\").toLowerCase();\n});\n//#endregion\n//#region src/recipe-identity.ts\n/**\n* The fields that decide what CSS a recipe produces. Anything else is metadata.\n*\n* `slots` and `scopeRoots` count. They do not change a declaration, but they change the\n* *shape* of what is emitted — which slots exist, and whether a slot's variants become\n* `@scope` rules or a class of its own. Two `sva`s differing only in `scopeRoots` hashed to\n* one name, and since an inline recipe is registered once, whichever was extracted first\n* decided the emission for both; the other's runtime then asked for classes no rule\n* existed under. \"Same styles, different DOM topology\" is exactly what `scopeRoots` is for,\n* so it is the collision most likely to happen.\n*/\nconst STYLE_FIELDS = [\n \"base\",\n \"variants\",\n \"compoundVariants\",\n \"defaultVariants\",\n \"slots\",\n \"scopeRoots\"\n];\n/**\n* A serialization that depends on the config's *content* and not on how it was written.\n*\n* Object keys are sorted, so reordering two variants in the source does not rename every\n* class the recipe emits. Arrays keep their order, because `compoundVariants` is precedence\n* ordered and two orderings are two different recipes.\n*\n* A function serializes to `null` rather than to its source. `JSON.stringify` already drops\n* them, and stringifying instead would key the name on whether the bundle was minified —\n* the same build-dependent divergence that `cx` was changed to avoid. Nothing that survives\n* static extraction is a function, so there is no real config this loses information about.\n*/\nconst stable = (value) => {\n if (value === null || typeof value !== \"object\") return JSON.stringify(value) ?? \"null\";\n if (Array.isArray(value)) return `[${value.map(stable).join(\",\")}]`;\n const source = value;\n return `{${Object.keys(source).sort().map((key) => `${JSON.stringify(key)}:${stable(source[key])}`).join(\",\")}}`;\n};\n/**\n* The name an inline `cva`/`sva` emits its classes under — `button--size_sm`, where this\n* returns the `button`.\n*\n* A config recipe gets its name from the key it is declared under. An inline one has no\n* such key, and the two places that need the name never meet: the build derives it while\n* emitting the stylesheet, the runtime derives it again in the browser. So it has to come\n* from something both of them see, which leaves the config object itself.\n*\n* Deriving it from the *binding* — `const button = cva(...)` — was the obvious alternative\n* and does not work. Only the build can see that binding; handing it to the runtime means\n* rewriting the call, and then a pipeline without that transform names classes differently\n* from one with it. An optional `className` gets the same readable output with none of\n* that, because it travels inside the config to both sides.\n*\n* `className` is the field a config recipe already names itself with, and it means the same\n* thing here — the prefix every class the recipe emits is built from. An inline recipe that\n* declares one is indistinguishable in the stylesheet from a recipe declared in config.\n*/\nconst getRecipeIdentity = (config, prefix = \"cva\") => {\n const declared = config?.className;\n if (typeof declared === \"string\" && declared) return declared;\n const styles = {};\n for (const field of STYLE_FIELDS) {\n const value = config?.[field];\n if (value !== void 0) styles[field] = value;\n }\n return `${prefix}_${toHash(stable(styles))}`;\n};\n/**\n* The classes a recipe puts on an element: its own, plus one per selected variant.\n*\n* Lives here rather than in the generated `cva` because the build has to be able to check\n* it. `checkNamingAgreement` derives class names both ways and compares them, and it can\n* only do that against the code the browser actually runs — a second implementation written\n* to match would agree with itself and prove nothing.\n*\n* Compound variants are absent by design. Their rule selects on the variant classes already\n* in this list, so it applies without a class of its own.\n*/\nconst getRecipeClassNames = (name, variants, selection, separator = \"_\", format = (className) => className) => {\n let result = format(name);\n for (const variant of Object.keys(variants ?? {})) {\n const value = selection[variant];\n if (value == null) continue;\n if (variants?.[variant]?.[value] == null) continue;\n result += ` ${format(`${name}--${variant}${separator}${withoutSpace(value)}`)}`;\n }\n return result;\n};\n//#endregion\n//#region src/is-css-function.ts\nconst fnRegExp = new RegExp(`^(${[\n \"min\",\n \"max\",\n \"clamp\",\n \"calc\"\n].join(\"|\")})\\\\(.*\\\\)`);\nconst isCssFunction = (v) => typeof v === \"string\" && fnRegExp.test(v);\n//#endregion\n//#region src/is-css-unit.ts\nconst lengthUnitsPattern = `(?:${\"cm,mm,Q,in,pc,pt,px,em,ex,ch,rem,lh,rlh,vw,vh,vmin,vmax,vb,vi,svw,svh,lvw,lvh,dvw,dvh,cqw,cqh,cqi,cqb,cqmin,cqmax,%\".split(\",\").join(\"|\")})`;\nconst lengthRegExp = new RegExp(`^[+-]?[0-9]*.?[0-9]+(?:[eE][+-]?[0-9]+)?${lengthUnitsPattern}$`);\nconst isCssUnit = (v) => typeof v === \"string\" && lengthRegExp.test(v);\n//#endregion\n//#region src/is-css-var.ts\nconst isCssVar = (v) => typeof v === \"string\" && /^var\\(--.+\\)$/.test(v);\n//#endregion\n//#region src/pattern-fns.ts\nconst patternFns = {\n map: mapObject,\n isCssFunction,\n isCssVar,\n isCssUnit\n};\nconst getPatternStyles = (pattern, styles) => {\n if (!pattern?.defaultValues) return styles;\n const defaults = typeof pattern.defaultValues === \"function\" ? pattern.defaultValues(styles) : pattern.defaultValues;\n return Object.assign({}, defaults, compact(styles));\n};\n//#endregion\n//#region src/slot.ts\nconst getSlotRecipes = (recipe = {}) => {\n const init = (slot) => ({\n className: [recipe.className, slot].filter(Boolean).join(\"__\"),\n base: recipe.base?.[slot] ?? {},\n variants: {},\n defaultVariants: recipe.defaultVariants ?? {},\n compoundVariants: recipe.compoundVariants ? getSlotCompoundVariant(recipe.compoundVariants, slot) : []\n });\n const recipeParts = (recipe.slots ?? []).map((slot) => [slot, init(slot)]);\n for (const [variantsKey, variantsSpec] of Object.entries(recipe.variants ?? {})) for (const [variantKey, variantSpec] of Object.entries(variantsSpec)) recipeParts.forEach(([slot, slotRecipe]) => {\n slotRecipe.variants[variantsKey] ??= {};\n slotRecipe.variants[variantsKey][variantKey] = variantSpec[slot] ?? {};\n });\n return Object.fromEntries(recipeParts);\n};\nconst getSlotCompoundVariant = (compoundVariants, slotName) => compoundVariants.filter((compoundVariant) => compoundVariant?.css?.[slotName]).map((compoundVariant) => ({\n ...compoundVariant,\n css: compoundVariant.css[slotName]\n}));\n//#endregion\n//#region src/split-props.ts\n/**\n* Move one key into a bucket, keeping whatever about it is observable.\n*\n* Shared by both paths below so there is one implementation of the descriptor rules rather\n* than two to keep in step. The rules themselves are documented on `splitProps`.\n*/\nconst copyKey = (props, target, key) => {\n const descriptor = Object.getOwnPropertyDescriptor(props, key);\n if (!descriptor) return false;\n if (\"get\" in descriptor || \"set\" in descriptor || !descriptor.enumerable || key === \"__proto__\") Object.defineProperty(target, key, descriptor);\n else target[key] = descriptor.value;\n return true;\n};\n/**\n* One array group, which is what every call site in this project passes — a recipe's\n* `variantKeys`.\n*\n* The general path below is built for several groups that may be predicates, and pays for\n* that shape on every call: a closure per group, a `map` and a `concat` to assemble the\n* result, and a branch per group to tell an array from a predicate. None of it is reachable\n* with one array group.\n*\n* What it does *not* skip is the part that looks skippable. `own` stays, because membership\n* has to be answered from `ownKeys` rather than by asking the object: on a proxy — which is\n* what Solid's `mergeProps` hands over — every question is a trap, and a recipe naming eight\n* variants would otherwise fire eight traps to learn what one `ownKeys` already said. And\n* the two passes stay separate, because the group bucket is in *group* order while the rest\n* bucket is in *props* order, and that ordering reaches the emitted CSS.\n*/\nconst splitOneGroup = (props, allKeys, group) => {\n const own = new Set(allKeys);\n const taken = /* @__PURE__ */ new Set();\n const picked = {};\n for (let i = 0; i < group.length; i++) {\n const key = group[i];\n if (taken.has(key) || !own.has(key)) continue;\n if (copyKey(props, picked, key)) taken.add(key);\n }\n const rest = {};\n for (let i = 0; i < allKeys.length; i++) {\n const key = allKeys[i];\n if (taken.has(key)) continue;\n copyKey(props, rest, key);\n }\n return [picked, rest];\n};\n/**\n* Deal a props object into one bucket per key group, plus a final bucket for the rest.\n* A key goes to the first group that claims it.\n*\n* ## Why the descriptor is read per key rather than in bulk\n*\n* This used to call `Object.getOwnPropertyDescriptors` for the whole object and\n* `defineProperty` for every key it moved. Copying plain values instead is 2.4–2.9x faster\n* on the shapes that allow it, but it is only correct where props are data — and they are\n* not always. Solid compiles props to accessors, so reading one eagerly runs whatever it\n* wraps: splitting a component's props would construct its children before the surrounding\n* provider exists.\n*\n* So the descriptor is fetched per key, and the value path is taken only when it changes\n* nothing observable. An accessor keeps its laziness, a non-enumerable key keeps its\n* invisibility, and `__proto__` is defined rather than assigned so it stays an own\n* property instead of reaching the prototype setter.\n*\n* The one thing the value path drops is `writable`/`configurable`, so a bucket key taken\n* from frozen props is writable where it used to be frozen. Nothing here relies on that,\n* and preserving it would mean `defineProperty` on the common path — the cost this exists\n* to avoid. Keys that take the descriptor path keep theirs, so a bucket can be\n* inconsistent in that one respect.\n*\n* Key order within a bucket is preserved exactly. It is not cosmetic: `cva` merges\n* variant props in iteration order, and the parser reads the rest bucket as the style\n* props it encodes, so order reaches the emitted CSS.\n*/\nfunction splitProps(props, ...keys) {\n const allKeys = Object.getOwnPropertyNames(props);\n if (keys.length === 1 && Array.isArray(keys[0])) return splitOneGroup(props, allKeys, keys[0]);\n const own = new Set(allKeys);\n const taken = /* @__PURE__ */ new Set();\n const split = (group) => {\n const clone = {};\n for (let i = 0; i < group.length; i++) {\n const key = group[i];\n if (taken.has(key) || !own.has(key)) continue;\n if (copyKey(props, clone, key)) taken.add(key);\n }\n return clone;\n };\n /**\n * The predicate is called with the key alone.\n *\n * Handing it to `filter` passes `(key, index, allKeys)`. A one-parameter predicate cannot\n * see the extras, but a memoized one reads its whole argument list — and the predicates\n * that arrive here are memoized, `isCssProperty` among them. So the memo hashed the entire\n * key array once per prop, and keyed its cache on it: two elements with different prop sets\n * shared no entry even for the same prop name.\n *\n * Worth ~9.7x on that path, and nothing at all on a plain predicate — which is why the\n * bench below it needs a memoized case to see this at all.\n *\n * A loop rather than `filter((k) => key(k))` because the wrapper allocates a closure per\n * group. The two measure the same to within noise; the loop just does not need one.\n */\n const matching = (predicate) => {\n const group = [];\n for (let i = 0; i < allKeys.length; i++) {\n const key = allKeys[i];\n if (predicate(key)) group.push(key);\n }\n return group;\n };\n return keys.map((key) => split(Array.isArray(key) ? key : matching(key))).concat(split(allKeys));\n}\n//#endregion\n//#region src/uniq.ts\nconst uniq = (...items) => {\n const set = items.reduce((acc, currItems) => {\n if (currItems) currItems.forEach((item) => acc.add(item));\n return acc;\n }, /* @__PURE__ */ new Set([]));\n return Array.from(set);\n};\n//#endregion\n//#region src/view-transition.ts\n/**\n* The slots a `viewTransition()` bag accepts, in the order they are emitted.\n*\n* Anything else in the options object is ignored — including by the hash, so adding a\n* comment key or spreading an unrelated object does not fork the class name.\n*/\nconst viewTransitionSlots = [\n \"group\",\n \"imagePair\",\n \"old\",\n \"new\"\n];\n/**\n* Serialize a value with object keys sorted, so two style objects that differ only in\n* authoring order hash to the same class.\n*\n* Values JSON has no encoding for (`undefined`, functions, symbols) serialize as `null`\n* rather than being dropped. Nothing reaches here holding one — `filterSlots` removes\n* nullish slots, and the extractor drops nullish leaves — so this is only a floor: it\n* keeps a hole in an array from shifting the elements after it.\n*\n* Not exported. The name promises a general-purpose stable serializer, and this is not\n* one; `viewTransitionClassName` is the whole reason it exists.\n*/\nfunction stableStringify(value) {\n if (value === null) return \"null\";\n const type = typeof value;\n if (type === \"boolean\") return value ? \"true\" : \"false\";\n if (type === \"number\") return Number.isFinite(value) ? String(value) : \"null\";\n if (type === \"string\") return JSON.stringify(value);\n if (Array.isArray(value)) {\n let out = \"[\";\n for (let index = 0; index < value.length; index++) {\n if (index) out += \",\";\n out += stableStringify(value[index]);\n }\n return out + \"]\";\n }\n if (type === \"object\") {\n const keys = Object.keys(value).sort();\n let out = \"{\";\n for (let index = 0; index < keys.length; index++) {\n if (index) out += \",\";\n const key = keys[index];\n out += JSON.stringify(key) + \":\" + stableStringify(value[key]);\n }\n return out + \"}\";\n }\n return \"null\";\n}\n/**\n* Keep the four known slots, dropping any that is nullish.\n*\n* Both halves of the contract have to agree on what an empty slot is, and only one of\n* them gets a choice. The extractor evaluates the source, and a nullish property is gone\n* from what it hands over — `{ new: undefined }` and `{}` reach the build identically.\n* So absent, `undefined` and `null` collapse here too, and `new: enabled ? {…} : null`\n* hashes to the bag it actually styles.\n*/\nfunction filterSlots(options) {\n const filtered = {};\n if (!options || typeof options !== \"object\") return filtered;\n for (const slot of viewTransitionSlots) {\n const value = options[slot];\n if (value != null) filtered[slot] = value;\n }\n return filtered;\n}\n/**\n* The class a `viewTransition()` call resolves to.\n*\n* This is the whole contract between the build and the runtime: the extractor hashes the\n* options it found in the source, the generated `viewTransition()` hashes the options it\n* is called with, and the CSS only reaches the element if the two agree. They agree by\n* construction — both call this function — but only over what the extractor can see. A\n* value it cannot resolve statically is absent from its side and present on the runtime's,\n* which is why a bag has to be a static object literal to be styled at all.\n*\n* The class is used twice, as the `view-transition-class` value and as the argument to\n* `::view-transition-*(.cls)`, so the prefix applies to both.\n*/\nfunction viewTransitionClassName(options, prefix = \"\") {\n const base = \"vt_\" + toHash(stableStringify(filterSlots(options)));\n return prefix ? prefix + \"-\" + base : base;\n}\n//#endregion\nexport { cloneStyles, compact, createCss, createMergeCss, filterBaseConditions, getPatternStyles, getRecipeClassNames, getRecipeIdentity, getSlotCompoundVariant, getSlotRecipes, hypenateProperty, isBaseCondition, isObject, leafClass, mapObject, memo, mergeProps, patternFns, splitProps, toHash, uniq, viewTransitionClassName, walkObject, withoutSpace };\n";
472
527
  //#endregion
473
528
  //#region src/artifacts/js/helpers.ts
474
529
  function generateHelpers() {
@@ -669,7 +724,19 @@ function generateCreateRecipe(ctx) {
669
724
  ${ctx.file.import("finalizeConditions, sortConditions", "../css/conditions")}
670
725
  ${ctx.file.import("assertCompoundVariant, getCompoundVariantCss", "../css/cva")}
671
726
  ${ctx.file.import("cx", "../css/cx")}
672
- ${ctx.file.import("compact, createCss, splitProps, uniq, withoutSpace", "../helpers")}
727
+ ${ctx.file.import("compact, createCss, splitProps, toHash, uniq, withoutSpace", "../helpers")}
728
+
729
+ /**
730
+ * What \`createCss\` does to a class name: prefix it, and hash it when \`hash.className\`
731
+ * is set.
732
+ *
733
+ * A slot that takes variants gets this for free, because its classes come from
734
+ * \`createCss\`. A *scoped* slot's class never goes through it — it is a constant — so it
735
+ * has to be formatted here or the runtime hands back a raw name while the stylesheet
736
+ * emits the rule under a hashed one, and the slot renders unstyled.
737
+ */
738
+ const withPrefix = ${prefix.className ? `(className) => className ? ${JSON.stringify(prefix.className)} + '-' + className : ${JSON.stringify(prefix.className)}` : `(className) => className`}
739
+ export const formatRecipeClass = ${hash.className ? `(className) => withPrefix((${utility.toHash})([className], toHash))` : `withPrefix`}
673
740
 
674
741
  export const createRecipe = (name, defaultVariants, compoundVariants) => {
675
742
  const getVariantProps = (variants) => {
@@ -780,12 +847,17 @@ function generateRecipes(ctx, filters) {
780
847
  const slotsAffectedBy = Object.fromEntries(Object.entries(config.variants ?? {}).map(([variant, values]) => [variant, Array.from(new Set(Object.values(values ?? {}).flatMap((slotStyles) => Object.keys(slotStyles ?? {}))))]));
781
848
  return outdent.outdent`
782
849
  ${ctx.file.import("compact, getSlotCompoundVariant, memo, splitProps", "../helpers")}
783
- ${ctx.file.import("createRecipe", "./create-recipe")}
850
+ ${ctx.file.import("createRecipe, formatRecipeClass", "./create-recipe")}
784
851
 
785
852
  const ${baseName}DefaultVariants = ${stringify$2(defaultVariants ?? {})}
786
853
  const ${baseName}CompoundVariants = ${stringify$2(compoundVariants ?? [])}
787
854
 
788
- const ${baseName}SlotNames = ${stringify$2(config.slots.map((slot) => [slot, `${config.className}__${slot}`]))}
855
+ // Formatted, not raw. A scoped slot's class is a constant that never passes through
856
+ // \`createCss\`, so \`hash.className\` and \`prefix\` have to be applied here to match
857
+ // the rule the stylesheet emits.
858
+ const ${baseName}SlotNames = ${stringify$2(config.slots.map((slot) => [slot, `${config.className}__${slot}`]))}.map(
859
+ ([slotName, className]) => [slotName, formatRecipeClass(className)],
860
+ )
789
861
  ${anchors.length ? outdent.outdent`
790
862
  /**
791
863
  * Only the anchors take variants: ${anchors.map((slot) => `\`${baseName}.${slot}\``).join(", ")}.
@@ -911,7 +983,7 @@ function generateSvaFn(ctx) {
911
983
  return {
912
984
  js: outdent.outdent`
913
985
  ${ctx.file.import("compact, getRecipeIdentity, getSlotRecipes, memo, splitProps", "../helpers")}
914
- ${ctx.file.import("cva", "./cva")}
986
+ ${ctx.file.import("cva, formatRecipeClass", "./cva")}
915
987
  ${ctx.file.import("cx", "./cx")}
916
988
 
917
989
  export function sva(config) {
@@ -929,8 +1001,12 @@ function generateSvaFn(ctx) {
929
1001
  // one before the split — the identity when the config declares none — so the guard
930
1002
  // that used to sit here left an anonymous \`sva\` reporting no slot classes despite
931
1003
  // emitting them.
1004
+ // Formatted, like the classes \`svaFn\` returns. Left raw, this reported the name the
1005
+ // element does *not* carry under \`hash\` or \`prefix\` — and \`auditSlotScopes\` builds
1006
+ // its selectors from this map, so the diagnostic went silent in exactly the configs
1007
+ // where a naming bug is likeliest.
932
1008
  const classNameMap = slots.reduce((acc, [slot, cvaFn]) => {
933
- acc[slot] = cvaFn.config.className
1009
+ acc[slot] = formatRecipeClass(cvaFn.config.className)
934
1010
  return acc
935
1011
  }, {})
936
1012
 
@@ -951,7 +1027,7 @@ function generateSvaFn(ctx) {
951
1027
  function svaFn(props) {
952
1028
  const result = slots.map(([slot, cvaFn]) => [
953
1029
  slot,
954
- anchors.length && !anchors.includes(slot) ? cvaFn.config.className : cvaFn(props),
1030
+ anchors.length && !anchors.includes(slot) ? formatRecipeClass(cvaFn.config.className) : cvaFn(props),
955
1031
  ])
956
1032
  return Object.fromEntries(result)
957
1033
  }
@@ -2346,19 +2422,38 @@ function transformSegment(seg) {
2346
2422
  const parent = (0, _bamboocss_core.extractParentSelectors)(seg);
2347
2423
  return parent ? `&${parent}` : seg;
2348
2424
  }
2425
+ /**
2426
+ * Build the nesting chain for one condition path.
2427
+ *
2428
+ * The outermost node is a `Root`, so the first segment has no enclosing rule and
2429
+ * its `&` refers to nothing — it is resolved away here rather than by
2430
+ * postcss-nested. Deeper segments keep their `&` and nest against the real
2431
+ * parent selector as before.
2432
+ *
2433
+ * This used to be seeded with an empty-selector rule and leaned on
2434
+ * postcss-nested to erase `&` against it. postcss 8.5.25 ("Fixed 8.5.17 visitor
2435
+ * regression") changed that edge case to collapse the whole selector, so every
2436
+ * conditional token was emitted as a selectorless — and therefore discarded —
2437
+ * rule, leaving only the `base` value in the tokens layer.
2438
+ */
2349
2439
  function getDeepestRule(root, selectors) {
2350
- const rule = postcss.default.rule({ selector: "" });
2440
+ const container = postcss.default.root();
2351
2441
  for (const selector of selectors) {
2352
- const node = getDeepestNode(rule) ?? rule;
2442
+ const node = getDeepestNode(container);
2443
+ const isTopLevel = node === container;
2353
2444
  if (selector.startsWith("@")) {
2445
+ const inner = isTopLevel ? root : `${root}&`;
2354
2446
  const atRule = postcss.default.rule({
2355
2447
  selector,
2356
- nodes: [postcss.default.rule({ selector: `${root}&` })]
2448
+ nodes: [postcss.default.rule({ selector: inner })]
2357
2449
  });
2358
2450
  node.append(atRule);
2359
- } else node.append(postcss.default.rule({ selector }));
2451
+ } else node.append(postcss.default.rule({ selector: isTopLevel ? withoutParentSelector(selector) : selector }));
2360
2452
  }
2361
- return rule;
2453
+ return container;
2454
+ }
2455
+ function withoutParentSelector(selector) {
2456
+ return selector.replaceAll("&", "").trim();
2362
2457
  }
2363
2458
  function getDeepestNode(node) {
2364
2459
  if (node.nodes && node.nodes.length) return getDeepestNode(node.nodes[node.nodes.length - 1]);
package/dist/index.mjs CHANGED
@@ -200,8 +200,12 @@ function generateCssFn(ctx) {
200
200
  }
201
201
 
202
202
  const cssFn = createCss(context)
203
- export const css = /* @__PURE__ */ memo((...styles) => cssFn(mergeCss(...styles)))
204
- // The merged result is cached and shared, so a caller mutating a nested
203
+ // \`mergeCssUncached\` rather than \`mergeCss\`: this callback runs only when the memo
204
+ // above it missed, and a miss means these arguments have not been seen — so a second
205
+ // cache keyed on the same arguments can only miss too, after paying for the lookup.
206
+ export const css = /* @__PURE__ */ memo((...styles) => cssFn(mergeCssUncached(...styles)))
207
+ // The cached merge here, since \`raw\` is called straight from user code with no memo
208
+ // above it. The merged result is cached and shared, so a caller mutating a nested
205
209
  // condition object would otherwise poison it for everyone after them.
206
210
  css.raw = (...styles) => cloneStyles(mergeCss(...styles))
207
211
 
@@ -225,7 +229,7 @@ function generateCssFn(ctx) {
225
229
  // still returns a class, exactly as \`css()\` does for a value it never saw.
226
230
  export const viewTransition = (options) => viewTransitionClassName(options, ${JSON.stringify(prefix.className ?? "")})
227
231
 
228
- export const { mergeCss, assignCss } = createMergeCss(context)
232
+ export const { mergeCss, assignCss, mergeCssUncached } = createMergeCss(context)
229
233
  `
230
234
  };
231
235
  }
@@ -238,12 +242,13 @@ function generateCvaFn(ctx) {
238
242
  js: outdent`
239
243
  ${ctx.file.import("cloneStyles, compact, getRecipeClassNames, getRecipeIdentity, mergeProps, memo, splitProps, toHash, uniq", "../helpers")}
240
244
  ${ctx.file.import("mergeCss", "./css")}
245
+ ${ctx.file.import("cx", "./cx")}
241
246
 
242
247
  // What \`createCss\` does to a class name, for the recipe path: prefix it, and hash it
243
248
  // when \`hash.className\` is set. The build applies the same two steps to the rules it
244
249
  // emits — see \`checkNamingAgreement\`, which compares the results.
245
250
  const withPrefix = ${withPrefix}
246
- const formatRecipeClass = ${hash.className ? `(className) => withPrefix((${utility.toHash})([className], toHash))` : `withPrefix`}
251
+ export const formatRecipeClass = ${hash.className ? `(className) => withPrefix((${utility.toHash})([className], toHash))` : `withPrefix`}
247
252
 
248
253
  const defaults = (conf) => ({
249
254
  base: {},
@@ -281,19 +286,6 @@ function generateCvaFn(ctx) {
281
286
  return mergeCss(variantCss, compoundVariantCss)
282
287
  }
283
288
 
284
- function merge(__cva) {
285
- const override = defaults(__cva.config)
286
- const variantKeys = uniq(__cva.variantKeys, Object.keys(variants))
287
- return cva({
288
- base: mergeCss(base, override.base),
289
- variants: Object.fromEntries(
290
- variantKeys.map((key) => [key, mergeCss(variants[key], override.variants[key])]),
291
- ),
292
- defaultVariants: mergeProps(defaultVariants, override.defaultVariants),
293
- compoundVariants: [...compoundVariants, ...override.compoundVariants],
294
- })
295
- }
296
-
297
289
  // \`raw\` runs per element per render — the JSX factory calls it to build the styles it
298
290
  // merges with style props — and \`resolve\` is not cheap: a \`mergeCss\` per active variant
299
291
  // plus a scan of every compound variant. Memoizing it keys that work on the variant
@@ -330,16 +322,79 @@ function generateCvaFn(ctx) {
330
322
 
331
323
  const variantMap = Object.fromEntries(Object.entries(variants).map(([key, value]) => [key, Object.keys(value)]))
332
324
 
333
- return Object.assign(memo(cvaFn), {
325
+ const self = Object.assign(memo(cvaFn), {
334
326
  __cva__: true,
335
327
  variantMap,
336
328
  variantKeys,
337
329
  raw: (...args) => cloneStyles(resolveVariants(...args)),
338
330
  config,
339
- merge,
331
+ // Composed against \`self\`, not against this closure, so \`a.merge(b).merge(c)\`
332
+ // composes the *result* with \`c\` rather than recomposing \`a\` with \`c\` and
333
+ // dropping \`b\`.
334
+ merge: (other) => composeRecipes(self, other),
340
335
  splitVariantProps,
341
336
  getVariantProps
342
337
  })
338
+
339
+ return self
340
+ }
341
+
342
+ /**
343
+ * Compose two recipes into one.
344
+ *
345
+ * The class names come from both parents joined, not from a merged config. A recipe's
346
+ * classes are named from the config the *build* saw, and the build only ever sees the
347
+ * literal \`cva(...)\` call sites — a config synthesised here at runtime has no rules
348
+ * behind it, so naming classes off it returned classes that styled nothing. This is the
349
+ * shape \`mergeRecipes\` already uses for config recipes.
350
+ *
351
+ * The selection is resolved once and handed to both parents. Passing the raw props
352
+ * instead let each parent apply *its own* defaults, so \`m()\` and
353
+ * \`m(m.getVariantProps())\` disagreed and \`raw()\` contradicted the \`config\` the same
354
+ * object publishes.
355
+ *
356
+ * \`raw\` still deep-merges, so per-property override survives where it can be expressed:
357
+ * \`css(a.merge(b).raw(props))\` resolves before any class name exists. Through the class
358
+ * path both parents land in the \`recipes\` layer, so a collision there is decided by
359
+ * stylesheet order rather than by which parent came second.
360
+ */
361
+ function composeRecipes(left, right) {
362
+ const leftConfig = defaults(left.config)
363
+ const rightConfig = defaults(right.config)
364
+ const variantKeys = uniq(left.variantKeys, right.variantKeys)
365
+
366
+ const config = {
367
+ base: mergeCss(leftConfig.base, rightConfig.base),
368
+ variants: Object.fromEntries(
369
+ variantKeys.map((key) => [key, mergeCss(leftConfig.variants[key], rightConfig.variants[key])]),
370
+ ),
371
+ defaultVariants: mergeProps(leftConfig.defaultVariants, rightConfig.defaultVariants),
372
+ compoundVariants: [...leftConfig.compoundVariants, ...rightConfig.compoundVariants],
373
+ }
374
+
375
+ const select = (props) => ({ ...config.defaultVariants, ...compact(props) })
376
+
377
+ const composed = Object.assign(
378
+ memo((props) => {
379
+ const selection = select(props)
380
+ return cx(left(selection), right(selection))
381
+ }),
382
+ {
383
+ __cva__: true,
384
+ variantMap: Object.fromEntries(variantKeys.map((key) => [key, Object.keys(config.variants[key] ?? {})])),
385
+ variantKeys,
386
+ raw: (props) => {
387
+ const selection = select(props)
388
+ return cloneStyles(mergeCss(left.raw(selection), right.raw(selection)))
389
+ },
390
+ config,
391
+ merge: (other) => composeRecipes(composed, other),
392
+ splitVariantProps: (props) => splitProps(props, variantKeys),
393
+ getVariantProps: select,
394
+ },
395
+ )
396
+
397
+ return composed
343
398
  }
344
399
 
345
400
  export function getCompoundVariantCss(compoundVariants, variantMap) {
@@ -442,7 +497,7 @@ function generateCx() {
442
497
  }
443
498
  //#endregion
444
499
  //#region src/artifacts/generated/helpers.mjs.json
445
- var content$8 = "//#region src/assert.ts\nfunction isObject(value) {\n return typeof value === \"object\" && value != null && !Array.isArray(value);\n}\nconst isObjectOrArray = (obj) => typeof obj === \"object\" && obj !== null;\n//#endregion\n//#region src/condition.ts\nconst isBaseCondition = (v) => v === \"base\";\nfunction filterBaseConditions(c) {\n return c.slice().filter((v) => !isBaseCondition(v));\n}\n//#endregion\n//#region src/hash.ts\nfunction toChar(code) {\n return String.fromCharCode(code + (code > 25 ? 39 : 97));\n}\nfunction toName(code) {\n let name = \"\";\n let x;\n for (x = Math.abs(code); x > 52; x = x / 52 | 0) name = toChar(x % 52) + name;\n return toChar(x % 52) + name;\n}\nfunction toPhash(h, x) {\n let i = x.length;\n while (i) h = h * 33 ^ x.charCodeAt(--i);\n return h;\n}\nfunction toHash(value) {\n return toName(toPhash(5381, value) >>> 0);\n}\n//#endregion\n//#region src/important.ts\nconst importantRegex = /\\s*!(important)?/i;\nconst whitespaceRegex = /\\s/;\n/**\n* Collapse every run of whitespace to a single space, which is what the class name is\n* built from. Exported because `leafClass` has to reproduce this exact pipeline, and a\n* second copy of it would be free to drift from the one `createCss` runs.\n*/\nfunction sanitize(value) {\n if (typeof value !== \"string\") return value;\n const collapsed = whitespaceRegex.test(value) ? value.replaceAll(/[\\n\\s]+/g, \" \") : value;\n return collapsed.includes(\"\\0\") ? collapsed.replaceAll(\"\\0\", \"\") : collapsed;\n}\nfunction isImportant(value) {\n if (typeof value !== \"string\") return false;\n return value.includes(\"!\") && importantRegex.test(value);\n}\nfunction withoutImportant(value) {\n if (typeof value !== \"string\") return value;\n if (!value.includes(\"!\")) return value.trim();\n return value.replace(importantRegex, \"\").trim();\n}\nfunction withoutSpace(str) {\n if (typeof str !== \"string\") return str;\n return str.includes(\" \") ? str.replaceAll(\" \", \"_\") : str;\n}\n//#endregion\n//#region src/memo.ts\n/**\n* Bounded argument memo used by the generated runtime (`css`, patterns, `cva`, recipes).\n*\n* Two regimes, picked per call:\n*\n* - Arguments that are flat (objects of primitives) take a cheap structural hash\n* and are confirmed with an exact comparison, so a hash collision can never\n* serve the wrong result. This is the shape `css({ ... })` has.\n* - Anything nested falls back to `JSON.stringify`, which V8 does faster than a\n* JS walk.\n*\n* The second point is the counter-intuitive one, and it has been measured rather\n* than assumed. Extending the structural hash to recurse — so nested styles could\n* take the fast path too — is *slower*, because it trades one native serialization\n* for two JS walks (hash, then the deep equality that confirms it). Over 10k\n* iterations per shape:\n*\n* shape stringify recursive hash + deep equal\n* flat 1.06ms 2.09ms\n* _hover 1.00ms 2.16ms\n* responsive 1.23ms 2.15ms\n* realistic 2.32ms 5.84ms\n* nested 3 deep 1.22ms 2.35ms\n*\n* So a nested `css()` call costing several times a flat one is not a defect here.\n* It is the floor for a value-keyed memo in JS, and the way to avoid it is to not\n* make the call — see the build-time fold in `@bamboocss/vite`.\n*\n* Both regimes key on *values*, never on object identity: mutating a style object\n* between calls changes its hash, so the next call misses and recomputes rather\n* than serving a stale class. Keying nested arguments on the identity of the inner\n* objects would skip serialization entirely, but it cannot detect a mutation, and\n* \"same object, different contents\" is exactly what a style object built per render\n* looks like.\n*\n* Both caches are bounded. An unbounded memo is a leak in any long-lived process\n* (SSR), where the set of distinct style objects grows without limit.\n*/\n/**\n* Distinct hashes held per memoized function before the cache rotates.\n*\n* This bounds *buckets*, not entries: a bucket keeps up to `MAX_BUCKET` colliding\n* argument lists, so the ceiling is `MAX_ENTRIES * MAX_BUCKET` live entries, and\n* twice that across both generations, since the previous one is retained until the\n* next rotation. Collisions are rare in practice, so the realistic figure is close\n* to `MAX_ENTRIES` — but the worst case is what matters when sizing a long-lived\n* process, so state it plainly.\n*\n* Rotation beats evicting the oldest key: single-key eviction is worst-case for a\n* working set that cycles, because it drops exactly the entry about to be needed.\n* Measured on a cycling set of 20k styles, one-at-a-time eviction cost ~719ns/op\n* against ~189ns unbounded, while rotation holds ~274ns. On realistic skewed\n* access rotation is at or below the unbounded cost.\n*/\nconst MAX_ENTRIES = 1e3;\n/** Entries kept per hash bucket, to bound the cost of a collision scan. */\nconst MAX_BUCKET = 8;\n/**\n* DJB2 over the arguments' own keys and primitive values.\n* Returns `null` for anything nested, which routes the call to the string key.\n*/\nconst flatHashOrNull = (args) => {\n let h = 5381;\n for (let a = 0; a < args.length; a++) {\n const obj = args[a];\n if (obj === null || typeof obj !== \"object\") {\n const t = typeof obj;\n if (t === \"string\") for (let i = 0; i < obj.length; i++) h = h * 33 ^ obj.charCodeAt(i);\n else if (t === \"number\") h = h * 33 ^ (obj | 0);\n else if (t === \"boolean\") h = h * 33 ^ (obj ? 991 : 997);\n else h = h * 33 ^ 3;\n continue;\n }\n if (Array.isArray(obj)) h = h * 33 ^ 7;\n else {\n const proto = Object.getPrototypeOf(obj);\n if (proto !== Object.prototype && proto !== null) return null;\n }\n for (const k in obj) {\n const v = obj[k];\n const tv = typeof v;\n if (v !== null && tv === \"object\") return null;\n for (let i = 0; i < k.length; i++) h = h * 33 ^ k.charCodeAt(i);\n if (tv === \"string\") for (let i = 0; i < v.length; i++) h = h * 33 ^ v.charCodeAt(i);\n else if (tv === \"number\") h = h * 33 ^ (v | 0);\n else if (tv === \"boolean\") h = h * 33 ^ (v ? 991 : 997);\n else h = h * 33 ^ 2;\n }\n }\n return h >>> 0;\n};\n/**\n* Value snapshot of the arguments, taken once at insert.\n*\n* The cache must not hold the caller's objects: a style object can capture a much\n* larger graph, and keeping it alive until the cache rotates changes GC behaviour\n* for code that never asked to be cached. Only the flat path reaches here, so a\n* shallow copy contains primitives only and retains nothing.\n*\n* Comparing against a copy also removes the last way a mutation could be missed.\n* Were the caller's own object stored, `oa === ob` would short-circuit the value\n* comparison, and a mutation that happened to preserve the hash would return the\n* stale entry. Against a copy that check can only ever be true for equal\n* primitives.\n*/\nconst snapshotArgs = (args) => {\n const values = [];\n const counts = [];\n for (let i = 0; i < args.length; i++) {\n const o = args[i];\n if (o !== null && typeof o === \"object\") {\n const copy = Array.isArray(o) ? [] : {};\n let n = 0;\n for (const k in o) {\n copy[k] = o[k];\n n++;\n }\n values.push(copy);\n counts.push(n);\n } else {\n values.push(o);\n counts.push(0);\n }\n }\n return {\n values,\n counts\n };\n};\n/**\n* Exact match, so a `flatHashOrNull` collision is resolved rather than trusted.\n* `bCounts` is the cached side's key count; comparing against it avoids the\n* `Object.keys()` allocation this would otherwise make on every cache hit.\n*/\nconst flatArgsEqual = (a, b, bCounts) => {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n const oa = a[i];\n const ob = b[i];\n if (oa === ob) continue;\n if (oa === null || ob === null || typeof oa !== \"object\" || typeof ob !== \"object\") return false;\n if (Array.isArray(oa) !== Array.isArray(ob)) return false;\n let n = 0;\n for (const k in oa) {\n if (oa[k] !== ob[k]) return false;\n n++;\n }\n if (n !== bCounts[i]) return false;\n }\n return true;\n};\nconst memo = (fn) => {\n let buckets = /* @__PURE__ */ new Map();\n let priorBuckets = /* @__PURE__ */ new Map();\n let strings = /* @__PURE__ */ new Map();\n let priorStrings = /* @__PURE__ */ new Map();\n /**\n * One scalar argument, keyed directly.\n *\n * This is the shape of the hottest callers — `isCssProperty(prop)` runs per prop\n * per render — and a plain map lookup beats hashing, bucket scanning and\n * snapshotting for it. Distinct types stay distinct keys, so `1` and `'1'` do not\n * share an entry.\n */\n let scalars = /* @__PURE__ */ new Map();\n let priorScalars = /* @__PURE__ */ new Map();\n const scan = (bucket, args) => {\n if (bucket) for (let i = 0; i < bucket.length; i++) {\n const entry = bucket[i];\n if (flatArgsEqual(args, entry.values, entry.counts)) return entry;\n }\n };\n const get = (...args) => {\n if (args.length === 1) {\n const only = args[0];\n if (only === null || typeof only !== \"object\") {\n if (scalars.has(only)) return scalars.get(only);\n if (priorScalars.has(only)) {\n const promoted = priorScalars.get(only);\n scalars.set(only, promoted);\n return promoted;\n }\n const out = fn(only);\n scalars.set(only, out);\n if (scalars.size > MAX_ENTRIES) {\n priorScalars = scalars;\n scalars = /* @__PURE__ */ new Map();\n }\n return out;\n }\n }\n const hash = flatHashOrNull(args);\n if (hash !== null) {\n let bucket = buckets.get(hash);\n const hit = scan(bucket, args);\n if (hit) return hit.out;\n const priorHit = scan(priorBuckets.get(hash), args);\n if (priorHit) {\n if (!bucket) {\n bucket = [];\n buckets.set(hash, bucket);\n }\n bucket.push(priorHit);\n if (bucket.length > MAX_BUCKET) bucket.shift();\n return priorHit.out;\n }\n const snap = snapshotArgs(args);\n const out = fn(...args);\n if (!bucket) {\n bucket = [];\n buckets.set(hash, bucket);\n }\n bucket.push({\n values: snap.values,\n counts: snap.counts,\n out\n });\n if (bucket.length > MAX_BUCKET) bucket.shift();\n if (buckets.size > MAX_ENTRIES) {\n priorBuckets = buckets;\n buckets = /* @__PURE__ */ new Map();\n }\n return out;\n }\n const key = JSON.stringify(args);\n if (strings.has(key)) return strings.get(key);\n if (priorStrings.has(key)) {\n const promoted = priorStrings.get(key);\n strings.set(key, promoted);\n return promoted;\n }\n const out = fn(...args);\n strings.set(key, out);\n if (strings.size > MAX_ENTRIES) {\n priorStrings = strings;\n strings = /* @__PURE__ */ new Map();\n }\n return out;\n };\n return get;\n};\n//#endregion\n//#region src/merge-props.ts\nconst MERGE_OMIT = new Set([\n \"__proto__\",\n \"constructor\",\n \"prototype\"\n]);\nfunction mergeProps(...sources) {\n return sources.reduce((prev, obj) => {\n if (!obj) return prev;\n Object.keys(obj).forEach((key) => {\n if (MERGE_OMIT.has(key)) return;\n const prevValue = prev[key];\n const value = obj[key];\n if (isObject(prevValue) && isObject(value)) prev[key] = mergeProps(prevValue, value);\n else prev[key] = value;\n });\n return prev;\n }, {});\n}\n//#endregion\n//#region src/walk-object.ts\nconst isNotNullish = (element) => element != null;\nfunction walkObject(target, predicate, options = {}) {\n const { stop, getKey } = options;\n function inner(value, path = []) {\n if (isObjectOrArray(value)) {\n const result = {};\n for (const [prop, child] of Object.entries(value)) {\n const key = getKey?.(prop, child) ?? prop;\n const childPath = [...path, key];\n if (stop?.(value, childPath)) return predicate(value, path);\n const next = inner(child, childPath);\n if (isNotNullish(next)) result[key] = next;\n }\n return result;\n }\n return predicate(value, path);\n }\n return inner(target);\n}\nfunction mapObject(obj, fn) {\n if (Array.isArray(obj)) return obj.map((value) => fn(value));\n if (!isObject(obj)) return fn(obj);\n return walkObject(obj, (value) => fn(value));\n}\n//#endregion\n//#region src/normalize-style-object.ts\nfunction toResponsiveObject(values, breakpoints) {\n return values.reduce((acc, current, index) => {\n const key = breakpoints[index];\n if (current != null) acc[key] = current;\n return acc;\n }, {});\n}\n/**\n* Whether walking the object would only rebuild it.\n*\n* Normalizing does three things: it renames a shorthand to its longhand, expands a responsive\n* array into a breakpoint object, and drops nullish leaves. A flat object of plain values\n* written in longhand needs none of them, and that is most of what `css()` is handed — but it\n* still paid for a full rebuild plus a path array per key.\n*\n* Every clause has to be exact, since a false positive returns an object the walk would have\n* changed. Nullish is one of them: a leaf the walk removes must not survive, or a later merge\n* would see it override the value beneath it. The array check is another, and it is on the\n* container as well as the values — `stop` is handed the container, so an array arriving at\n* the top level becomes a breakpoint object rather than being walked into.\n*\n* `for...in` reads inherited keys the walk ignores, which is safe in the only direction it can\n* be wrong — an extra key can send this to the slow path, never past it.\n*\n* It does read every value, as `compactStyles` and the argument memo already do, so an\n* accessor prop is read once more than before. Style props are values by the time they get\n* here and reading one has no effect, but it is the reason this cannot be reordered to read\n* lazily.\n*/\nfunction needsNoNormalizing(styles, resolveShorthand) {\n if (Array.isArray(styles)) return false;\n for (const key in styles) {\n const value = styles[key];\n if (value == null || typeof value === \"object\") return false;\n if (resolveShorthand !== void 0 && resolveShorthand(key) !== key) return false;\n }\n return true;\n}\n/**\n* The result may be the argument itself rather than a fresh object, so callers have to treat\n* it as read-only. Every one of them does today: merging accumulates into its own object and\n* the two `raw()` helpers clone at the boundary.\n*/\nfunction normalizeStyleObject(styles, context, shorthand = true) {\n const { utility, conditions } = context;\n const { hasShorthand, resolveShorthand } = utility;\n if (needsNoNormalizing(styles, shorthand && hasShorthand ? resolveShorthand : void 0)) return styles;\n return walkObject(styles, (value) => {\n return Array.isArray(value) ? toResponsiveObject(value, conditions.breakpoints.keys) : value;\n }, {\n stop: (value) => Array.isArray(value),\n getKey: shorthand ? (prop) => hasShorthand ? resolveShorthand(prop) : prop : void 0\n });\n}\n//#endregion\n//#region src/classname.ts\nconst fallbackCondition = {\n shift: (v) => v,\n finalize: (v) => v,\n breakpoints: { keys: [] }\n};\nconst ENTRY_SEP = \"]___[\";\nconst COND_SEP = \"<___>\";\n/**\n* The class a whole grouped `css()` call resolves to, given its group id.\n*\n* Shared with `StyleDecoder.collectGrouped` on purpose: both sides name this class, and\n* deriving it twice is what let `hash.className` re-hash on the build side only, leaving\n* every grouped element carrying a class no rule was emitted for.\n*\n* A group id already digests every declaration in the call, so it is hashed exactly once\n* and `hash.className` is deliberately not consulted — that option shortens *utility*\n* class names, and a grouped class is not one. The build `esc()`s the result for a\n* selector; the runtime does not. That asymmetry belongs to the callers.\n*/\nfunction groupClassName(groupId, toHashFn, formatClassName) {\n return formatClassName(toHashFn([\"grouped\", groupId], toHash));\n}\nfunction createCss(context) {\n const { utility, hash, grouped, conditions: conds = fallbackCondition } = context;\n const { prefix } = utility;\n const formatClassName = prefix ? (str) => str ? `${prefix}-${str}` : prefix : (str) => str || \"\";\n const hashFn = (conditions, className) => {\n if (hash) {\n const baseArray = [...conds.finalize(conditions), className];\n return formatClassName(utility.toHash(baseArray, toHash));\n }\n const finalized = conds.finalize(conditions);\n if (finalized.length === 0) return formatClassName(className);\n return [...finalized, formatClassName(className)].join(\":\");\n };\n /** One declaration, kept only when there is a fallback that might need to name it. */\n const atomicName = (prop, value, conditions) => {\n const important = isImportant(value);\n const className = hashFn(conditions, utility.transform(prop, withoutImportant(sanitize(value))).className);\n return important ? `${className}!` : className;\n };\n if (grouped) {\n const { knownGroups } = context;\n return memo(({ base, ...styles } = {}) => {\n const normalizedObject = normalizeStyleObject(Object.assign(styles, base), context);\n const hashes = [];\n const leaves = knownGroups ? [] : void 0;\n walkObject(normalizedObject, (value, paths) => {\n if (value == null) return;\n const [prop, ...allConditions] = conds.shift(paths);\n const conditions = filterBaseConditions(allConditions);\n const parts = [`${prop}${ENTRY_SEP}value:${value}`];\n if (conditions.length) parts.push(`cond:${conditions.join(COND_SEP)}`);\n hashes.push(parts.join(ENTRY_SEP));\n leaves?.push([\n prop,\n value,\n conditions\n ]);\n });\n if (hashes.length === 0) return \"\";\n hashes.sort();\n const className = groupClassName(hashes.join(\"|\"), utility.toHash, formatClassName);\n if (!leaves || knownGroups.has(className)) return className;\n const classNames = new Set([className]);\n for (const [prop, value, conditions] of leaves) classNames.add(atomicName(prop, value, conditions));\n return Array.from(classNames).join(\" \");\n });\n }\n return memo(({ base, ...styles } = {}) => {\n const normalizedObject = normalizeStyleObject(Object.assign(styles, base), context);\n const classNames = /* @__PURE__ */ new Set();\n walkObject(normalizedObject, (value, paths) => {\n if (value == null) return;\n const [prop, ...allConditions] = conds.shift(paths);\n classNames.add(atomicName(prop, value, filterBaseConditions(allConditions)));\n });\n return Array.from(classNames).join(\" \");\n });\n}\n/**\n* Whether a style object carries anything `compact` would have kept.\n*\n* The question `compactStyles` asks is only ever \"is this empty once undefined values are\n* dropped\", but it used to answer it by building the compacted object and then a key array\n* for it, then throwing both away. `Object.keys` enumerates exactly what `compact`'s\n* `Object.entries` did — own, enumerable, string-keyed — so this is the same predicate\n* without the two allocations, and it stops at the first value that settles it.\n*/\nfunction hasDefinedValue(style) {\n const keys = Object.keys(style);\n for (let i = 0; i < keys.length; i++) if (style[keys[i]] !== void 0) return true;\n return false;\n}\nfunction compactStyles(...styles) {\n return styles.flat().filter((style) => isObject(style) && hasDefinedValue(style));\n}\nfunction createMergeCss(context) {\n function resolve(styles) {\n const allStyles = compactStyles(...styles);\n if (allStyles.length === 1) return allStyles;\n return allStyles.map((style) => normalizeStyleObject(style, context));\n }\n function mergeCss(...styles) {\n return mergeProps(...resolve(styles));\n }\n function assignCss(...styles) {\n return Object.assign({}, ...resolve(styles));\n }\n return {\n mergeCss: memo(mergeCss),\n assignCss\n };\n}\n//#endregion\n//#region src/clone-styles.ts\nconst OMIT = new Set([\n \"__proto__\",\n \"constructor\",\n \"prototype\"\n]);\n/**\n* Independent copy of a style object, nested condition blocks included.\n*\n* Merged style objects are cached, so anything handed to user code has to be\n* copied first: a caller mutating what it received would otherwise change what\n* every later caller reads back. `css.raw()` and `cva.raw()` are those boundaries.\n*\n* Kept separate from `mergeProps` deliberately. Merging is on the hot path — it\n* runs on every `css()` cache miss and on every render of a pattern component\n* under `jsxStyleProps: 'minimal'` — and copying there charges every caller for a\n* guarantee only the two `raw()` helpers need. Measured on a realistic style\n* object (5 base properties, 4 condition blocks) that was roughly twice the cost\n* of merging alone.\n*/\nfunction cloneStyles(styles) {\n if (Array.isArray(styles)) return styles.map((value) => cloneStyles(value));\n if (!isObject(styles)) return styles;\n const out = {};\n for (const key of Object.keys(styles)) {\n if (OMIT.has(key)) continue;\n out[key] = cloneStyles(styles[key]);\n }\n return out;\n}\n//#endregion\n//#region src/compact.ts\nfunction compact(value) {\n return Object.fromEntries(Object.entries(value ?? {}).filter(([_, value]) => value !== void 0));\n}\n//#endregion\n//#region src/leaf-class.ts\n/**\n* The class a single dynamic style leaf resolves to, given the prefix its property and\n* condition path produce.\n*\n* ## Why this can exist at all\n*\n* `css()` builds a class from the value alone — `utility.transform` is string\n* construction over a static map, and nothing consults which rules were actually emitted.\n* So `css({ color: tone })` already returns `c_<tone>` for a value the extractor never\n* saw, with no CSS behind it. Reproducing that string here cannot be less correct than\n* the call it replaces; it just skips the object literal, the merge and the memo.\n*\n* ## Why it is not a template literal\n*\n* Three shapes do not reduce to `prefix + value`, and all three return `undefined` so the\n* caller runs `css()` instead:\n*\n* - An array is expanded to a responsive object by `normalizeStyleObject`, so it produces\n* one class per breakpoint rather than one class.\n* - An object is a condition block, walked into for the same reason.\n* - `null` and `undefined` are skipped by the walk entirely, which is an empty string\n* rather than a class — that one is answered here, since it needs no `css()` call.\n*\n* ## Why the character scan\n*\n* The remaining work — collapsing whitespace, stripping `!important`, turning spaces into\n* underscores — is three regexes, and paying them per call makes this *slower* than a\n* memo hit. Almost no token value contains whitespace or `!`, so one scan for the\n* characters that make any of it necessary sends the common value straight to a\n* concatenation. A false positive only costs the slow path, so the scan errs wide.\n*/\nfunction leafClass(prefix, value) {\n if (value == null) return \"\";\n const type = typeof value;\n if (type === \"number\" || type === \"boolean\") return `${prefix}${value}`;\n if (type !== \"string\") return void 0;\n const str = value;\n for (let index = 0; index < str.length; index++) {\n const code = str.charCodeAt(index);\n if (code <= 33 || code === 160 || code === 5760 || code >= 8192) return slowLeaf(prefix, str);\n }\n return `${prefix}${str}`;\n}\n/** The full pipeline `createCss` runs, for a value that needs it. */\nfunction slowLeaf(prefix, value) {\n const important = isImportant(value);\n const className = `${prefix}${withoutSpace(withoutImportant(sanitize(value)))}`;\n return important ? `${className}!` : className;\n}\n//#endregion\n//#region src/hypenate-property.ts\nconst wordRegex = /([A-Z])/g;\nconst msRegex = /^ms-/;\nconst hypenateProperty = memo((property) => {\n if (property.startsWith(\"--\")) return property;\n return property.replace(wordRegex, \"-$1\").replace(msRegex, \"-ms-\").toLowerCase();\n});\n//#endregion\n//#region src/recipe-identity.ts\n/** The fields that decide what CSS a recipe produces. Anything else is metadata. */\nconst STYLE_FIELDS = [\n \"base\",\n \"variants\",\n \"compoundVariants\",\n \"defaultVariants\"\n];\n/**\n* A serialization that depends on the config's *content* and not on how it was written.\n*\n* Object keys are sorted, so reordering two variants in the source does not rename every\n* class the recipe emits. Arrays keep their order, because `compoundVariants` is precedence\n* ordered and two orderings are two different recipes.\n*\n* A function serializes to `null` rather than to its source. `JSON.stringify` already drops\n* them, and stringifying instead would key the name on whether the bundle was minified —\n* the same build-dependent divergence that `cx` was changed to avoid. Nothing that survives\n* static extraction is a function, so there is no real config this loses information about.\n*/\nconst stable = (value) => {\n if (value === null || typeof value !== \"object\") return JSON.stringify(value) ?? \"null\";\n if (Array.isArray(value)) return `[${value.map(stable).join(\",\")}]`;\n const source = value;\n return `{${Object.keys(source).sort().map((key) => `${JSON.stringify(key)}:${stable(source[key])}`).join(\",\")}}`;\n};\n/**\n* The name an inline `cva`/`sva` emits its classes under — `button--size_sm`, where this\n* returns the `button`.\n*\n* A config recipe gets its name from the key it is declared under. An inline one has no\n* such key, and the two places that need the name never meet: the build derives it while\n* emitting the stylesheet, the runtime derives it again in the browser. So it has to come\n* from something both of them see, which leaves the config object itself.\n*\n* Deriving it from the *binding* — `const button = cva(...)` — was the obvious alternative\n* and does not work. Only the build can see that binding; handing it to the runtime means\n* rewriting the call, and then a pipeline without that transform names classes differently\n* from one with it. An optional `className` gets the same readable output with none of\n* that, because it travels inside the config to both sides.\n*\n* `className` is the field a config recipe already names itself with, and it means the same\n* thing here — the prefix every class the recipe emits is built from. An inline recipe that\n* declares one is indistinguishable in the stylesheet from a recipe declared in config.\n*/\nconst getRecipeIdentity = (config, prefix = \"cva\") => {\n const declared = config?.className;\n if (typeof declared === \"string\" && declared) return declared;\n const styles = {};\n for (const field of STYLE_FIELDS) {\n const value = config?.[field];\n if (value !== void 0) styles[field] = value;\n }\n return `${prefix}_${toHash(stable(styles))}`;\n};\n/**\n* The classes a recipe puts on an element: its own, plus one per selected variant.\n*\n* Lives here rather than in the generated `cva` because the build has to be able to check\n* it. `checkNamingAgreement` derives class names both ways and compares them, and it can\n* only do that against the code the browser actually runs — a second implementation written\n* to match would agree with itself and prove nothing.\n*\n* Compound variants are absent by design. Their rule selects on the variant classes already\n* in this list, so it applies without a class of its own.\n*/\nconst getRecipeClassNames = (name, variants, selection, separator = \"_\", format = (className) => className) => {\n let result = format(name);\n for (const variant of Object.keys(variants ?? {})) {\n const value = selection[variant];\n if (value == null) continue;\n if (variants?.[variant]?.[value] == null) continue;\n result += ` ${format(`${name}--${variant}${separator}${withoutSpace(value)}`)}`;\n }\n return result;\n};\n//#endregion\n//#region src/is-css-function.ts\nconst fnRegExp = new RegExp(`^(${[\n \"min\",\n \"max\",\n \"clamp\",\n \"calc\"\n].join(\"|\")})\\\\(.*\\\\)`);\nconst isCssFunction = (v) => typeof v === \"string\" && fnRegExp.test(v);\n//#endregion\n//#region src/is-css-unit.ts\nconst lengthUnitsPattern = `(?:${\"cm,mm,Q,in,pc,pt,px,em,ex,ch,rem,lh,rlh,vw,vh,vmin,vmax,vb,vi,svw,svh,lvw,lvh,dvw,dvh,cqw,cqh,cqi,cqb,cqmin,cqmax,%\".split(\",\").join(\"|\")})`;\nconst lengthRegExp = new RegExp(`^[+-]?[0-9]*.?[0-9]+(?:[eE][+-]?[0-9]+)?${lengthUnitsPattern}$`);\nconst isCssUnit = (v) => typeof v === \"string\" && lengthRegExp.test(v);\n//#endregion\n//#region src/is-css-var.ts\nconst isCssVar = (v) => typeof v === \"string\" && /^var\\(--.+\\)$/.test(v);\n//#endregion\n//#region src/pattern-fns.ts\nconst patternFns = {\n map: mapObject,\n isCssFunction,\n isCssVar,\n isCssUnit\n};\nconst getPatternStyles = (pattern, styles) => {\n if (!pattern?.defaultValues) return styles;\n const defaults = typeof pattern.defaultValues === \"function\" ? pattern.defaultValues(styles) : pattern.defaultValues;\n return Object.assign({}, defaults, compact(styles));\n};\n//#endregion\n//#region src/slot.ts\nconst getSlotRecipes = (recipe = {}) => {\n const init = (slot) => ({\n className: [recipe.className, slot].filter(Boolean).join(\"__\"),\n base: recipe.base?.[slot] ?? {},\n variants: {},\n defaultVariants: recipe.defaultVariants ?? {},\n compoundVariants: recipe.compoundVariants ? getSlotCompoundVariant(recipe.compoundVariants, slot) : []\n });\n const recipeParts = (recipe.slots ?? []).map((slot) => [slot, init(slot)]);\n for (const [variantsKey, variantsSpec] of Object.entries(recipe.variants ?? {})) for (const [variantKey, variantSpec] of Object.entries(variantsSpec)) recipeParts.forEach(([slot, slotRecipe]) => {\n slotRecipe.variants[variantsKey] ??= {};\n slotRecipe.variants[variantsKey][variantKey] = variantSpec[slot] ?? {};\n });\n return Object.fromEntries(recipeParts);\n};\nconst getSlotCompoundVariant = (compoundVariants, slotName) => compoundVariants.filter((compoundVariant) => compoundVariant.css[slotName]).map((compoundVariant) => ({\n ...compoundVariant,\n css: compoundVariant.css[slotName]\n}));\n//#endregion\n//#region src/split-props.ts\n/**\n* Deal a props object into one bucket per key group, plus a final bucket for the rest.\n* A key goes to the first group that claims it.\n*\n* ## Why the descriptor is read per key rather than in bulk\n*\n* This used to call `Object.getOwnPropertyDescriptors` for the whole object and\n* `defineProperty` for every key it moved. Copying plain values instead is 2.4–2.9x faster\n* on the shapes that allow it, but it is only correct where props are data — and they are\n* not always. Solid compiles props to accessors, so reading one eagerly runs whatever it\n* wraps: splitting a component's props would construct its children before the surrounding\n* provider exists.\n*\n* So the descriptor is fetched per key, and the value path is taken only when it changes\n* nothing observable. An accessor keeps its laziness, a non-enumerable key keeps its\n* invisibility, and `__proto__` is defined rather than assigned so it stays an own\n* property instead of reaching the prototype setter.\n*\n* The one thing the value path drops is `writable`/`configurable`, so a bucket key taken\n* from frozen props is writable where it used to be frozen. Nothing here relies on that,\n* and preserving it would mean `defineProperty` on the common path — the cost this exists\n* to avoid. Keys that take the descriptor path keep theirs, so a bucket can be\n* inconsistent in that one respect.\n*\n* Key order within a bucket is preserved exactly. It is not cosmetic: `cva` merges\n* variant props in iteration order, and the parser reads the rest bucket as the style\n* props it encodes, so order reaches the emitted CSS.\n*/\nfunction splitProps(props, ...keys) {\n const allKeys = Object.getOwnPropertyNames(props);\n const own = new Set(allKeys);\n const taken = /* @__PURE__ */ new Set();\n const split = (group) => {\n const clone = {};\n for (let i = 0; i < group.length; i++) {\n const key = group[i];\n if (taken.has(key) || !own.has(key)) continue;\n const descriptor = Object.getOwnPropertyDescriptor(props, key);\n if (!descriptor) continue;\n if (\"get\" in descriptor || \"set\" in descriptor || !descriptor.enumerable || key === \"__proto__\") Object.defineProperty(clone, key, descriptor);\n else clone[key] = descriptor.value;\n taken.add(key);\n }\n return clone;\n };\n /**\n * The predicate is called with the key alone.\n *\n * Handing it to `filter` passes `(key, index, allKeys)`. A one-parameter predicate cannot\n * see the extras, but a memoized one reads its whole argument list — and the predicates\n * that arrive here are memoized, `isCssProperty` among them. So the memo hashed the entire\n * key array once per prop, and keyed its cache on it: two elements with different prop sets\n * shared no entry even for the same prop name.\n *\n * Worth ~9.7x on that path, and nothing at all on a plain predicate — which is why the\n * bench below it needs a memoized case to see this at all.\n *\n * A loop rather than `filter((k) => key(k))` because the wrapper allocates a closure per\n * group. The two measure the same to within noise; the loop just does not need one.\n */\n const matching = (predicate) => {\n const group = [];\n for (let i = 0; i < allKeys.length; i++) {\n const key = allKeys[i];\n if (predicate(key)) group.push(key);\n }\n return group;\n };\n return keys.map((key) => split(Array.isArray(key) ? key : matching(key))).concat(split(allKeys));\n}\n//#endregion\n//#region src/uniq.ts\nconst uniq = (...items) => {\n const set = items.reduce((acc, currItems) => {\n if (currItems) currItems.forEach((item) => acc.add(item));\n return acc;\n }, /* @__PURE__ */ new Set([]));\n return Array.from(set);\n};\n//#endregion\n//#region src/view-transition.ts\n/**\n* The slots a `viewTransition()` bag accepts, in the order they are emitted.\n*\n* Anything else in the options object is ignored — including by the hash, so adding a\n* comment key or spreading an unrelated object does not fork the class name.\n*/\nconst viewTransitionSlots = [\n \"group\",\n \"imagePair\",\n \"old\",\n \"new\"\n];\n/**\n* Serialize a value with object keys sorted, so two style objects that differ only in\n* authoring order hash to the same class.\n*\n* Values JSON has no encoding for (`undefined`, functions, symbols) serialize as `null`\n* rather than being dropped. Nothing reaches here holding one — `filterSlots` removes\n* nullish slots, and the extractor drops nullish leaves — so this is only a floor: it\n* keeps a hole in an array from shifting the elements after it.\n*\n* Not exported. The name promises a general-purpose stable serializer, and this is not\n* one; `viewTransitionClassName` is the whole reason it exists.\n*/\nfunction stableStringify(value) {\n if (value === null) return \"null\";\n const type = typeof value;\n if (type === \"boolean\") return value ? \"true\" : \"false\";\n if (type === \"number\") return Number.isFinite(value) ? String(value) : \"null\";\n if (type === \"string\") return JSON.stringify(value);\n if (Array.isArray(value)) {\n let out = \"[\";\n for (let index = 0; index < value.length; index++) {\n if (index) out += \",\";\n out += stableStringify(value[index]);\n }\n return out + \"]\";\n }\n if (type === \"object\") {\n const keys = Object.keys(value).sort();\n let out = \"{\";\n for (let index = 0; index < keys.length; index++) {\n if (index) out += \",\";\n const key = keys[index];\n out += JSON.stringify(key) + \":\" + stableStringify(value[key]);\n }\n return out + \"}\";\n }\n return \"null\";\n}\n/**\n* Keep the four known slots, dropping any that is nullish.\n*\n* Both halves of the contract have to agree on what an empty slot is, and only one of\n* them gets a choice. The extractor evaluates the source, and a nullish property is gone\n* from what it hands over — `{ new: undefined }` and `{}` reach the build identically.\n* So absent, `undefined` and `null` collapse here too, and `new: enabled ? {…} : null`\n* hashes to the bag it actually styles.\n*/\nfunction filterSlots(options) {\n const filtered = {};\n if (!options || typeof options !== \"object\") return filtered;\n for (const slot of viewTransitionSlots) {\n const value = options[slot];\n if (value != null) filtered[slot] = value;\n }\n return filtered;\n}\n/**\n* The class a `viewTransition()` call resolves to.\n*\n* This is the whole contract between the build and the runtime: the extractor hashes the\n* options it found in the source, the generated `viewTransition()` hashes the options it\n* is called with, and the CSS only reaches the element if the two agree. They agree by\n* construction — both call this function — but only over what the extractor can see. A\n* value it cannot resolve statically is absent from its side and present on the runtime's,\n* which is why a bag has to be a static object literal to be styled at all.\n*\n* The class is used twice, as the `view-transition-class` value and as the argument to\n* `::view-transition-*(.cls)`, so the prefix applies to both.\n*/\nfunction viewTransitionClassName(options, prefix = \"\") {\n const base = \"vt_\" + toHash(stableStringify(filterSlots(options)));\n return prefix ? prefix + \"-\" + base : base;\n}\n//#endregion\nexport { cloneStyles, compact, createCss, createMergeCss, filterBaseConditions, getPatternStyles, getRecipeClassNames, getRecipeIdentity, getSlotCompoundVariant, getSlotRecipes, hypenateProperty, isBaseCondition, isObject, leafClass, mapObject, memo, mergeProps, patternFns, splitProps, toHash, uniq, viewTransitionClassName, walkObject, withoutSpace };\n";
500
+ var content$8 = "//#region src/assert.ts\nfunction isObject(value) {\n return typeof value === \"object\" && value != null && !Array.isArray(value);\n}\nconst isObjectOrArray = (obj) => typeof obj === \"object\" && obj !== null;\n//#endregion\n//#region src/condition.ts\nconst isBaseCondition = (v) => v === \"base\";\nfunction filterBaseConditions(c) {\n return c.slice().filter((v) => !isBaseCondition(v));\n}\n//#endregion\n//#region src/hash.ts\nfunction toChar(code) {\n return String.fromCharCode(code + (code > 25 ? 39 : 97));\n}\nfunction toName(code) {\n let name = \"\";\n let x;\n for (x = Math.abs(code); x > 52; x = x / 52 | 0) name = toChar(x % 52) + name;\n return toChar(x % 52) + name;\n}\nfunction toPhash(h, x) {\n let i = x.length;\n while (i) h = h * 33 ^ x.charCodeAt(--i);\n return h;\n}\nfunction toHash(value) {\n return toName(toPhash(5381, value) >>> 0);\n}\n//#endregion\n//#region src/important.ts\nconst importantRegex = /\\s*!(important)?/i;\nconst whitespaceRegex = /\\s/;\n/**\n* Collapse every run of whitespace to a single space, which is what the class name is\n* built from. Exported because `leafClass` has to reproduce this exact pipeline, and a\n* second copy of it would be free to drift from the one `createCss` runs.\n*/\nfunction sanitize(value) {\n if (typeof value !== \"string\") return value;\n const collapsed = whitespaceRegex.test(value) ? value.replaceAll(/[\\n\\s]+/g, \" \") : value;\n return collapsed.includes(\"\\0\") ? collapsed.replaceAll(\"\\0\", \"\") : collapsed;\n}\nfunction isImportant(value) {\n if (typeof value !== \"string\") return false;\n return value.includes(\"!\") && importantRegex.test(value);\n}\nfunction withoutImportant(value) {\n if (typeof value !== \"string\") return value;\n if (!value.includes(\"!\")) return value.trim();\n return value.replace(importantRegex, \"\").trim();\n}\nfunction withoutSpace(str) {\n if (typeof str !== \"string\") return str;\n return str.includes(\" \") ? str.replaceAll(\" \", \"_\") : str;\n}\n//#endregion\n//#region src/memo.ts\n/**\n* Bounded argument memo used by the generated runtime (`css`, patterns, `cva`, recipes).\n*\n* Two regimes, picked per call:\n*\n* - Arguments that are flat (objects of primitives) take a cheap structural hash\n* and are confirmed with an exact comparison, so a hash collision can never\n* serve the wrong result. This is the shape `css({ ... })` has.\n* - Anything nested falls back to `JSON.stringify`, which V8 does faster than a\n* JS walk.\n*\n* The second point is the counter-intuitive one, and it has been measured rather\n* than assumed. Extending the structural hash to recurse — so nested styles could\n* take the fast path too — is *slower*, because it trades one native serialization\n* for two JS walks (hash, then the deep equality that confirms it). Over 10k\n* iterations per shape:\n*\n* shape stringify recursive hash + deep equal\n* flat 1.06ms 2.09ms\n* _hover 1.00ms 2.16ms\n* responsive 1.23ms 2.15ms\n* realistic 2.32ms 5.84ms\n* nested 3 deep 1.22ms 2.35ms\n*\n* So a nested `css()` call costing several times a flat one is not a defect here.\n* It is the floor for a value-keyed memo in JS, and the way to avoid it is to not\n* make the call — see the build-time fold in `@bamboocss/vite`.\n*\n* Both regimes key on *values*, never on object identity: mutating a style object\n* between calls changes its hash, so the next call misses and recomputes rather\n* than serving a stale class. Keying nested arguments on the identity of the inner\n* objects would skip serialization entirely, but it cannot detect a mutation, and\n* \"same object, different contents\" is exactly what a style object built per render\n* looks like.\n*\n* Both caches are bounded. An unbounded memo is a leak in any long-lived process\n* (SSR), where the set of distinct style objects grows without limit.\n*/\n/**\n* Distinct hashes held per memoized function before the cache rotates.\n*\n* This bounds *buckets*, not entries: a bucket keeps up to `MAX_BUCKET` colliding\n* argument lists, so the ceiling is `MAX_ENTRIES * MAX_BUCKET` live entries, and\n* twice that across both generations, since the previous one is retained until the\n* next rotation. Collisions are rare in practice, so the realistic figure is close\n* to `MAX_ENTRIES` — but the worst case is what matters when sizing a long-lived\n* process, so state it plainly.\n*\n* Rotation beats evicting the oldest key: single-key eviction is worst-case for a\n* working set that cycles, because it drops exactly the entry about to be needed.\n* Measured on a cycling set of 20k styles, one-at-a-time eviction cost ~719ns/op\n* against ~189ns unbounded, while rotation holds ~274ns. On realistic skewed\n* access rotation is at or below the unbounded cost.\n*/\nconst MAX_ENTRIES = 1e3;\n/** Entries kept per hash bucket, to bound the cost of a collision scan. */\nconst MAX_BUCKET = 8;\n/**\n* DJB2 over the arguments' own keys and primitive values.\n* Returns `null` for anything nested, which routes the call to the string key.\n*/\nconst flatHashOrNull = (args) => {\n let h = 5381;\n for (let a = 0; a < args.length; a++) {\n const obj = args[a];\n if (obj === null || typeof obj !== \"object\") {\n const t = typeof obj;\n if (t === \"string\") for (let i = 0; i < obj.length; i++) h = h * 33 ^ obj.charCodeAt(i);\n else if (t === \"number\") h = h * 33 ^ (obj | 0);\n else if (t === \"boolean\") h = h * 33 ^ (obj ? 991 : 997);\n else h = h * 33 ^ 3;\n continue;\n }\n if (Array.isArray(obj)) h = h * 33 ^ 7;\n else {\n const proto = Object.getPrototypeOf(obj);\n if (proto !== Object.prototype && proto !== null) return null;\n }\n for (const k in obj) {\n const v = obj[k];\n const tv = typeof v;\n if (v !== null && tv === \"object\") return null;\n for (let i = 0; i < k.length; i++) h = h * 33 ^ k.charCodeAt(i);\n if (tv === \"string\") for (let i = 0; i < v.length; i++) h = h * 33 ^ v.charCodeAt(i);\n else if (tv === \"number\") h = h * 33 ^ (v | 0);\n else if (tv === \"boolean\") h = h * 33 ^ (v ? 991 : 997);\n else h = h * 33 ^ 2;\n }\n }\n return h >>> 0;\n};\n/**\n* Value snapshot of the arguments, taken once at insert.\n*\n* The cache must not hold the caller's objects: a style object can capture a much\n* larger graph, and keeping it alive until the cache rotates changes GC behaviour\n* for code that never asked to be cached. Only the flat path reaches here, so a\n* shallow copy contains primitives only and retains nothing.\n*\n* Comparing against a copy also removes the last way a mutation could be missed.\n* Were the caller's own object stored, `oa === ob` would short-circuit the value\n* comparison, and a mutation that happened to preserve the hash would return the\n* stale entry. Against a copy that check can only ever be true for equal\n* primitives.\n*/\nconst snapshotArgs = (args) => {\n const values = [];\n const counts = [];\n for (let i = 0; i < args.length; i++) {\n const o = args[i];\n if (o !== null && typeof o === \"object\") {\n const copy = Array.isArray(o) ? [] : {};\n let n = 0;\n for (const k in o) {\n copy[k] = o[k];\n n++;\n }\n values.push(copy);\n counts.push(n);\n } else {\n values.push(o);\n counts.push(0);\n }\n }\n return {\n values,\n counts\n };\n};\n/**\n* Exact match, so a `flatHashOrNull` collision is resolved rather than trusted.\n* `bCounts` is the cached side's key count; comparing against it avoids the\n* `Object.keys()` allocation this would otherwise make on every cache hit.\n*/\nconst flatArgsEqual = (a, b, bCounts) => {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n const oa = a[i];\n const ob = b[i];\n if (oa === ob) continue;\n if (oa === null || ob === null || typeof oa !== \"object\" || typeof ob !== \"object\") return false;\n if (Array.isArray(oa) !== Array.isArray(ob)) return false;\n let n = 0;\n for (const k in oa) {\n if (oa[k] !== ob[k]) return false;\n n++;\n }\n if (n !== bCounts[i]) return false;\n }\n return true;\n};\nconst memo = (fn) => {\n let buckets = /* @__PURE__ */ new Map();\n let priorBuckets = /* @__PURE__ */ new Map();\n let strings = /* @__PURE__ */ new Map();\n let priorStrings = /* @__PURE__ */ new Map();\n /**\n * One scalar argument, keyed directly.\n *\n * This is the shape of the hottest callers — `isCssProperty(prop)` runs per prop\n * per render — and a plain map lookup beats hashing, bucket scanning and\n * snapshotting for it. Distinct types stay distinct keys, so `1` and `'1'` do not\n * share an entry.\n */\n let scalars = /* @__PURE__ */ new Map();\n let priorScalars = /* @__PURE__ */ new Map();\n const scan = (bucket, args) => {\n if (bucket) for (let i = 0; i < bucket.length; i++) {\n const entry = bucket[i];\n if (flatArgsEqual(args, entry.values, entry.counts)) return entry;\n }\n };\n const get = (...args) => {\n if (args.length === 1) {\n const only = args[0];\n if (only === null || typeof only !== \"object\") {\n if (scalars.has(only)) return scalars.get(only);\n if (priorScalars.has(only)) {\n const promoted = priorScalars.get(only);\n scalars.set(only, promoted);\n return promoted;\n }\n const out = fn(only);\n scalars.set(only, out);\n if (scalars.size > MAX_ENTRIES) {\n priorScalars = scalars;\n scalars = /* @__PURE__ */ new Map();\n }\n return out;\n }\n }\n const hash = flatHashOrNull(args);\n if (hash !== null) {\n let bucket = buckets.get(hash);\n const hit = scan(bucket, args);\n if (hit) return hit.out;\n const priorHit = scan(priorBuckets.get(hash), args);\n if (priorHit) {\n if (!bucket) {\n bucket = [];\n buckets.set(hash, bucket);\n }\n bucket.push(priorHit);\n if (bucket.length > MAX_BUCKET) bucket.shift();\n return priorHit.out;\n }\n const snap = snapshotArgs(args);\n const out = fn(...args);\n if (!bucket) {\n bucket = [];\n buckets.set(hash, bucket);\n }\n bucket.push({\n values: snap.values,\n counts: snap.counts,\n out\n });\n if (bucket.length > MAX_BUCKET) bucket.shift();\n if (buckets.size > MAX_ENTRIES) {\n priorBuckets = buckets;\n buckets = /* @__PURE__ */ new Map();\n }\n return out;\n }\n const key = JSON.stringify(args);\n if (strings.has(key)) return strings.get(key);\n if (priorStrings.has(key)) {\n const promoted = priorStrings.get(key);\n strings.set(key, promoted);\n return promoted;\n }\n const out = fn(...args);\n strings.set(key, out);\n if (strings.size > MAX_ENTRIES) {\n priorStrings = strings;\n strings = /* @__PURE__ */ new Map();\n }\n return out;\n };\n return get;\n};\n//#endregion\n//#region src/merge-props.ts\nconst MERGE_OMIT = new Set([\n \"__proto__\",\n \"constructor\",\n \"prototype\"\n]);\nfunction mergeProps(...sources) {\n return sources.reduce((prev, obj) => {\n if (!obj) return prev;\n Object.keys(obj).forEach((key) => {\n if (MERGE_OMIT.has(key)) return;\n const prevValue = prev[key];\n const value = obj[key];\n if (isObject(prevValue) && isObject(value)) prev[key] = mergeProps(prevValue, value);\n else prev[key] = value;\n });\n return prev;\n }, {});\n}\n//#endregion\n//#region src/walk-object.ts\nconst isNotNullish = (element) => element != null;\nfunction walkObject(target, predicate, options = {}) {\n const { stop, getKey } = options;\n function inner(value, path = []) {\n if (isObjectOrArray(value)) {\n const result = {};\n for (const [prop, child] of Object.entries(value)) {\n const key = getKey?.(prop, child) ?? prop;\n const childPath = [...path, key];\n if (stop?.(value, childPath)) return predicate(value, path);\n const next = inner(child, childPath);\n if (isNotNullish(next)) result[key] = next;\n }\n return result;\n }\n return predicate(value, path);\n }\n return inner(target);\n}\nfunction mapObject(obj, fn) {\n if (Array.isArray(obj)) return obj.map((value) => fn(value));\n if (!isObject(obj)) return fn(obj);\n return walkObject(obj, (value) => fn(value));\n}\n//#endregion\n//#region src/normalize-style-object.ts\nfunction toResponsiveObject(values, breakpoints) {\n return values.reduce((acc, current, index) => {\n const key = breakpoints[index];\n if (current != null) acc[key] = current;\n return acc;\n }, {});\n}\n/**\n* Whether walking the object would only rebuild it.\n*\n* Normalizing does three things: it renames a shorthand to its longhand, expands a responsive\n* array into a breakpoint object, and drops nullish leaves. A flat object of plain values\n* written in longhand needs none of them, and that is most of what `css()` is handed — but it\n* still paid for a full rebuild plus a path array per key.\n*\n* Every clause has to be exact, since a false positive returns an object the walk would have\n* changed. Nullish is one of them: a leaf the walk removes must not survive, or a later merge\n* would see it override the value beneath it. The array check is another, and it is on the\n* container as well as the values — `stop` is handed the container, so an array arriving at\n* the top level becomes a breakpoint object rather than being walked into.\n*\n* `for...in` reads inherited keys the walk ignores, which is safe in the only direction it can\n* be wrong — an extra key can send this to the slow path, never past it.\n*\n* It does read every value, as `compactStyles` and the argument memo already do, so an\n* accessor prop is read once more than before. Style props are values by the time they get\n* here and reading one has no effect, but it is the reason this cannot be reordered to read\n* lazily.\n*/\nfunction needsNoNormalizing(styles, resolveShorthand) {\n if (Array.isArray(styles)) return false;\n for (const key in styles) {\n const value = styles[key];\n if (value == null || typeof value === \"object\") return false;\n if (resolveShorthand !== void 0 && resolveShorthand(key) !== key) return false;\n }\n return true;\n}\n/**\n* The result may be the argument itself rather than a fresh object, so callers have to treat\n* it as read-only. Every one of them does today: merging accumulates into its own object and\n* the two `raw()` helpers clone at the boundary.\n*/\nfunction normalizeStyleObject(styles, context, shorthand = true) {\n const { utility, conditions } = context;\n const { hasShorthand, resolveShorthand } = utility;\n if (needsNoNormalizing(styles, shorthand && hasShorthand ? resolveShorthand : void 0)) return styles;\n return walkObject(styles, (value) => {\n return Array.isArray(value) ? toResponsiveObject(value, conditions.breakpoints.keys) : value;\n }, {\n stop: (value) => Array.isArray(value),\n getKey: shorthand ? (prop) => hasShorthand ? resolveShorthand(prop) : prop : void 0\n });\n}\n//#endregion\n//#region src/classname.ts\nconst fallbackCondition = {\n shift: (v) => v,\n finalize: (v) => v,\n breakpoints: { keys: [] }\n};\nconst ENTRY_SEP = \"]___[\";\nconst COND_SEP = \"<___>\";\n/**\n* The class a whole grouped `css()` call resolves to, given its group id.\n*\n* Shared with `StyleDecoder.collectGrouped` on purpose: both sides name this class, and\n* deriving it twice is what let `hash.className` re-hash on the build side only, leaving\n* every grouped element carrying a class no rule was emitted for.\n*\n* A group id already digests every declaration in the call, so it is hashed exactly once\n* and `hash.className` is deliberately not consulted — that option shortens *utility*\n* class names, and a grouped class is not one. The build `esc()`s the result for a\n* selector; the runtime does not. That asymmetry belongs to the callers.\n*/\nfunction groupClassName(groupId, toHashFn, formatClassName) {\n return formatClassName(toHashFn([\"grouped\", groupId], toHash));\n}\nfunction createCss(context) {\n const { utility, hash, grouped, conditions: conds = fallbackCondition } = context;\n const { prefix } = utility;\n const formatClassName = prefix ? (str) => str ? `${prefix}-${str}` : prefix : (str) => str || \"\";\n const hashFn = (conditions, className) => {\n if (hash) {\n const baseArray = [...conds.finalize(conditions), className];\n return formatClassName(utility.toHash(baseArray, toHash));\n }\n const finalized = conds.finalize(conditions);\n if (finalized.length === 0) return formatClassName(className);\n return [...finalized, formatClassName(className)].join(\":\");\n };\n /** One declaration, kept only when there is a fallback that might need to name it. */\n const atomicName = (prop, value, conditions) => {\n const important = isImportant(value);\n const className = hashFn(conditions, utility.transform(prop, withoutImportant(sanitize(value))).className);\n return important ? `${className}!` : className;\n };\n if (grouped) {\n const { knownGroups } = context;\n return memo(({ base, ...styles } = {}) => {\n const normalizedObject = normalizeStyleObject(Object.assign(styles, base), context);\n const hashes = [];\n const leaves = knownGroups ? [] : void 0;\n walkObject(normalizedObject, (value, paths) => {\n if (value == null) return;\n const [prop, ...allConditions] = conds.shift(paths);\n const conditions = filterBaseConditions(allConditions);\n const parts = [`${prop}${ENTRY_SEP}value:${value}`];\n if (conditions.length) parts.push(`cond:${conditions.join(COND_SEP)}`);\n hashes.push(parts.join(ENTRY_SEP));\n leaves?.push([\n prop,\n value,\n conditions\n ]);\n });\n if (hashes.length === 0) return \"\";\n hashes.sort();\n const className = groupClassName(hashes.join(\"|\"), utility.toHash, formatClassName);\n if (!leaves || knownGroups.has(className)) return className;\n const classNames = new Set([className]);\n for (const [prop, value, conditions] of leaves) classNames.add(atomicName(prop, value, conditions));\n return Array.from(classNames).join(\" \");\n });\n }\n return memo(({ base, ...styles } = {}) => {\n const normalizedObject = normalizeStyleObject(Object.assign(styles, base), context);\n const classNames = /* @__PURE__ */ new Set();\n walkObject(normalizedObject, (value, paths) => {\n if (value == null) return;\n const [prop, ...allConditions] = conds.shift(paths);\n classNames.add(atomicName(prop, value, filterBaseConditions(allConditions)));\n });\n return Array.from(classNames).join(\" \");\n });\n}\n/**\n* Whether a style object carries anything `compact` would have kept.\n*\n* The question `compactStyles` asks is only ever \"is this empty once undefined values are\n* dropped\", but it used to answer it by building the compacted object and then a key array\n* for it, then throwing both away. `Object.keys` enumerates exactly what `compact`'s\n* `Object.entries` did — own, enumerable, string-keyed — so this is the same predicate\n* without the two allocations, and it stops at the first value that settles it.\n*/\nfunction hasDefinedValue(style) {\n const keys = Object.keys(style);\n for (let i = 0; i < keys.length; i++) if (style[keys[i]] !== void 0) return true;\n return false;\n}\nfunction compactStyles(...styles) {\n return styles.flat().filter((style) => isObject(style) && hasDefinedValue(style));\n}\nfunction createMergeCss(context) {\n function resolve(styles) {\n const allStyles = compactStyles(...styles);\n if (allStyles.length === 1) return allStyles;\n return allStyles.map((style) => normalizeStyleObject(style, context));\n }\n function mergeCss(...styles) {\n return mergeProps(...resolve(styles));\n }\n function assignCss(...styles) {\n return Object.assign({}, ...resolve(styles));\n }\n return {\n mergeCss: memo(mergeCss),\n assignCss,\n mergeCssUncached: mergeCss\n };\n}\n//#endregion\n//#region src/clone-styles.ts\nconst OMIT = new Set([\n \"__proto__\",\n \"constructor\",\n \"prototype\"\n]);\n/**\n* Independent copy of a style object, nested condition blocks included.\n*\n* Merged style objects are cached, so anything handed to user code has to be\n* copied first: a caller mutating what it received would otherwise change what\n* every later caller reads back. `css.raw()` and `cva.raw()` are those boundaries.\n*\n* Kept separate from `mergeProps` deliberately. Merging is on the hot path — it\n* runs on every `css()` cache miss and on every render of a pattern component\n* under `jsxStyleProps: 'minimal'` — and copying there charges every caller for a\n* guarantee only the two `raw()` helpers need. Measured on a realistic style\n* object (5 base properties, 4 condition blocks) that was roughly twice the cost\n* of merging alone.\n*/\nfunction cloneStyles(styles) {\n if (Array.isArray(styles)) return styles.map((value) => cloneStyles(value));\n if (!isObject(styles)) return styles;\n const out = {};\n for (const key of Object.keys(styles)) {\n if (OMIT.has(key)) continue;\n out[key] = cloneStyles(styles[key]);\n }\n return out;\n}\n//#endregion\n//#region src/compact.ts\nfunction compact(value) {\n return Object.fromEntries(Object.entries(value ?? {}).filter(([_, value]) => value !== void 0));\n}\n//#endregion\n//#region src/leaf-class.ts\n/**\n* The class a single dynamic style leaf resolves to, given the prefix its property and\n* condition path produce.\n*\n* ## Why this can exist at all\n*\n* `css()` builds a class from the value alone — `utility.transform` is string\n* construction over a static map, and nothing consults which rules were actually emitted.\n* So `css({ color: tone })` already returns `c_<tone>` for a value the extractor never\n* saw, with no CSS behind it. Reproducing that string here cannot be less correct than\n* the call it replaces; it just skips the object literal, the merge and the memo.\n*\n* ## Why it is not a template literal\n*\n* Three shapes do not reduce to `prefix + value`, and all three return `undefined` so the\n* caller runs `css()` instead:\n*\n* - An array is expanded to a responsive object by `normalizeStyleObject`, so it produces\n* one class per breakpoint rather than one class.\n* - An object is a condition block, walked into for the same reason.\n* - `null` and `undefined` are skipped by the walk entirely, which is an empty string\n* rather than a class — that one is answered here, since it needs no `css()` call.\n*\n* ## Why the character scan\n*\n* The remaining work — collapsing whitespace, stripping `!important`, turning spaces into\n* underscores — is three regexes, and paying them per call makes this *slower* than a\n* memo hit. Almost no token value contains whitespace or `!`, so one scan for the\n* characters that make any of it necessary sends the common value straight to a\n* concatenation. A false positive only costs the slow path, so the scan errs wide.\n*/\nfunction leafClass(prefix, value) {\n if (value == null) return \"\";\n const type = typeof value;\n if (type === \"number\" || type === \"boolean\") return `${prefix}${value}`;\n if (type !== \"string\") return void 0;\n const str = value;\n for (let index = 0; index < str.length; index++) {\n const code = str.charCodeAt(index);\n if (code <= 33 || code === 160 || code === 5760 || code >= 8192) return slowLeaf(prefix, str);\n }\n return `${prefix}${str}`;\n}\n/** The full pipeline `createCss` runs, for a value that needs it. */\nfunction slowLeaf(prefix, value) {\n const important = isImportant(value);\n const className = `${prefix}${withoutSpace(withoutImportant(sanitize(value)))}`;\n return important ? `${className}!` : className;\n}\n//#endregion\n//#region src/hypenate-property.ts\nconst wordRegex = /([A-Z])/g;\nconst msRegex = /^ms-/;\nconst hypenateProperty = memo((property) => {\n if (property.startsWith(\"--\")) return property;\n return property.replace(wordRegex, \"-$1\").replace(msRegex, \"-ms-\").toLowerCase();\n});\n//#endregion\n//#region src/recipe-identity.ts\n/**\n* The fields that decide what CSS a recipe produces. Anything else is metadata.\n*\n* `slots` and `scopeRoots` count. They do not change a declaration, but they change the\n* *shape* of what is emitted — which slots exist, and whether a slot's variants become\n* `@scope` rules or a class of its own. Two `sva`s differing only in `scopeRoots` hashed to\n* one name, and since an inline recipe is registered once, whichever was extracted first\n* decided the emission for both; the other's runtime then asked for classes no rule\n* existed under. \"Same styles, different DOM topology\" is exactly what `scopeRoots` is for,\n* so it is the collision most likely to happen.\n*/\nconst STYLE_FIELDS = [\n \"base\",\n \"variants\",\n \"compoundVariants\",\n \"defaultVariants\",\n \"slots\",\n \"scopeRoots\"\n];\n/**\n* A serialization that depends on the config's *content* and not on how it was written.\n*\n* Object keys are sorted, so reordering two variants in the source does not rename every\n* class the recipe emits. Arrays keep their order, because `compoundVariants` is precedence\n* ordered and two orderings are two different recipes.\n*\n* A function serializes to `null` rather than to its source. `JSON.stringify` already drops\n* them, and stringifying instead would key the name on whether the bundle was minified —\n* the same build-dependent divergence that `cx` was changed to avoid. Nothing that survives\n* static extraction is a function, so there is no real config this loses information about.\n*/\nconst stable = (value) => {\n if (value === null || typeof value !== \"object\") return JSON.stringify(value) ?? \"null\";\n if (Array.isArray(value)) return `[${value.map(stable).join(\",\")}]`;\n const source = value;\n return `{${Object.keys(source).sort().map((key) => `${JSON.stringify(key)}:${stable(source[key])}`).join(\",\")}}`;\n};\n/**\n* The name an inline `cva`/`sva` emits its classes under — `button--size_sm`, where this\n* returns the `button`.\n*\n* A config recipe gets its name from the key it is declared under. An inline one has no\n* such key, and the two places that need the name never meet: the build derives it while\n* emitting the stylesheet, the runtime derives it again in the browser. So it has to come\n* from something both of them see, which leaves the config object itself.\n*\n* Deriving it from the *binding* — `const button = cva(...)` — was the obvious alternative\n* and does not work. Only the build can see that binding; handing it to the runtime means\n* rewriting the call, and then a pipeline without that transform names classes differently\n* from one with it. An optional `className` gets the same readable output with none of\n* that, because it travels inside the config to both sides.\n*\n* `className` is the field a config recipe already names itself with, and it means the same\n* thing here — the prefix every class the recipe emits is built from. An inline recipe that\n* declares one is indistinguishable in the stylesheet from a recipe declared in config.\n*/\nconst getRecipeIdentity = (config, prefix = \"cva\") => {\n const declared = config?.className;\n if (typeof declared === \"string\" && declared) return declared;\n const styles = {};\n for (const field of STYLE_FIELDS) {\n const value = config?.[field];\n if (value !== void 0) styles[field] = value;\n }\n return `${prefix}_${toHash(stable(styles))}`;\n};\n/**\n* The classes a recipe puts on an element: its own, plus one per selected variant.\n*\n* Lives here rather than in the generated `cva` because the build has to be able to check\n* it. `checkNamingAgreement` derives class names both ways and compares them, and it can\n* only do that against the code the browser actually runs — a second implementation written\n* to match would agree with itself and prove nothing.\n*\n* Compound variants are absent by design. Their rule selects on the variant classes already\n* in this list, so it applies without a class of its own.\n*/\nconst getRecipeClassNames = (name, variants, selection, separator = \"_\", format = (className) => className) => {\n let result = format(name);\n for (const variant of Object.keys(variants ?? {})) {\n const value = selection[variant];\n if (value == null) continue;\n if (variants?.[variant]?.[value] == null) continue;\n result += ` ${format(`${name}--${variant}${separator}${withoutSpace(value)}`)}`;\n }\n return result;\n};\n//#endregion\n//#region src/is-css-function.ts\nconst fnRegExp = new RegExp(`^(${[\n \"min\",\n \"max\",\n \"clamp\",\n \"calc\"\n].join(\"|\")})\\\\(.*\\\\)`);\nconst isCssFunction = (v) => typeof v === \"string\" && fnRegExp.test(v);\n//#endregion\n//#region src/is-css-unit.ts\nconst lengthUnitsPattern = `(?:${\"cm,mm,Q,in,pc,pt,px,em,ex,ch,rem,lh,rlh,vw,vh,vmin,vmax,vb,vi,svw,svh,lvw,lvh,dvw,dvh,cqw,cqh,cqi,cqb,cqmin,cqmax,%\".split(\",\").join(\"|\")})`;\nconst lengthRegExp = new RegExp(`^[+-]?[0-9]*.?[0-9]+(?:[eE][+-]?[0-9]+)?${lengthUnitsPattern}$`);\nconst isCssUnit = (v) => typeof v === \"string\" && lengthRegExp.test(v);\n//#endregion\n//#region src/is-css-var.ts\nconst isCssVar = (v) => typeof v === \"string\" && /^var\\(--.+\\)$/.test(v);\n//#endregion\n//#region src/pattern-fns.ts\nconst patternFns = {\n map: mapObject,\n isCssFunction,\n isCssVar,\n isCssUnit\n};\nconst getPatternStyles = (pattern, styles) => {\n if (!pattern?.defaultValues) return styles;\n const defaults = typeof pattern.defaultValues === \"function\" ? pattern.defaultValues(styles) : pattern.defaultValues;\n return Object.assign({}, defaults, compact(styles));\n};\n//#endregion\n//#region src/slot.ts\nconst getSlotRecipes = (recipe = {}) => {\n const init = (slot) => ({\n className: [recipe.className, slot].filter(Boolean).join(\"__\"),\n base: recipe.base?.[slot] ?? {},\n variants: {},\n defaultVariants: recipe.defaultVariants ?? {},\n compoundVariants: recipe.compoundVariants ? getSlotCompoundVariant(recipe.compoundVariants, slot) : []\n });\n const recipeParts = (recipe.slots ?? []).map((slot) => [slot, init(slot)]);\n for (const [variantsKey, variantsSpec] of Object.entries(recipe.variants ?? {})) for (const [variantKey, variantSpec] of Object.entries(variantsSpec)) recipeParts.forEach(([slot, slotRecipe]) => {\n slotRecipe.variants[variantsKey] ??= {};\n slotRecipe.variants[variantsKey][variantKey] = variantSpec[slot] ?? {};\n });\n return Object.fromEntries(recipeParts);\n};\nconst getSlotCompoundVariant = (compoundVariants, slotName) => compoundVariants.filter((compoundVariant) => compoundVariant?.css?.[slotName]).map((compoundVariant) => ({\n ...compoundVariant,\n css: compoundVariant.css[slotName]\n}));\n//#endregion\n//#region src/split-props.ts\n/**\n* Move one key into a bucket, keeping whatever about it is observable.\n*\n* Shared by both paths below so there is one implementation of the descriptor rules rather\n* than two to keep in step. The rules themselves are documented on `splitProps`.\n*/\nconst copyKey = (props, target, key) => {\n const descriptor = Object.getOwnPropertyDescriptor(props, key);\n if (!descriptor) return false;\n if (\"get\" in descriptor || \"set\" in descriptor || !descriptor.enumerable || key === \"__proto__\") Object.defineProperty(target, key, descriptor);\n else target[key] = descriptor.value;\n return true;\n};\n/**\n* One array group, which is what every call site in this project passes — a recipe's\n* `variantKeys`.\n*\n* The general path below is built for several groups that may be predicates, and pays for\n* that shape on every call: a closure per group, a `map` and a `concat` to assemble the\n* result, and a branch per group to tell an array from a predicate. None of it is reachable\n* with one array group.\n*\n* What it does *not* skip is the part that looks skippable. `own` stays, because membership\n* has to be answered from `ownKeys` rather than by asking the object: on a proxy — which is\n* what Solid's `mergeProps` hands over — every question is a trap, and a recipe naming eight\n* variants would otherwise fire eight traps to learn what one `ownKeys` already said. And\n* the two passes stay separate, because the group bucket is in *group* order while the rest\n* bucket is in *props* order, and that ordering reaches the emitted CSS.\n*/\nconst splitOneGroup = (props, allKeys, group) => {\n const own = new Set(allKeys);\n const taken = /* @__PURE__ */ new Set();\n const picked = {};\n for (let i = 0; i < group.length; i++) {\n const key = group[i];\n if (taken.has(key) || !own.has(key)) continue;\n if (copyKey(props, picked, key)) taken.add(key);\n }\n const rest = {};\n for (let i = 0; i < allKeys.length; i++) {\n const key = allKeys[i];\n if (taken.has(key)) continue;\n copyKey(props, rest, key);\n }\n return [picked, rest];\n};\n/**\n* Deal a props object into one bucket per key group, plus a final bucket for the rest.\n* A key goes to the first group that claims it.\n*\n* ## Why the descriptor is read per key rather than in bulk\n*\n* This used to call `Object.getOwnPropertyDescriptors` for the whole object and\n* `defineProperty` for every key it moved. Copying plain values instead is 2.4–2.9x faster\n* on the shapes that allow it, but it is only correct where props are data — and they are\n* not always. Solid compiles props to accessors, so reading one eagerly runs whatever it\n* wraps: splitting a component's props would construct its children before the surrounding\n* provider exists.\n*\n* So the descriptor is fetched per key, and the value path is taken only when it changes\n* nothing observable. An accessor keeps its laziness, a non-enumerable key keeps its\n* invisibility, and `__proto__` is defined rather than assigned so it stays an own\n* property instead of reaching the prototype setter.\n*\n* The one thing the value path drops is `writable`/`configurable`, so a bucket key taken\n* from frozen props is writable where it used to be frozen. Nothing here relies on that,\n* and preserving it would mean `defineProperty` on the common path — the cost this exists\n* to avoid. Keys that take the descriptor path keep theirs, so a bucket can be\n* inconsistent in that one respect.\n*\n* Key order within a bucket is preserved exactly. It is not cosmetic: `cva` merges\n* variant props in iteration order, and the parser reads the rest bucket as the style\n* props it encodes, so order reaches the emitted CSS.\n*/\nfunction splitProps(props, ...keys) {\n const allKeys = Object.getOwnPropertyNames(props);\n if (keys.length === 1 && Array.isArray(keys[0])) return splitOneGroup(props, allKeys, keys[0]);\n const own = new Set(allKeys);\n const taken = /* @__PURE__ */ new Set();\n const split = (group) => {\n const clone = {};\n for (let i = 0; i < group.length; i++) {\n const key = group[i];\n if (taken.has(key) || !own.has(key)) continue;\n if (copyKey(props, clone, key)) taken.add(key);\n }\n return clone;\n };\n /**\n * The predicate is called with the key alone.\n *\n * Handing it to `filter` passes `(key, index, allKeys)`. A one-parameter predicate cannot\n * see the extras, but a memoized one reads its whole argument list — and the predicates\n * that arrive here are memoized, `isCssProperty` among them. So the memo hashed the entire\n * key array once per prop, and keyed its cache on it: two elements with different prop sets\n * shared no entry even for the same prop name.\n *\n * Worth ~9.7x on that path, and nothing at all on a plain predicate — which is why the\n * bench below it needs a memoized case to see this at all.\n *\n * A loop rather than `filter((k) => key(k))` because the wrapper allocates a closure per\n * group. The two measure the same to within noise; the loop just does not need one.\n */\n const matching = (predicate) => {\n const group = [];\n for (let i = 0; i < allKeys.length; i++) {\n const key = allKeys[i];\n if (predicate(key)) group.push(key);\n }\n return group;\n };\n return keys.map((key) => split(Array.isArray(key) ? key : matching(key))).concat(split(allKeys));\n}\n//#endregion\n//#region src/uniq.ts\nconst uniq = (...items) => {\n const set = items.reduce((acc, currItems) => {\n if (currItems) currItems.forEach((item) => acc.add(item));\n return acc;\n }, /* @__PURE__ */ new Set([]));\n return Array.from(set);\n};\n//#endregion\n//#region src/view-transition.ts\n/**\n* The slots a `viewTransition()` bag accepts, in the order they are emitted.\n*\n* Anything else in the options object is ignored — including by the hash, so adding a\n* comment key or spreading an unrelated object does not fork the class name.\n*/\nconst viewTransitionSlots = [\n \"group\",\n \"imagePair\",\n \"old\",\n \"new\"\n];\n/**\n* Serialize a value with object keys sorted, so two style objects that differ only in\n* authoring order hash to the same class.\n*\n* Values JSON has no encoding for (`undefined`, functions, symbols) serialize as `null`\n* rather than being dropped. Nothing reaches here holding one — `filterSlots` removes\n* nullish slots, and the extractor drops nullish leaves — so this is only a floor: it\n* keeps a hole in an array from shifting the elements after it.\n*\n* Not exported. The name promises a general-purpose stable serializer, and this is not\n* one; `viewTransitionClassName` is the whole reason it exists.\n*/\nfunction stableStringify(value) {\n if (value === null) return \"null\";\n const type = typeof value;\n if (type === \"boolean\") return value ? \"true\" : \"false\";\n if (type === \"number\") return Number.isFinite(value) ? String(value) : \"null\";\n if (type === \"string\") return JSON.stringify(value);\n if (Array.isArray(value)) {\n let out = \"[\";\n for (let index = 0; index < value.length; index++) {\n if (index) out += \",\";\n out += stableStringify(value[index]);\n }\n return out + \"]\";\n }\n if (type === \"object\") {\n const keys = Object.keys(value).sort();\n let out = \"{\";\n for (let index = 0; index < keys.length; index++) {\n if (index) out += \",\";\n const key = keys[index];\n out += JSON.stringify(key) + \":\" + stableStringify(value[key]);\n }\n return out + \"}\";\n }\n return \"null\";\n}\n/**\n* Keep the four known slots, dropping any that is nullish.\n*\n* Both halves of the contract have to agree on what an empty slot is, and only one of\n* them gets a choice. The extractor evaluates the source, and a nullish property is gone\n* from what it hands over — `{ new: undefined }` and `{}` reach the build identically.\n* So absent, `undefined` and `null` collapse here too, and `new: enabled ? {…} : null`\n* hashes to the bag it actually styles.\n*/\nfunction filterSlots(options) {\n const filtered = {};\n if (!options || typeof options !== \"object\") return filtered;\n for (const slot of viewTransitionSlots) {\n const value = options[slot];\n if (value != null) filtered[slot] = value;\n }\n return filtered;\n}\n/**\n* The class a `viewTransition()` call resolves to.\n*\n* This is the whole contract between the build and the runtime: the extractor hashes the\n* options it found in the source, the generated `viewTransition()` hashes the options it\n* is called with, and the CSS only reaches the element if the two agree. They agree by\n* construction — both call this function — but only over what the extractor can see. A\n* value it cannot resolve statically is absent from its side and present on the runtime's,\n* which is why a bag has to be a static object literal to be styled at all.\n*\n* The class is used twice, as the `view-transition-class` value and as the argument to\n* `::view-transition-*(.cls)`, so the prefix applies to both.\n*/\nfunction viewTransitionClassName(options, prefix = \"\") {\n const base = \"vt_\" + toHash(stableStringify(filterSlots(options)));\n return prefix ? prefix + \"-\" + base : base;\n}\n//#endregion\nexport { cloneStyles, compact, createCss, createMergeCss, filterBaseConditions, getPatternStyles, getRecipeClassNames, getRecipeIdentity, getSlotCompoundVariant, getSlotRecipes, hypenateProperty, isBaseCondition, isObject, leafClass, mapObject, memo, mergeProps, patternFns, splitProps, toHash, uniq, viewTransitionClassName, walkObject, withoutSpace };\n";
446
501
  //#endregion
447
502
  //#region src/artifacts/js/helpers.ts
448
503
  function generateHelpers() {
@@ -643,7 +698,19 @@ function generateCreateRecipe(ctx) {
643
698
  ${ctx.file.import("finalizeConditions, sortConditions", "../css/conditions")}
644
699
  ${ctx.file.import("assertCompoundVariant, getCompoundVariantCss", "../css/cva")}
645
700
  ${ctx.file.import("cx", "../css/cx")}
646
- ${ctx.file.import("compact, createCss, splitProps, uniq, withoutSpace", "../helpers")}
701
+ ${ctx.file.import("compact, createCss, splitProps, toHash, uniq, withoutSpace", "../helpers")}
702
+
703
+ /**
704
+ * What \`createCss\` does to a class name: prefix it, and hash it when \`hash.className\`
705
+ * is set.
706
+ *
707
+ * A slot that takes variants gets this for free, because its classes come from
708
+ * \`createCss\`. A *scoped* slot's class never goes through it — it is a constant — so it
709
+ * has to be formatted here or the runtime hands back a raw name while the stylesheet
710
+ * emits the rule under a hashed one, and the slot renders unstyled.
711
+ */
712
+ const withPrefix = ${prefix.className ? `(className) => className ? ${JSON.stringify(prefix.className)} + '-' + className : ${JSON.stringify(prefix.className)}` : `(className) => className`}
713
+ export const formatRecipeClass = ${hash.className ? `(className) => withPrefix((${utility.toHash})([className], toHash))` : `withPrefix`}
647
714
 
648
715
  export const createRecipe = (name, defaultVariants, compoundVariants) => {
649
716
  const getVariantProps = (variants) => {
@@ -754,12 +821,17 @@ function generateRecipes(ctx, filters) {
754
821
  const slotsAffectedBy = Object.fromEntries(Object.entries(config.variants ?? {}).map(([variant, values]) => [variant, Array.from(new Set(Object.values(values ?? {}).flatMap((slotStyles) => Object.keys(slotStyles ?? {}))))]));
755
822
  return outdent`
756
823
  ${ctx.file.import("compact, getSlotCompoundVariant, memo, splitProps", "../helpers")}
757
- ${ctx.file.import("createRecipe", "./create-recipe")}
824
+ ${ctx.file.import("createRecipe, formatRecipeClass", "./create-recipe")}
758
825
 
759
826
  const ${baseName}DefaultVariants = ${stringify$2(defaultVariants ?? {})}
760
827
  const ${baseName}CompoundVariants = ${stringify$2(compoundVariants ?? [])}
761
828
 
762
- const ${baseName}SlotNames = ${stringify$2(config.slots.map((slot) => [slot, `${config.className}__${slot}`]))}
829
+ // Formatted, not raw. A scoped slot's class is a constant that never passes through
830
+ // \`createCss\`, so \`hash.className\` and \`prefix\` have to be applied here to match
831
+ // the rule the stylesheet emits.
832
+ const ${baseName}SlotNames = ${stringify$2(config.slots.map((slot) => [slot, `${config.className}__${slot}`]))}.map(
833
+ ([slotName, className]) => [slotName, formatRecipeClass(className)],
834
+ )
763
835
  ${anchors.length ? outdent`
764
836
  /**
765
837
  * Only the anchors take variants: ${anchors.map((slot) => `\`${baseName}.${slot}\``).join(", ")}.
@@ -885,7 +957,7 @@ function generateSvaFn(ctx) {
885
957
  return {
886
958
  js: outdent`
887
959
  ${ctx.file.import("compact, getRecipeIdentity, getSlotRecipes, memo, splitProps", "../helpers")}
888
- ${ctx.file.import("cva", "./cva")}
960
+ ${ctx.file.import("cva, formatRecipeClass", "./cva")}
889
961
  ${ctx.file.import("cx", "./cx")}
890
962
 
891
963
  export function sva(config) {
@@ -903,8 +975,12 @@ function generateSvaFn(ctx) {
903
975
  // one before the split — the identity when the config declares none — so the guard
904
976
  // that used to sit here left an anonymous \`sva\` reporting no slot classes despite
905
977
  // emitting them.
978
+ // Formatted, like the classes \`svaFn\` returns. Left raw, this reported the name the
979
+ // element does *not* carry under \`hash\` or \`prefix\` — and \`auditSlotScopes\` builds
980
+ // its selectors from this map, so the diagnostic went silent in exactly the configs
981
+ // where a naming bug is likeliest.
906
982
  const classNameMap = slots.reduce((acc, [slot, cvaFn]) => {
907
- acc[slot] = cvaFn.config.className
983
+ acc[slot] = formatRecipeClass(cvaFn.config.className)
908
984
  return acc
909
985
  }, {})
910
986
 
@@ -925,7 +1001,7 @@ function generateSvaFn(ctx) {
925
1001
  function svaFn(props) {
926
1002
  const result = slots.map(([slot, cvaFn]) => [
927
1003
  slot,
928
- anchors.length && !anchors.includes(slot) ? cvaFn.config.className : cvaFn(props),
1004
+ anchors.length && !anchors.includes(slot) ? formatRecipeClass(cvaFn.config.className) : cvaFn(props),
929
1005
  ])
930
1006
  return Object.fromEntries(result)
931
1007
  }
@@ -2320,19 +2396,38 @@ function transformSegment(seg) {
2320
2396
  const parent = extractParentSelectors(seg);
2321
2397
  return parent ? `&${parent}` : seg;
2322
2398
  }
2399
+ /**
2400
+ * Build the nesting chain for one condition path.
2401
+ *
2402
+ * The outermost node is a `Root`, so the first segment has no enclosing rule and
2403
+ * its `&` refers to nothing — it is resolved away here rather than by
2404
+ * postcss-nested. Deeper segments keep their `&` and nest against the real
2405
+ * parent selector as before.
2406
+ *
2407
+ * This used to be seeded with an empty-selector rule and leaned on
2408
+ * postcss-nested to erase `&` against it. postcss 8.5.25 ("Fixed 8.5.17 visitor
2409
+ * regression") changed that edge case to collapse the whole selector, so every
2410
+ * conditional token was emitted as a selectorless — and therefore discarded —
2411
+ * rule, leaving only the `base` value in the tokens layer.
2412
+ */
2323
2413
  function getDeepestRule(root, selectors) {
2324
- const rule = postcss.rule({ selector: "" });
2414
+ const container = postcss.root();
2325
2415
  for (const selector of selectors) {
2326
- const node = getDeepestNode(rule) ?? rule;
2416
+ const node = getDeepestNode(container);
2417
+ const isTopLevel = node === container;
2327
2418
  if (selector.startsWith("@")) {
2419
+ const inner = isTopLevel ? root : `${root}&`;
2328
2420
  const atRule = postcss.rule({
2329
2421
  selector,
2330
- nodes: [postcss.rule({ selector: `${root}&` })]
2422
+ nodes: [postcss.rule({ selector: inner })]
2331
2423
  });
2332
2424
  node.append(atRule);
2333
- } else node.append(postcss.rule({ selector }));
2425
+ } else node.append(postcss.rule({ selector: isTopLevel ? withoutParentSelector(selector) : selector }));
2334
2426
  }
2335
- return rule;
2427
+ return container;
2428
+ }
2429
+ function withoutParentSelector(selector) {
2430
+ return selector.replaceAll("&", "").trim();
2336
2431
  }
2337
2432
  function getDeepestNode(node) {
2338
2433
  if (node.nodes && node.nodes.length) return getDeepestNode(node.nodes[node.nodes.length - 1]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/generator",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "The css generator for css bamboo",
5
5
  "homepage": "https://bamboocss.com",
6
6
  "license": "MIT",
@@ -36,14 +36,14 @@
36
36
  "javascript-stringify": "2.1.0",
37
37
  "outdent": " ^0.8.0",
38
38
  "pluralize": "8.0.0",
39
- "postcss": "8.5.14",
39
+ "postcss": "8.5.25",
40
40
  "ts-pattern": "5.9.0",
41
- "@bamboocss/core": "1.16.0",
42
- "@bamboocss/is-valid-prop": "^1.16.0",
43
- "@bamboocss/logger": "1.16.0",
44
- "@bamboocss/shared": "1.16.0",
45
- "@bamboocss/token-dictionary": "1.16.0",
46
- "@bamboocss/types": "1.16.0"
41
+ "@bamboocss/core": "1.17.0",
42
+ "@bamboocss/is-valid-prop": "^1.17.0",
43
+ "@bamboocss/logger": "1.17.0",
44
+ "@bamboocss/shared": "1.17.0",
45
+ "@bamboocss/token-dictionary": "1.17.0",
46
+ "@bamboocss/types": "1.17.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/pluralize": "0.0.33"