@bamboocss/generator 1.12.3 → 1.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md CHANGED
@@ -1,6 +1,7 @@
1
1
  MIT License
2
2
 
3
3
  Copyright (c) 2023 Segun Adebayo
4
+ Copyright (c) 2026 Gajus Kuizinas
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
7
  documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
package/dist/index.cjs CHANGED
@@ -22,6 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  }) : target, mod));
23
23
  //#endregion
24
24
  let _bamboocss_core = require("@bamboocss/core");
25
+ let _bamboocss_logger = require("@bamboocss/logger");
25
26
  let _bamboocss_shared = require("@bamboocss/shared");
26
27
  let ts_pattern = require("ts-pattern");
27
28
  let outdent = require("outdent");
@@ -30,7 +31,6 @@ let javascript_stringify = require("javascript-stringify");
30
31
  let _bamboocss_is_valid_prop = require("@bamboocss/is-valid-prop");
31
32
  let pluralize = require("pluralize");
32
33
  pluralize = __toESM(pluralize);
33
- let _bamboocss_logger = require("@bamboocss/logger");
34
34
  let postcss = require("postcss");
35
35
  postcss = __toESM(postcss);
36
36
  //#region src/artifacts/js/conditions.ts
@@ -164,9 +164,15 @@ function generateCssFn(ctx) {
164
164
  }
165
165
 
166
166
  export declare const css: CssFunction;
167
+
168
+ /**
169
+ * Internal. Emitted for the source transform, which rewrites a single dynamic style
170
+ * leaf into a call to this. Not part of the authoring API.
171
+ */
172
+ export declare const cssLeaf: (prefix: string, prop: string, value: unknown) => string;
167
173
  `,
168
174
  js: outdent.outdent`
169
- ${ctx.file.import("createCss, createMergeCss, hypenateProperty, withoutSpace", "../helpers")}
175
+ ${ctx.file.import("cloneStyles, createCss, createMergeCss, hypenateProperty, leafClass, memo, withoutSpace", "../helpers")}
170
176
  ${ctx.file.import("sortConditions, finalizeConditions", "./conditions")}
171
177
 
172
178
  const utilities = "${utility.entries().map(([prop, className]) => {
@@ -217,8 +223,19 @@ function generateCssFn(ctx) {
217
223
  }
218
224
 
219
225
  const cssFn = createCss(context)
220
- export const css = (...styles) => cssFn(mergeCss(...styles))
221
- css.raw = (...styles) => mergeCss(...styles)
226
+ export const css = /* @__PURE__ */ memo((...styles) => cssFn(mergeCss(...styles)))
227
+ // The merged result is cached and shared, so a caller mutating a nested
228
+ // condition object would otherwise poison it for everyone after them.
229
+ css.raw = (...styles) => cloneStyles(mergeCss(...styles))
230
+
231
+ // Emitted for the source transform, which rewrites a single dynamic style leaf into a
232
+ // call to this rather than leaving a \`css()\` behind. \`prefix\` is the class up to the
233
+ // value, resolved at build time; \`prop\` is only used for the shapes \`leafClass\`
234
+ // declines, which have to run the real thing.
235
+ export const cssLeaf = (prefix, prop, value) => {
236
+ const className = leafClass(prefix, value)
237
+ return className === undefined ? css({ [prop]: value }) : className
238
+ }
222
239
 
223
240
  export const { mergeCss, assignCss } = createMergeCss(context)
224
241
  `
@@ -253,7 +270,7 @@ function generateStringLiteralCssFn(ctx) {
253
270
  export declare const css: CssFunction;
254
271
  `,
255
272
  js: outdent.outdent`
256
- ${ctx.file.import("astish, createCss, isObject, mergeProps, withoutSpace", "../helpers")}
273
+ ${ctx.file.import("astish, cloneStyles, createCss, isObject, mergeProps, withoutSpace", "../helpers")}
257
274
  ${ctx.file.import("finalizeConditions, sortConditions", "./conditions")}
258
275
 
259
276
  function transform(prop, value) {
@@ -283,7 +300,9 @@ function generateStringLiteralCssFn(ctx) {
283
300
 
284
301
  const fn = (style) => (isObject(style) ? style : astish(style[0]))
285
302
  export const css = (...styles) => cssFn(mergeProps(...styles.filter(Boolean).map(fn)))
286
- css.raw = (...styles) => mergeProps(...styles.filter(Boolean).map(fn))
303
+ // Same independence guarantee as the object-syntax css.raw(), so the public
304
+ // API behaves identically across both syntaxes.
305
+ css.raw = (...styles) => cloneStyles(mergeProps(...styles.filter(Boolean).map(fn)))
287
306
  `
288
307
  };
289
308
  }
@@ -292,7 +311,7 @@ function generateStringLiteralCssFn(ctx) {
292
311
  function generateCvaFn(ctx) {
293
312
  return {
294
313
  js: outdent.outdent`
295
- ${ctx.file.import("compact, mergeProps, memo, splitProps, uniq", "../helpers")}
314
+ ${ctx.file.import("cloneStyles, compact, mergeProps, memo, splitProps, uniq", "../helpers")}
296
315
  ${ctx.file.import("css, mergeCss", "./css")}
297
316
 
298
317
  const defaults = (conf) => ({
@@ -348,7 +367,7 @@ function generateCvaFn(ctx) {
348
367
  __cva__: true,
349
368
  variantMap,
350
369
  variantKeys,
351
- raw: resolve,
370
+ raw: (...args) => cloneStyles(resolve(...args)),
352
371
  config,
353
372
  merge,
354
373
  splitVariantProps,
@@ -424,7 +443,7 @@ function generateCx() {
424
443
  var content$11 = "//#region src/astish.ts\nconst newRule = /(?:([\\u0080-\\uFFFF\\w-%@]+) *:? *([^{;]+?);|([^;}{]*?) *{)|(}\\s*)/g;\nconst ruleClean = /\\/\\*[^]*?\\*\\/| +/g;\nconst ruleNewline = /\\n+/g;\nconst empty = \" \";\nconst astish = (val, tree = [{}]) => {\n if (!val) return tree[0];\n let block, left;\n while (block = newRule.exec(val.replace(ruleClean, \"\"))) if (block[4]) tree.shift();\n else if (block[3]) {\n left = block[3].replace(ruleNewline, empty).trim();\n if (!left.includes(\"&\") && !left.startsWith(\"@\")) left = \"& \" + left;\n tree.unshift(tree[0][left] = tree[0][left] || {});\n } else tree[0][block[1]] = block[2].replace(ruleNewline, empty).trim();\n return tree[0];\n};\n//#endregion\nexport { astish };\n";
425
444
  //#endregion
426
445
  //#region src/artifacts/generated/helpers.mjs.json
427
- var content$10 = "//#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/compact.ts\nfunction compact(value) {\n return Object.fromEntries(Object.entries(value ?? {}).filter(([_, value]) => value !== void 0));\n}\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;\nfunction isImportant(value) {\n return typeof value === \"string\" ? importantRegex.test(value) : false;\n}\nfunction withoutImportant(value) {\n return typeof value === \"string\" ? value.replace(importantRegex, \"\").trim() : value;\n}\nfunction withoutSpace(str) {\n return typeof str === \"string\" ? str.replaceAll(\" \", \"_\") : str;\n}\n//#endregion\n//#region src/memo.ts\nconst memo = (fn) => {\n const cache = /* @__PURE__ */ new Map();\n const get = (...args) => {\n const key = JSON.stringify(args);\n if (cache.has(key)) return cache.get(key);\n const result = fn(...args);\n cache.set(key, result);\n return result;\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}\nfunction normalizeStyleObject(styles, context, shorthand = true) {\n const { utility, conditions } = context;\n const { hasShorthand, resolveShorthand } = utility;\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 sanitize = (value) => typeof value === \"string\" ? value.replaceAll(/[\\n\\s]+/g, \" \") : value;\nconst ENTRY_SEP = \"]___[\";\nconst COND_SEP = \"<___>\";\nfunction createCss(context) {\n const { utility, hash, grouped, conditions: conds = fallbackCondition } = context;\n const formatClassName = (str) => [utility.prefix, str].filter(Boolean).join(\"-\");\n const hashFn = (conditions, className) => {\n let result;\n if (hash) {\n const baseArray = [...conds.finalize(conditions), className];\n result = formatClassName(utility.toHash(baseArray, toHash));\n } else result = [...conds.finalize(conditions), formatClassName(className)].join(\":\");\n return result;\n };\n if (grouped) return memo(({ base, ...styles } = {}) => {\n const normalizedObject = normalizeStyleObject(Object.assign(styles, base), context);\n const hashes = [];\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 });\n if (hashes.length === 0) return \"\";\n hashes.sort();\n const groupId = hashes.join(\"|\");\n return formatClassName(utility.toHash([\"grouped\", groupId], toHash));\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 important = isImportant(value);\n const [prop, ...allConditions] = conds.shift(paths);\n let className = hashFn(filterBaseConditions(allConditions), utility.transform(prop, withoutImportant(sanitize(value))).className);\n if (important) className = `${className}!`;\n classNames.add(className);\n });\n return Array.from(classNames).join(\" \");\n });\n}\nfunction compactStyles(...styles) {\n return styles.flat().filter((style) => isObject(style) && Object.keys(compact(style)).length > 0);\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/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/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\nfunction splitProps(props, ...keys) {\n const descriptors = Object.getOwnPropertyDescriptors(props);\n const dKeys = Object.keys(descriptors);\n const split = (k) => {\n const clone = {};\n for (let i = 0; i < k.length; i++) {\n const key = k[i];\n if (descriptors[key]) {\n Object.defineProperty(clone, key, descriptors[key]);\n delete descriptors[key];\n }\n }\n return clone;\n };\n const fn = (key) => split(Array.isArray(key) ? key : dKeys.filter(key));\n return keys.map(fn).concat(split(dKeys));\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\nexport { compact, createCss, createMergeCss, filterBaseConditions, getPatternStyles, getSlotCompoundVariant, getSlotRecipes, hypenateProperty, isBaseCondition, isObject, mapObject, memo, mergeProps, patternFns, splitProps, toHash, uniq, walkObject, withoutSpace };\n";
446
+ var content$10 = "//#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/compact.ts\nfunction compact(value) {\n return Object.fromEntries(Object.entries(value ?? {}).filter(([_, value]) => value !== void 0));\n}\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;\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 return typeof value === \"string\" ? value.replaceAll(/[\\n\\s]+/g, \" \") : value;\n}\nfunction isImportant(value) {\n return typeof value === \"string\" ? importantRegex.test(value) : false;\n}\nfunction withoutImportant(value) {\n return typeof value === \"string\" ? value.replace(importantRegex, \"\").trim() : value;\n}\nfunction withoutSpace(str) {\n return typeof str === \"string\" ? 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}\nfunction normalizeStyleObject(styles, context, shorthand = true) {\n const { utility, conditions } = context;\n const { hasShorthand, resolveShorthand } = utility;\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 = \"<___>\";\nfunction createCss(context) {\n const { utility, hash, grouped, conditions: conds = fallbackCondition } = context;\n const formatClassName = (str) => [utility.prefix, str].filter(Boolean).join(\"-\");\n const hashFn = (conditions, className) => {\n let result;\n if (hash) {\n const baseArray = [...conds.finalize(conditions), className];\n result = formatClassName(utility.toHash(baseArray, toHash));\n } else result = [...conds.finalize(conditions), formatClassName(className)].join(\":\");\n return result;\n };\n if (grouped) return memo(({ base, ...styles } = {}) => {\n const normalizedObject = normalizeStyleObject(Object.assign(styles, base), context);\n const hashes = [];\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 });\n if (hashes.length === 0) return \"\";\n hashes.sort();\n const groupId = hashes.join(\"|\");\n return formatClassName(utility.toHash([\"grouped\", groupId], toHash));\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 important = isImportant(value);\n const [prop, ...allConditions] = conds.shift(paths);\n let className = hashFn(filterBaseConditions(allConditions), utility.transform(prop, withoutImportant(sanitize(value))).className);\n if (important) className = `${className}!`;\n classNames.add(className);\n });\n return Array.from(classNames).join(\" \");\n });\n}\nfunction compactStyles(...styles) {\n return styles.flat().filter((style) => isObject(style) && Object.keys(compact(style)).length > 0);\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/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/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 return keys.map((key) => split(Array.isArray(key) ? key : allKeys.filter(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\nexport { cloneStyles, compact, createCss, createMergeCss, filterBaseConditions, getPatternStyles, getSlotCompoundVariant, getSlotRecipes, hypenateProperty, isBaseCondition, isObject, leafClass, mapObject, memo, mergeProps, patternFns, splitProps, toHash, uniq, walkObject, withoutSpace };\n";
428
447
  //#endregion
429
448
  //#region src/artifacts/generated/normalize-html.mjs.json
430
449
  var content$9 = "//#region src/normalize-html.ts\nconst htmlProps = [\n \"htmlSize\",\n \"htmlTranslate\",\n \"htmlWidth\",\n \"htmlHeight\"\n];\nfunction convert(key) {\n return htmlProps.includes(key) ? key.replace(\"html\", \"\").toLowerCase() : key;\n}\nfunction normalizeHTMLProps(props) {\n return Object.fromEntries(Object.entries(props).map(([key, value]) => [convert(key), value]));\n}\nnormalizeHTMLProps.keys = htmlProps;\n//#endregion\nexport { normalizeHTMLProps };\n";
@@ -451,17 +470,19 @@ function generateHelpers(ctx) {
451
470
  var content$8 = "//#region src/index.ts\nconst userGenerated = \"\".split(\",\");\nconst allCssProperties = \"WebkitAppearance,WebkitBorderBefore,WebkitBorderBeforeColor,WebkitBorderBeforeStyle,WebkitBorderBeforeWidth,WebkitBoxReflect,WebkitLineClamp,WebkitMask,WebkitMaskAttachment,WebkitMaskClip,WebkitMaskComposite,WebkitMaskImage,WebkitMaskOrigin,WebkitMaskPosition,WebkitMaskPositionX,WebkitMaskPositionY,WebkitMaskRepeat,WebkitMaskRepeatX,WebkitMaskRepeatY,WebkitMaskSize,WebkitOverflowScrolling,WebkitTapHighlightColor,WebkitTextFillColor,WebkitTextStroke,WebkitTextStrokeColor,WebkitTextStrokeWidth,WebkitTouchCallout,WebkitUserModify,WebkitUserSelect,accentColor,alignContent,alignItems,alignSelf,alignTracks,all,anchorName,anchorScope,animation,animationComposition,animationDelay,animationDirection,animationDuration,animationFillMode,animationIterationCount,animationName,animationPlayState,animationRange,animationRangeEnd,animationRangeStart,animationTimeline,animationTimingFunction,appearance,aspectRatio,backdropFilter,backfaceVisibility,background,backgroundAttachment,backgroundBlendMode,backgroundClip,backgroundColor,backgroundImage,backgroundOrigin,backgroundPosition,backgroundPositionX,backgroundPositionY,backgroundRepeat,backgroundSize,blockSize,border,borderBlock,borderBlockColor,borderBlockEnd,borderBlockEndColor,borderBlockEndStyle,borderBlockEndWidth,borderBlockStart,borderBlockStartColor,borderBlockStartStyle,borderBlockStartWidth,borderBlockStyle,borderBlockWidth,borderBottom,borderBottomColor,borderBottomLeftRadius,borderBottomRightRadius,borderBottomStyle,borderBottomWidth,borderCollapse,borderColor,borderEndEndRadius,borderEndStartRadius,borderImage,borderImageOutset,borderImageRepeat,borderImageSlice,borderImageSource,borderImageWidth,borderInline,borderInlineColor,borderInlineEnd,borderInlineEndColor,borderInlineEndStyle,borderInlineEndWidth,borderInlineStart,borderInlineStartColor,borderInlineStartStyle,borderInlineStartWidth,borderInlineStyle,borderInlineWidth,borderLeft,borderLeftColor,borderLeftStyle,borderLeftWidth,borderRadius,borderRight,borderRightColor,borderRightStyle,borderRightWidth,borderSpacing,borderStartEndRadius,borderStartStartRadius,borderStyle,borderTop,borderTopColor,borderTopLeftRadius,borderTopRightRadius,borderTopStyle,borderTopWidth,borderWidth,bottom,boxAlign,boxDecorationBreak,boxDirection,boxFlex,boxFlexGroup,boxLines,boxOrdinalGroup,boxOrient,boxPack,boxShadow,boxSizing,breakAfter,breakBefore,breakInside,captionSide,caret,caretColor,caretShape,clear,clip,clipPath,clipRule,color,colorInterpolationFilters,colorScheme,columnCount,columnFill,columnGap,columnRule,columnRuleColor,columnRuleStyle,columnRuleWidth,columnSpan,columnWidth,columns,contain,containIntrinsicBlockSize,containIntrinsicHeight,containIntrinsicInlineSize,containIntrinsicSize,containIntrinsicWidth,container,containerName,containerType,content,contentVisibility,cornerShape,counterIncrement,counterReset,counterSet,cursor,cx,cy,d,direction,display,dominantBaseline,emptyCells,fieldSizing,fill,fillOpacity,fillRule,filter,flex,flexBasis,flexDirection,flexFlow,flexGrow,flexShrink,flexWrap,float,floodColor,floodOpacity,font,fontFamily,fontFeatureSettings,fontKerning,fontLanguageOverride,fontOpticalSizing,fontPalette,fontSize,fontSizeAdjust,fontSmooth,fontStretch,fontStyle,fontSynthesis,fontSynthesisPosition,fontSynthesisSmallCaps,fontSynthesisStyle,fontSynthesisWeight,fontVariant,fontVariantAlternates,fontVariantCaps,fontVariantEastAsian,fontVariantEmoji,fontVariantLigatures,fontVariantNumeric,fontVariantPosition,fontVariationSettings,fontWeight,forcedColorAdjust,gap,grid,gridArea,gridAutoColumns,gridAutoFlow,gridAutoRows,gridColumn,gridColumnEnd,gridColumnGap,gridColumnStart,gridGap,gridRow,gridRowEnd,gridRowGap,gridRowStart,gridTemplate,gridTemplateAreas,gridTemplateColumns,gridTemplateRows,hangingPunctuation,height,hyphenateCharacter,hyphenateLimitChars,hyphens,imageOrientation,imageRendering,imageResolution,imeMode,initialLetter,initialLetterAlign,inlineSize,inset,insetBlock,insetBlockEnd,insetBlockStart,insetInline,insetInlineEnd,insetInlineStart,interpolateSize,isolation,justifyContent,justifyItems,justifySelf,justifyTracks,left,letterSpacing,lightingColor,lineBreak,lineClamp,lineHeight,lineHeightStep,listStyle,listStyleImage,listStylePosition,listStyleType,margin,marginBlock,marginBlockEnd,marginBlockStart,marginBottom,marginInline,marginInlineEnd,marginInlineStart,marginLeft,marginRight,marginTop,marginTrim,marker,markerEnd,markerMid,markerStart,mask,maskBorder,maskBorderMode,maskBorderOutset,maskBorderRepeat,maskBorderSlice,maskBorderSource,maskBorderWidth,maskClip,maskComposite,maskImage,maskMode,maskOrigin,maskPosition,maskRepeat,maskSize,maskType,masonryAutoFlow,mathDepth,mathShift,mathStyle,maxBlockSize,maxHeight,maxInlineSize,maxLines,maxWidth,minBlockSize,minHeight,minInlineSize,minWidth,mixBlendMode,objectFit,objectPosition,offset,offsetAnchor,offsetDistance,offsetPath,offsetPosition,offsetRotate,opacity,order,orphans,outline,outlineColor,outlineOffset,outlineStyle,outlineWidth,overflow,overflowAnchor,overflowBlock,overflowClipBox,overflowClipMargin,overflowInline,overflowWrap,overflowX,overflowY,overlay,overscrollBehavior,overscrollBehaviorBlock,overscrollBehaviorInline,overscrollBehaviorX,overscrollBehaviorY,padding,paddingBlock,paddingBlockEnd,paddingBlockStart,paddingBottom,paddingInline,paddingInlineEnd,paddingInlineStart,paddingLeft,paddingRight,paddingTop,page,pageBreakAfter,pageBreakBefore,pageBreakInside,paintOrder,perspective,perspectiveOrigin,placeContent,placeItems,placeSelf,pointerEvents,position,positionAnchor,positionArea,positionTry,positionTryFallbacks,positionTryOrder,positionVisibility,printColorAdjust,quotes,r,resize,right,rotate,rowGap,rubyAlign,rubyMerge,rubyPosition,rx,ry,scale,scrollBehavior,scrollMargin,scrollMarginBlock,scrollMarginBlockEnd,scrollMarginBlockStart,scrollMarginBottom,scrollMarginInline,scrollMarginInlineEnd,scrollMarginInlineStart,scrollMarginLeft,scrollMarginRight,scrollMarginTop,scrollPadding,scrollPaddingBlock,scrollPaddingBlockEnd,scrollPaddingBlockStart,scrollPaddingBottom,scrollPaddingInline,scrollPaddingInlineEnd,scrollPaddingInlineStart,scrollPaddingLeft,scrollPaddingRight,scrollPaddingTop,scrollSnapAlign,scrollSnapCoordinate,scrollSnapDestination,scrollSnapPointsX,scrollSnapPointsY,scrollSnapStop,scrollSnapType,scrollSnapTypeX,scrollSnapTypeY,scrollTimeline,scrollTimelineAxis,scrollTimelineName,scrollbarColor,scrollbarGutter,scrollbarWidth,shapeImageThreshold,shapeMargin,shapeOutside,shapeRendering,stopColor,stopOpacity,stroke,strokeDasharray,strokeDashoffset,strokeLinecap,strokeLinejoin,strokeMiterlimit,strokeOpacity,strokeWidth,tabSize,tableLayout,textAlign,textAlignLast,textAnchor,textBox,textBoxEdge,textBoxTrim,textCombineUpright,textDecoration,textDecorationColor,textDecorationLine,textDecorationSkip,textDecorationSkipInk,textDecorationStyle,textDecorationThickness,textEmphasis,textEmphasisColor,textEmphasisPosition,textEmphasisStyle,textIndent,textJustify,textOrientation,textOverflow,textRendering,textShadow,textSizeAdjust,textSpacingTrim,textTransform,textUnderlineOffset,textUnderlinePosition,textWrap,textWrapMode,textWrapStyle,timelineScope,top,touchAction,transform,transformBox,transformOrigin,transformStyle,transition,transitionBehavior,transitionDelay,transitionDuration,transitionProperty,transitionTimingFunction,translate,unicodeBidi,userSelect,vectorEffect,verticalAlign,viewTimeline,viewTimelineAxis,viewTimelineInset,viewTimelineName,viewTransitionName,visibility,whiteSpace,whiteSpaceCollapse,widows,width,willChange,wordBreak,wordSpacing,wordWrap,writingMode,x,y,zIndex,zoom,alignmentBaseline,baselineShift,colorInterpolation,colorRendering,glyphOrientationVertical\".split(\",\").concat(userGenerated);\nconst properties = new Map(allCssProperties.map((prop) => [prop, true]));\nfunction memo(fn) {\n const cache = Object.create(null);\n return (arg) => {\n if (cache[arg] === void 0) cache[arg] = fn(arg);\n return cache[arg];\n };\n}\nconst cssPropertySelectorRegex = /&|@/;\nconst isCssProperty = /* @__PURE__ */ memo((prop) => {\n return properties.has(prop) || prop.startsWith(\"--\") || cssPropertySelectorRegex.test(prop);\n});\n//#endregion\nexport { allCssProperties, isCssProperty };\n";
452
471
  //#endregion
453
472
  //#region src/artifacts/js/is-valid-prop.ts
454
- const cssPropRegex = /var cssPropertiesStr = ".*?";/;
455
- const memoFnDeclarationRegex = /function memo(.+?)\nvar cssPropertySelectorRegex/s;
473
+ const cssPropListRegex = /const userGenerated = ".*?"\.split\(","\);\s*const allCssProperties = "(.*?)"\.split\(","\)\.concat\(userGenerated\);/;
474
+ const memoFnDeclarationRegex = /function memo(?:.+?)\n((?:var|const|let) cssPropertySelectorRegex)/s;
456
475
  function generateIsValidProp(ctx) {
457
476
  if (ctx.isTemplateLiteralSyntax) return;
458
477
  let content = content$8;
459
- content = content.replace("var userGeneratedStr = \"\";", `var userGeneratedStr = "${(0, ts_pattern.match)(ctx.jsx.styleProps).with("all", () => Array.from(ctx.properties).join(",")).with("minimal", () => "css").with("none", () => "css").exhaustive()}"`);
460
- content = content.replace(memoFnDeclarationRegex, "var cssPropertySelectorRegex");
461
- if (ctx.jsx.styleProps === "minimal" || ctx.jsx.styleProps === "none") {
462
- content = content.replace("/* @__PURE__ */ memo(", "/* @__PURE__ */ (");
463
- content = content.replace(cssPropRegex, "var cssPropertiesStr = \"\";");
464
- } else content = ctx.file.import("memo", "../helpers") + "\n" + content;
478
+ const propertyList = content.match(cssPropListRegex);
479
+ if (!propertyList) throw new _bamboocss_shared.BambooError("NOT_FOUND", "Could not find the property list in the prebuilt is-valid-prop module. Its bundled shape has changed.");
480
+ const userProperties = (0, ts_pattern.match)(ctx.jsx.styleProps).with("all", () => Array.from(ctx.properties)).with("minimal", () => ["css"]).with("none", () => ["css"]).exhaustive();
481
+ const browserProperties = ctx.jsx.styleProps === "all" ? propertyList[1].split(",") : [];
482
+ content = content.replace(cssPropListRegex, () => `const allCssProperties = "${(0, _bamboocss_shared.uniq)(browserProperties, userProperties).join(",")}".split(",");`);
483
+ content = content.replace(memoFnDeclarationRegex, "$1");
484
+ if (ctx.jsx.styleProps === "minimal" || ctx.jsx.styleProps === "none") content = content.replace("/* @__PURE__ */ memo(", "/* @__PURE__ */ (");
485
+ else content = ctx.file.import("memo", "../helpers") + "\n" + content;
465
486
  content = ctx.file.import("splitProps", "../helpers") + "\n" + content;
466
487
  content += `export const splitCssProps = (props) => splitProps(props, isCssProperty)`;
467
488
  return {
@@ -513,6 +534,56 @@ function generatedJsxHelpers(ctx) {
513
534
  `) };
514
535
  }
515
536
  //#endregion
537
+ //#region src/artifacts/js/package-json.ts
538
+ /**
539
+ * The generated output is a plain directory, not an installed package, so bundlers
540
+ * have no `sideEffects` hint for it and must assume every module mutates something.
541
+ * That keeps every module a barrel pulls in — importing a single component from
542
+ * `styled-system/jsx` retains all patterns.
543
+ *
544
+ * The CSS globs are required: `sideEffects: false` alone lets a bundler drop a bare
545
+ * `import 'styled-system/styles.css'`. Both shapes are listed because the stylesheet
546
+ * is emitted at the root (`styles.css`) and, under `splitting`, in `styles/`.
547
+ *
548
+ * `type: module` restates what the directory already is. Adding a package.json makes
549
+ * this directory its own package boundary, so `.js` output would otherwise stop
550
+ * inheriting the consumer's `type` and be re-read as CommonJS; the emitted code is
551
+ * always ESM. It is a no-op for the default `.mjs` extension.
552
+ *
553
+ * `private` is there because the same package boundary makes a workspace glob able to
554
+ * match this directory. It is never published.
555
+ *
556
+ * `name` has to be present for the same reason. This file used to be emitted without
557
+ * one, to keep two outputs in a single workspace from colliding — but a nameless
558
+ * package.json is not a package a workspace scanner skips, it is one it refuses:
559
+ * pnpm, npm and changesets all abort with `missing the "name" field` and no hint as
560
+ * to which directory produced it.
561
+ *
562
+ * The name is derived from `outdir` because that is the only input that is both
563
+ * deterministic and portable — `cwd` is absolute, so putting it here would make the
564
+ * generated output differ per machine. Two projects in one workspace that both keep
565
+ * the default `outdir` therefore still collide, but on a duplicate-name error that
566
+ * names both paths and is resolved by setting `outdir`, rather than on a missing
567
+ * field that points nowhere.
568
+ */
569
+ function generatePackageJson(ctx) {
570
+ return { json: JSON.stringify({
571
+ name: toPackageName(ctx.config.outdir),
572
+ type: "module",
573
+ private: true,
574
+ sideEffects: ["*.css", "**/*.css"]
575
+ }, null, 2) + "\n" };
576
+ }
577
+ /**
578
+ * `outdir` is a path, npm names are not: it may be nested (`src/styled-system`), and
579
+ * npm rejects uppercase, a leading dot or underscore, and anything outside its
580
+ * url-safe set. Path segments are joined rather than dropped so that nested outputs
581
+ * stay distinct from one another.
582
+ */
583
+ function toPackageName(outdir) {
584
+ return outdir.split(/[\\/]/).filter(Boolean).join("-").toLowerCase().replace(/[^a-z0-9\-._]/g, "-").replace(/^[._]+/, "") || "styled-system";
585
+ }
586
+ //#endregion
516
587
  //#region src/artifacts/js/pattern.ts
517
588
  function generatePattern(ctx, filters) {
518
589
  if (ctx.patterns.isEmpty()) return;
@@ -523,7 +594,7 @@ function generatePattern(ctx, filters) {
523
594
  transform,
524
595
  defaultValues
525
596
  })) ?? "";
526
- const helperImports = ["getPatternStyles, patternFns"];
597
+ const helperImports = ["getPatternStyles, patternFns, memo"];
527
598
  if (patternConfigFn.includes("__spreadValues")) helperImports.push("__spreadValues");
528
599
  if (patternConfigFn.includes("__objRest")) helperImports.push("__objRest");
529
600
  return {
@@ -576,7 +647,7 @@ function generatePattern(ctx, filters) {
576
647
  return ${baseName}Config.transform(_styles, patternFns)
577
648
  }
578
649
 
579
- export const ${baseName} = (styles) => css(${styleFnName}(styles))
650
+ export const ${baseName} = /* @__PURE__ */ memo((styles) => css(${styleFnName}(styles)))
580
651
  ${baseName}.raw = ${styleFnName}
581
652
  `
582
653
  };
@@ -976,7 +1047,6 @@ function generatePreactJsxFactory(ctx) {
976
1047
  return { js: outdent.outdent`
977
1048
  import { h } from 'preact'
978
1049
  import { forwardRef } from 'preact/compat'
979
- import { useMemo } from 'preact/hooks'
980
1050
  ${ctx.file.import("defaultShouldForwardProp, composeShouldForwardProps, composeCvaFn, getDisplayName", "./factory-helper")}
981
1051
  ${ctx.file.import("isCssProperty", "./is-valid-prop")}
982
1052
  ${ctx.file.import("css, cx, cva", "../css/index")}
@@ -1004,11 +1074,13 @@ function generatePreactJsxFactory(ctx) {
1004
1074
  const { as: Element = __base__, unstyled, children, ...restProps } = props
1005
1075
 
1006
1076
 
1007
- const combinedProps = useMemo(() => Object.assign({}, defaultProps, restProps), [restProps])
1077
+ // Not memoized, deliberately. \`restProps\` is rest destructuring, so it is a fresh
1078
+ // object on every render and a dependency on it can never match — a memo here is a
1079
+ // guaranteed miss that still costs a hook slot, a deps array and a retained cell.
1080
+ const combinedProps = Object.assign({}, defaultProps, restProps)
1008
1081
 
1009
- const [htmlProps, forwardedProps, variantProps, styleProps, elementProps] = useMemo(() => {
1010
- return splitProps(combinedProps, normalizeHTMLProps.keys, __shouldForwardProps__, __cvaFn__.variantKeys, isCssProperty)
1011
- }, [combinedProps])
1082
+ const [htmlProps, forwardedProps, variantProps, styleProps, elementProps] =
1083
+ splitProps(combinedProps, normalizeHTMLProps.keys, __shouldForwardProps__, __cvaFn__.variantKeys, isCssProperty)
1012
1084
 
1013
1085
  function recipeClass() {
1014
1086
  const { css: cssStyles, ...propStyles } = styleProps
@@ -1836,7 +1908,7 @@ export type ${typeName}<T extends ElementType> = ComponentProps<T>
1836
1908
  function generateReactJsxFactory(ctx) {
1837
1909
  const { factoryName, componentName } = ctx.jsx;
1838
1910
  return { js: outdent.outdent`
1839
- import { createElement, forwardRef, useMemo } from 'react'
1911
+ import { createElement, forwardRef } from 'react'
1840
1912
  ${ctx.file.import("css, cx, cva", "../css/index")}
1841
1913
  ${ctx.file.import("defaultShouldForwardProp, composeShouldForwardProps, composeCvaFn, getDisplayName", "./factory-helper")}
1842
1914
  ${ctx.file.import("splitProps, normalizeHTMLProps", "../helpers")}
@@ -1863,11 +1935,13 @@ function generateReactJsxFactory(ctx) {
1863
1935
  const ${componentName} = /* @__PURE__ */ forwardRef(function ${componentName}(props, ref) {
1864
1936
  const { as: Element = __base__, unstyled, children, ...restProps } = props
1865
1937
 
1866
- const combinedProps = useMemo(() => Object.assign({}, defaultProps, restProps), [restProps])
1938
+ // Not memoized, deliberately. \`restProps\` is rest destructuring, so it is a fresh
1939
+ // object on every render and a dependency on it can never match — a memo here is a
1940
+ // guaranteed miss that still costs a hook slot, a deps array and a retained cell.
1941
+ const combinedProps = Object.assign({}, defaultProps, restProps)
1867
1942
 
1868
- const [htmlProps, forwardedProps, variantProps, styleProps, elementProps] = useMemo(() => {
1869
- return splitProps(combinedProps, normalizeHTMLProps.keys, __shouldForwardProps__, __cvaFn__.variantKeys, isCssProperty)
1870
- }, [combinedProps])
1943
+ const [htmlProps, forwardedProps, variantProps, styleProps, elementProps] =
1944
+ splitProps(combinedProps, normalizeHTMLProps.keys, __shouldForwardProps__, __cvaFn__.variantKeys, isCssProperty)
1871
1945
 
1872
1946
  function recipeClass() {
1873
1947
  const { css: cssStyles, ...propStyles } = styleProps
@@ -4552,8 +4626,8 @@ function generatePropTypes(ctx) {
4552
4626
  * fontSize: '[123px]', // ⚠️ will not throw even if you haven't defined 123px as a token
4553
4627
  * })
4554
4628
  *
4555
- * @see https://bamboo-css.com/docs/concepts/writing-styles#stricttokens
4556
- * @see https://bamboo-css.com/docs/concepts/writing-styles#strictpropertyvalues
4629
+ * @see https://bamboocss.com/docs/concepts/writing-styles#stricttokens
4630
+ * @see https://bamboocss.com/docs/concepts/writing-styles#strictpropertyvalues
4557
4631
  */
4558
4632
  export type WithEscapeHatch<T> = T | \`[\${string}]\` | WithColorOpacityModifier<T> | WithImportant<T>
4559
4633
 
@@ -4565,7 +4639,7 @@ function generatePropTypes(ctx) {
4565
4639
  * display: 'abc', // ❌ will throw
4566
4640
  * })
4567
4641
  *
4568
- * @see https://bamboo-css.com/docs/concepts/writing-styles#strictpropertyvalues
4642
+ * @see https://bamboocss.com/docs/concepts/writing-styles#strictpropertyvalues
4569
4643
  */
4570
4644
  export type OnlyKnown<Key, Value> = Value extends boolean
4571
4645
  ? Value
@@ -4981,6 +5055,15 @@ function setupHelpers(ctx) {
4981
5055
  }]
4982
5056
  };
4983
5057
  }
5058
+ function setupPackageJson(ctx) {
5059
+ return {
5060
+ id: "package.json",
5061
+ files: [{
5062
+ file: "package.json",
5063
+ code: generatePackageJson(ctx).json
5064
+ }]
5065
+ };
5066
+ }
4984
5067
  function setupDesignTokens(ctx) {
4985
5068
  const code = generateTokenJs(ctx);
4986
5069
  return {
@@ -5436,6 +5519,7 @@ const filterArtifactsFiles = (artifacts, filters) => {
5436
5519
  });
5437
5520
  };
5438
5521
  const entries = [
5522
+ ["package.json", setupPackageJson],
5439
5523
  ["helpers", setupHelpers],
5440
5524
  ["design-tokens", setupDesignTokens],
5441
5525
  ["types-jsx", setupJsxTypes],
@@ -6079,6 +6163,136 @@ var Generator = class extends _bamboocss_core.Context {
6079
6163
  const decoder = this.decoder.collect(this.encoder);
6080
6164
  sheet.processDecoder(decoder);
6081
6165
  };
6166
+ /**
6167
+ * Drop token css variables nothing can reach. Call this only once the sheet holds the
6168
+ * whole stylesheet — a baseline-only sheet has no utilities to reference anything, so
6169
+ * every token would look unused.
6170
+ *
6171
+ * `keep` carries references this cannot see for itself; see `collectTokenReferences`.
6172
+ */
6173
+ pruneTokens = (sheet, keep) => {
6174
+ if (!this.config.pruneUnusedTokens) return;
6175
+ const layers = sheet.layers;
6176
+ const result = (0, _bamboocss_core.pruneTokenVars)({
6177
+ scan: [
6178
+ layers.reset,
6179
+ layers.base,
6180
+ layers.tokens,
6181
+ layers.recipes,
6182
+ layers.recipes_base,
6183
+ layers.recipes_slots,
6184
+ layers.recipes_slots_base,
6185
+ layers.utilities,
6186
+ layers.compositions
6187
+ ],
6188
+ target: layers.tokens,
6189
+ tokenVars: this.getTokenVarNames(),
6190
+ keep: new Set([
6191
+ ...this.getAlwaysKeptTokenVars(),
6192
+ ...this.getThemeTokenVars(),
6193
+ ...keep ?? []
6194
+ ])
6195
+ });
6196
+ _bamboocss_logger.logger.debug("prune:tokens", `Removed ${result.removed} unused token css variable(s)`);
6197
+ return result;
6198
+ };
6199
+ /**
6200
+ * Drop `@keyframes` nothing can reach. Same completeness requirement as
6201
+ * `pruneTokens`: the sheet has to hold the whole stylesheet, or every keyframe looks
6202
+ * unused for want of a utility to reference it.
6203
+ *
6204
+ * `keep` carries names this cannot see for itself; see `collectKeyframeReferences`.
6205
+ */
6206
+ pruneKeyframes = (sheet, keep) => {
6207
+ if (!this.config.pruneUnusedKeyframes) return;
6208
+ const layers = sheet.layers;
6209
+ const keyframeNames = new Set(Object.keys(this.config.theme?.keyframes ?? {}));
6210
+ const result = (0, _bamboocss_core.pruneKeyframes)({
6211
+ scan: [
6212
+ layers.reset,
6213
+ layers.base,
6214
+ layers.tokens,
6215
+ layers.recipes,
6216
+ layers.recipes_base,
6217
+ layers.recipes_slots,
6218
+ layers.recipes_slots_base,
6219
+ layers.utilities,
6220
+ layers.compositions
6221
+ ],
6222
+ target: layers.tokens,
6223
+ keyframeNames,
6224
+ keep: new Set([...this.getThemeKeyframeNames(keyframeNames), ...keep ?? []])
6225
+ });
6226
+ _bamboocss_logger.logger.debug("prune:keyframes", `Removed ${result.removed} unused keyframe(s)`);
6227
+ return result;
6228
+ };
6229
+ /**
6230
+ * Keyframes the themes name.
6231
+ *
6232
+ * A theme is emitted as its own artifact and injected at runtime, so its css is not in
6233
+ * the sheet being pruned. A theme that points an animation token at a different
6234
+ * keyframe than the base does — `--animations-enter: fade-in` in the base and
6235
+ * `slide-up` under `dark` — would otherwise have that keyframe removed, because
6236
+ * nothing in the pruned sheet ever names it.
6237
+ */
6238
+ getThemeKeyframeNames = (keyframeNames) => {
6239
+ const names = /* @__PURE__ */ new Set();
6240
+ const themes = this.config.themes;
6241
+ if (!themes || !keyframeNames.size) return names;
6242
+ for (const themeName of Object.keys(themes)) for (const token of getThemeCss(this, themeName).split(/[^\w-]+/)) if (keyframeNames.has(token)) names.add(token);
6243
+ return names;
6244
+ };
6245
+ /**
6246
+ * Every custom property the token system declares. Used as the allow-list of what may
6247
+ * be removed, so custom properties from `globalCss` are never touched.
6248
+ */
6249
+ getTokenVarNames = () => {
6250
+ const names = /* @__PURE__ */ new Set();
6251
+ for (const values of this.tokens.view.vars.values()) for (const name of values.keys()) names.add(name);
6252
+ return names;
6253
+ };
6254
+ /**
6255
+ * Everything the themes refer to.
6256
+ *
6257
+ * A theme is emitted as its own artifact and injected at runtime, so its css is not in
6258
+ * the sheet being pruned and nothing there points at what it needs. A theme that maps a
6259
+ * token onto a base colour would otherwise be left referring to a declaration that has
6260
+ * been removed.
6261
+ */
6262
+ getThemeTokenVars = () => {
6263
+ const names = /* @__PURE__ */ new Set();
6264
+ const themes = this.config.themes;
6265
+ if (!themes) return names;
6266
+ for (const themeName of Object.keys(themes)) for (const name of (0, _bamboocss_shared.cssVarRefs)(getThemeCss(this, themeName))) names.add(name);
6267
+ return names;
6268
+ };
6269
+ /**
6270
+ * Tokens whose javascript value is a `var()` reference rather than a literal.
6271
+ * `token('colors.text')` hands those to the caller as a reference, so the declaration
6272
+ * has to survive whether or not the generated css mentions it. Ordinary tokens resolve
6273
+ * to a literal in javascript and need no such exemption.
6274
+ *
6275
+ * The two cases mirror `generateTokenJs`, which is what decides the value javascript
6276
+ * actually receives:
6277
+ *
6278
+ * - A virtual token, or one carrying a condition, is handed its own `varRef`.
6279
+ * - A negative token is handed `calc(var(--x) * -1)`, so it is a reference too — but to
6280
+ * the *positive* token's declaration. Its own var is never declared, so the name has
6281
+ * to come out of the value.
6282
+ */
6283
+ getAlwaysKeptTokenVars = () => {
6284
+ const names = /* @__PURE__ */ new Set();
6285
+ this.tokens.allTokens.forEach((token) => {
6286
+ const { isVirtual, isNegative, condition, var: varName } = token.extensions;
6287
+ if (isVirtual || condition !== "base") {
6288
+ if (varName) names.add(varName.startsWith("--") ? varName : `--${varName}`);
6289
+ return;
6290
+ }
6291
+ if (!isNegative) return;
6292
+ for (const name of (0, _bamboocss_shared.cssVarRefs)(token.value)) names.add(name);
6293
+ });
6294
+ return names;
6295
+ };
6082
6296
  getParserCss = (decoder) => {
6083
6297
  return generateParserCss(this, decoder);
6084
6298
  };
package/dist/index.d.cts CHANGED
@@ -29,6 +29,67 @@ declare class Generator extends Context {
29
29
  appendLayerParams: (sheet: Stylesheet) => void;
30
30
  appendBaselineCss: (sheet: Stylesheet) => void;
31
31
  appendParserCss: (sheet: Stylesheet) => void;
32
+ /**
33
+ * Drop token css variables nothing can reach. Call this only once the sheet holds the
34
+ * whole stylesheet — a baseline-only sheet has no utilities to reference anything, so
35
+ * every token would look unused.
36
+ *
37
+ * `keep` carries references this cannot see for itself; see `collectTokenReferences`.
38
+ */
39
+ pruneTokens: (sheet: Stylesheet, keep?: Set<string>) => {
40
+ removed: number;
41
+ kept: number;
42
+ } | undefined;
43
+ /**
44
+ * Drop `@keyframes` nothing can reach. Same completeness requirement as
45
+ * `pruneTokens`: the sheet has to hold the whole stylesheet, or every keyframe looks
46
+ * unused for want of a utility to reference it.
47
+ *
48
+ * `keep` carries names this cannot see for itself; see `collectKeyframeReferences`.
49
+ */
50
+ pruneKeyframes: (sheet: Stylesheet, keep?: Set<string>) => {
51
+ removed: number;
52
+ kept: number;
53
+ } | undefined;
54
+ /**
55
+ * Keyframes the themes name.
56
+ *
57
+ * A theme is emitted as its own artifact and injected at runtime, so its css is not in
58
+ * the sheet being pruned. A theme that points an animation token at a different
59
+ * keyframe than the base does — `--animations-enter: fade-in` in the base and
60
+ * `slide-up` under `dark` — would otherwise have that keyframe removed, because
61
+ * nothing in the pruned sheet ever names it.
62
+ */
63
+ private getThemeKeyframeNames;
64
+ /**
65
+ * Every custom property the token system declares. Used as the allow-list of what may
66
+ * be removed, so custom properties from `globalCss` are never touched.
67
+ */
68
+ private getTokenVarNames;
69
+ /**
70
+ * Everything the themes refer to.
71
+ *
72
+ * A theme is emitted as its own artifact and injected at runtime, so its css is not in
73
+ * the sheet being pruned and nothing there points at what it needs. A theme that maps a
74
+ * token onto a base colour would otherwise be left referring to a declaration that has
75
+ * been removed.
76
+ */
77
+ private getThemeTokenVars;
78
+ /**
79
+ * Tokens whose javascript value is a `var()` reference rather than a literal.
80
+ * `token('colors.text')` hands those to the caller as a reference, so the declaration
81
+ * has to survive whether or not the generated css mentions it. Ordinary tokens resolve
82
+ * to a literal in javascript and need no such exemption.
83
+ *
84
+ * The two cases mirror `generateTokenJs`, which is what decides the value javascript
85
+ * actually receives:
86
+ *
87
+ * - A virtual token, or one carrying a condition, is handed its own `varRef`.
88
+ * - A negative token is handed `calc(var(--x) * -1)`, so it is a reference too — but to
89
+ * the *positive* token's declaration. Its own var is never declared, so the name has
90
+ * to come out of the value.
91
+ */
92
+ private getAlwaysKeptTokenVars;
32
93
  getParserCss: (decoder: StyleDecoder) => string;
33
94
  getCss: (stylesheet?: Stylesheet) => string;
34
95
  /**
package/dist/index.d.mts CHANGED
@@ -29,6 +29,67 @@ declare class Generator extends Context {
29
29
  appendLayerParams: (sheet: Stylesheet) => void;
30
30
  appendBaselineCss: (sheet: Stylesheet) => void;
31
31
  appendParserCss: (sheet: Stylesheet) => void;
32
+ /**
33
+ * Drop token css variables nothing can reach. Call this only once the sheet holds the
34
+ * whole stylesheet — a baseline-only sheet has no utilities to reference anything, so
35
+ * every token would look unused.
36
+ *
37
+ * `keep` carries references this cannot see for itself; see `collectTokenReferences`.
38
+ */
39
+ pruneTokens: (sheet: Stylesheet, keep?: Set<string>) => {
40
+ removed: number;
41
+ kept: number;
42
+ } | undefined;
43
+ /**
44
+ * Drop `@keyframes` nothing can reach. Same completeness requirement as
45
+ * `pruneTokens`: the sheet has to hold the whole stylesheet, or every keyframe looks
46
+ * unused for want of a utility to reference it.
47
+ *
48
+ * `keep` carries names this cannot see for itself; see `collectKeyframeReferences`.
49
+ */
50
+ pruneKeyframes: (sheet: Stylesheet, keep?: Set<string>) => {
51
+ removed: number;
52
+ kept: number;
53
+ } | undefined;
54
+ /**
55
+ * Keyframes the themes name.
56
+ *
57
+ * A theme is emitted as its own artifact and injected at runtime, so its css is not in
58
+ * the sheet being pruned. A theme that points an animation token at a different
59
+ * keyframe than the base does — `--animations-enter: fade-in` in the base and
60
+ * `slide-up` under `dark` — would otherwise have that keyframe removed, because
61
+ * nothing in the pruned sheet ever names it.
62
+ */
63
+ private getThemeKeyframeNames;
64
+ /**
65
+ * Every custom property the token system declares. Used as the allow-list of what may
66
+ * be removed, so custom properties from `globalCss` are never touched.
67
+ */
68
+ private getTokenVarNames;
69
+ /**
70
+ * Everything the themes refer to.
71
+ *
72
+ * A theme is emitted as its own artifact and injected at runtime, so its css is not in
73
+ * the sheet being pruned and nothing there points at what it needs. A theme that maps a
74
+ * token onto a base colour would otherwise be left referring to a declaration that has
75
+ * been removed.
76
+ */
77
+ private getThemeTokenVars;
78
+ /**
79
+ * Tokens whose javascript value is a `var()` reference rather than a literal.
80
+ * `token('colors.text')` hands those to the caller as a reference, so the declaration
81
+ * has to survive whether or not the generated css mentions it. Ordinary tokens resolve
82
+ * to a literal in javascript and need no such exemption.
83
+ *
84
+ * The two cases mirror `generateTokenJs`, which is what decides the value javascript
85
+ * actually receives:
86
+ *
87
+ * - A virtual token, or one carrying a condition, is handed its own `varRef`.
88
+ * - A negative token is handed `calc(var(--x) * -1)`, so it is a reference too — but to
89
+ * the *positive* token's declaration. Its own var is never declared, so the name has
90
+ * to come out of the value.
91
+ */
92
+ private getAlwaysKeptTokenVars;
32
93
  getParserCss: (decoder: StyleDecoder) => string;
33
94
  getCss: (stylesheet?: Stylesheet) => string;
34
95
  /**
package/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
- import { Context, Recipes, expandNestedCss, extractParentSelectors, extractTrailingPseudos, stringify } from "@bamboocss/core";
2
- import { BambooError, capitalize, compact, dashCase, isBoolean, isObject, mapEntries, unionType, walkObject } from "@bamboocss/shared";
1
+ import { Context, Recipes, expandNestedCss, extractParentSelectors, extractTrailingPseudos, pruneKeyframes, pruneTokenVars, stringify } from "@bamboocss/core";
2
+ import { logger } from "@bamboocss/logger";
3
+ import { BambooError, capitalize, compact, cssVarRefs, dashCase, isBoolean, isObject, mapEntries, unionType, uniq, walkObject } from "@bamboocss/shared";
3
4
  import { match } from "ts-pattern";
4
5
  import outdent$1, { outdent } from "outdent";
5
6
  import { stringify as stringify$1 } from "javascript-stringify";
6
7
  import { allCssProperties } from "@bamboocss/is-valid-prop";
7
8
  import pluralize from "pluralize";
8
- import { logger } from "@bamboocss/logger";
9
9
  import postcss, { CssSyntaxError } from "postcss";
10
10
  //#region src/artifacts/js/conditions.ts
11
11
  function formatConditionJsDoc(raw) {
@@ -138,9 +138,15 @@ function generateCssFn(ctx) {
138
138
  }
139
139
 
140
140
  export declare const css: CssFunction;
141
+
142
+ /**
143
+ * Internal. Emitted for the source transform, which rewrites a single dynamic style
144
+ * leaf into a call to this. Not part of the authoring API.
145
+ */
146
+ export declare const cssLeaf: (prefix: string, prop: string, value: unknown) => string;
141
147
  `,
142
148
  js: outdent`
143
- ${ctx.file.import("createCss, createMergeCss, hypenateProperty, withoutSpace", "../helpers")}
149
+ ${ctx.file.import("cloneStyles, createCss, createMergeCss, hypenateProperty, leafClass, memo, withoutSpace", "../helpers")}
144
150
  ${ctx.file.import("sortConditions, finalizeConditions", "./conditions")}
145
151
 
146
152
  const utilities = "${utility.entries().map(([prop, className]) => {
@@ -191,8 +197,19 @@ function generateCssFn(ctx) {
191
197
  }
192
198
 
193
199
  const cssFn = createCss(context)
194
- export const css = (...styles) => cssFn(mergeCss(...styles))
195
- css.raw = (...styles) => mergeCss(...styles)
200
+ export const css = /* @__PURE__ */ memo((...styles) => cssFn(mergeCss(...styles)))
201
+ // The merged result is cached and shared, so a caller mutating a nested
202
+ // condition object would otherwise poison it for everyone after them.
203
+ css.raw = (...styles) => cloneStyles(mergeCss(...styles))
204
+
205
+ // Emitted for the source transform, which rewrites a single dynamic style leaf into a
206
+ // call to this rather than leaving a \`css()\` behind. \`prefix\` is the class up to the
207
+ // value, resolved at build time; \`prop\` is only used for the shapes \`leafClass\`
208
+ // declines, which have to run the real thing.
209
+ export const cssLeaf = (prefix, prop, value) => {
210
+ const className = leafClass(prefix, value)
211
+ return className === undefined ? css({ [prop]: value }) : className
212
+ }
196
213
 
197
214
  export const { mergeCss, assignCss } = createMergeCss(context)
198
215
  `
@@ -227,7 +244,7 @@ function generateStringLiteralCssFn(ctx) {
227
244
  export declare const css: CssFunction;
228
245
  `,
229
246
  js: outdent`
230
- ${ctx.file.import("astish, createCss, isObject, mergeProps, withoutSpace", "../helpers")}
247
+ ${ctx.file.import("astish, cloneStyles, createCss, isObject, mergeProps, withoutSpace", "../helpers")}
231
248
  ${ctx.file.import("finalizeConditions, sortConditions", "./conditions")}
232
249
 
233
250
  function transform(prop, value) {
@@ -257,7 +274,9 @@ function generateStringLiteralCssFn(ctx) {
257
274
 
258
275
  const fn = (style) => (isObject(style) ? style : astish(style[0]))
259
276
  export const css = (...styles) => cssFn(mergeProps(...styles.filter(Boolean).map(fn)))
260
- css.raw = (...styles) => mergeProps(...styles.filter(Boolean).map(fn))
277
+ // Same independence guarantee as the object-syntax css.raw(), so the public
278
+ // API behaves identically across both syntaxes.
279
+ css.raw = (...styles) => cloneStyles(mergeProps(...styles.filter(Boolean).map(fn)))
261
280
  `
262
281
  };
263
282
  }
@@ -266,7 +285,7 @@ function generateStringLiteralCssFn(ctx) {
266
285
  function generateCvaFn(ctx) {
267
286
  return {
268
287
  js: outdent`
269
- ${ctx.file.import("compact, mergeProps, memo, splitProps, uniq", "../helpers")}
288
+ ${ctx.file.import("cloneStyles, compact, mergeProps, memo, splitProps, uniq", "../helpers")}
270
289
  ${ctx.file.import("css, mergeCss", "./css")}
271
290
 
272
291
  const defaults = (conf) => ({
@@ -322,7 +341,7 @@ function generateCvaFn(ctx) {
322
341
  __cva__: true,
323
342
  variantMap,
324
343
  variantKeys,
325
- raw: resolve,
344
+ raw: (...args) => cloneStyles(resolve(...args)),
326
345
  config,
327
346
  merge,
328
347
  splitVariantProps,
@@ -398,7 +417,7 @@ function generateCx() {
398
417
  var content$11 = "//#region src/astish.ts\nconst newRule = /(?:([\\u0080-\\uFFFF\\w-%@]+) *:? *([^{;]+?);|([^;}{]*?) *{)|(}\\s*)/g;\nconst ruleClean = /\\/\\*[^]*?\\*\\/| +/g;\nconst ruleNewline = /\\n+/g;\nconst empty = \" \";\nconst astish = (val, tree = [{}]) => {\n if (!val) return tree[0];\n let block, left;\n while (block = newRule.exec(val.replace(ruleClean, \"\"))) if (block[4]) tree.shift();\n else if (block[3]) {\n left = block[3].replace(ruleNewline, empty).trim();\n if (!left.includes(\"&\") && !left.startsWith(\"@\")) left = \"& \" + left;\n tree.unshift(tree[0][left] = tree[0][left] || {});\n } else tree[0][block[1]] = block[2].replace(ruleNewline, empty).trim();\n return tree[0];\n};\n//#endregion\nexport { astish };\n";
399
418
  //#endregion
400
419
  //#region src/artifacts/generated/helpers.mjs.json
401
- var content$10 = "//#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/compact.ts\nfunction compact(value) {\n return Object.fromEntries(Object.entries(value ?? {}).filter(([_, value]) => value !== void 0));\n}\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;\nfunction isImportant(value) {\n return typeof value === \"string\" ? importantRegex.test(value) : false;\n}\nfunction withoutImportant(value) {\n return typeof value === \"string\" ? value.replace(importantRegex, \"\").trim() : value;\n}\nfunction withoutSpace(str) {\n return typeof str === \"string\" ? str.replaceAll(\" \", \"_\") : str;\n}\n//#endregion\n//#region src/memo.ts\nconst memo = (fn) => {\n const cache = /* @__PURE__ */ new Map();\n const get = (...args) => {\n const key = JSON.stringify(args);\n if (cache.has(key)) return cache.get(key);\n const result = fn(...args);\n cache.set(key, result);\n return result;\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}\nfunction normalizeStyleObject(styles, context, shorthand = true) {\n const { utility, conditions } = context;\n const { hasShorthand, resolveShorthand } = utility;\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 sanitize = (value) => typeof value === \"string\" ? value.replaceAll(/[\\n\\s]+/g, \" \") : value;\nconst ENTRY_SEP = \"]___[\";\nconst COND_SEP = \"<___>\";\nfunction createCss(context) {\n const { utility, hash, grouped, conditions: conds = fallbackCondition } = context;\n const formatClassName = (str) => [utility.prefix, str].filter(Boolean).join(\"-\");\n const hashFn = (conditions, className) => {\n let result;\n if (hash) {\n const baseArray = [...conds.finalize(conditions), className];\n result = formatClassName(utility.toHash(baseArray, toHash));\n } else result = [...conds.finalize(conditions), formatClassName(className)].join(\":\");\n return result;\n };\n if (grouped) return memo(({ base, ...styles } = {}) => {\n const normalizedObject = normalizeStyleObject(Object.assign(styles, base), context);\n const hashes = [];\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 });\n if (hashes.length === 0) return \"\";\n hashes.sort();\n const groupId = hashes.join(\"|\");\n return formatClassName(utility.toHash([\"grouped\", groupId], toHash));\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 important = isImportant(value);\n const [prop, ...allConditions] = conds.shift(paths);\n let className = hashFn(filterBaseConditions(allConditions), utility.transform(prop, withoutImportant(sanitize(value))).className);\n if (important) className = `${className}!`;\n classNames.add(className);\n });\n return Array.from(classNames).join(\" \");\n });\n}\nfunction compactStyles(...styles) {\n return styles.flat().filter((style) => isObject(style) && Object.keys(compact(style)).length > 0);\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/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/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\nfunction splitProps(props, ...keys) {\n const descriptors = Object.getOwnPropertyDescriptors(props);\n const dKeys = Object.keys(descriptors);\n const split = (k) => {\n const clone = {};\n for (let i = 0; i < k.length; i++) {\n const key = k[i];\n if (descriptors[key]) {\n Object.defineProperty(clone, key, descriptors[key]);\n delete descriptors[key];\n }\n }\n return clone;\n };\n const fn = (key) => split(Array.isArray(key) ? key : dKeys.filter(key));\n return keys.map(fn).concat(split(dKeys));\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\nexport { compact, createCss, createMergeCss, filterBaseConditions, getPatternStyles, getSlotCompoundVariant, getSlotRecipes, hypenateProperty, isBaseCondition, isObject, mapObject, memo, mergeProps, patternFns, splitProps, toHash, uniq, walkObject, withoutSpace };\n";
420
+ var content$10 = "//#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/compact.ts\nfunction compact(value) {\n return Object.fromEntries(Object.entries(value ?? {}).filter(([_, value]) => value !== void 0));\n}\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;\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 return typeof value === \"string\" ? value.replaceAll(/[\\n\\s]+/g, \" \") : value;\n}\nfunction isImportant(value) {\n return typeof value === \"string\" ? importantRegex.test(value) : false;\n}\nfunction withoutImportant(value) {\n return typeof value === \"string\" ? value.replace(importantRegex, \"\").trim() : value;\n}\nfunction withoutSpace(str) {\n return typeof str === \"string\" ? 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}\nfunction normalizeStyleObject(styles, context, shorthand = true) {\n const { utility, conditions } = context;\n const { hasShorthand, resolveShorthand } = utility;\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 = \"<___>\";\nfunction createCss(context) {\n const { utility, hash, grouped, conditions: conds = fallbackCondition } = context;\n const formatClassName = (str) => [utility.prefix, str].filter(Boolean).join(\"-\");\n const hashFn = (conditions, className) => {\n let result;\n if (hash) {\n const baseArray = [...conds.finalize(conditions), className];\n result = formatClassName(utility.toHash(baseArray, toHash));\n } else result = [...conds.finalize(conditions), formatClassName(className)].join(\":\");\n return result;\n };\n if (grouped) return memo(({ base, ...styles } = {}) => {\n const normalizedObject = normalizeStyleObject(Object.assign(styles, base), context);\n const hashes = [];\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 });\n if (hashes.length === 0) return \"\";\n hashes.sort();\n const groupId = hashes.join(\"|\");\n return formatClassName(utility.toHash([\"grouped\", groupId], toHash));\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 important = isImportant(value);\n const [prop, ...allConditions] = conds.shift(paths);\n let className = hashFn(filterBaseConditions(allConditions), utility.transform(prop, withoutImportant(sanitize(value))).className);\n if (important) className = `${className}!`;\n classNames.add(className);\n });\n return Array.from(classNames).join(\" \");\n });\n}\nfunction compactStyles(...styles) {\n return styles.flat().filter((style) => isObject(style) && Object.keys(compact(style)).length > 0);\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/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/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 return keys.map((key) => split(Array.isArray(key) ? key : allKeys.filter(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\nexport { cloneStyles, compact, createCss, createMergeCss, filterBaseConditions, getPatternStyles, getSlotCompoundVariant, getSlotRecipes, hypenateProperty, isBaseCondition, isObject, leafClass, mapObject, memo, mergeProps, patternFns, splitProps, toHash, uniq, walkObject, withoutSpace };\n";
402
421
  //#endregion
403
422
  //#region src/artifacts/generated/normalize-html.mjs.json
404
423
  var content$9 = "//#region src/normalize-html.ts\nconst htmlProps = [\n \"htmlSize\",\n \"htmlTranslate\",\n \"htmlWidth\",\n \"htmlHeight\"\n];\nfunction convert(key) {\n return htmlProps.includes(key) ? key.replace(\"html\", \"\").toLowerCase() : key;\n}\nfunction normalizeHTMLProps(props) {\n return Object.fromEntries(Object.entries(props).map(([key, value]) => [convert(key), value]));\n}\nnormalizeHTMLProps.keys = htmlProps;\n//#endregion\nexport { normalizeHTMLProps };\n";
@@ -425,17 +444,19 @@ function generateHelpers(ctx) {
425
444
  var content$8 = "//#region src/index.ts\nconst userGenerated = \"\".split(\",\");\nconst allCssProperties = \"WebkitAppearance,WebkitBorderBefore,WebkitBorderBeforeColor,WebkitBorderBeforeStyle,WebkitBorderBeforeWidth,WebkitBoxReflect,WebkitLineClamp,WebkitMask,WebkitMaskAttachment,WebkitMaskClip,WebkitMaskComposite,WebkitMaskImage,WebkitMaskOrigin,WebkitMaskPosition,WebkitMaskPositionX,WebkitMaskPositionY,WebkitMaskRepeat,WebkitMaskRepeatX,WebkitMaskRepeatY,WebkitMaskSize,WebkitOverflowScrolling,WebkitTapHighlightColor,WebkitTextFillColor,WebkitTextStroke,WebkitTextStrokeColor,WebkitTextStrokeWidth,WebkitTouchCallout,WebkitUserModify,WebkitUserSelect,accentColor,alignContent,alignItems,alignSelf,alignTracks,all,anchorName,anchorScope,animation,animationComposition,animationDelay,animationDirection,animationDuration,animationFillMode,animationIterationCount,animationName,animationPlayState,animationRange,animationRangeEnd,animationRangeStart,animationTimeline,animationTimingFunction,appearance,aspectRatio,backdropFilter,backfaceVisibility,background,backgroundAttachment,backgroundBlendMode,backgroundClip,backgroundColor,backgroundImage,backgroundOrigin,backgroundPosition,backgroundPositionX,backgroundPositionY,backgroundRepeat,backgroundSize,blockSize,border,borderBlock,borderBlockColor,borderBlockEnd,borderBlockEndColor,borderBlockEndStyle,borderBlockEndWidth,borderBlockStart,borderBlockStartColor,borderBlockStartStyle,borderBlockStartWidth,borderBlockStyle,borderBlockWidth,borderBottom,borderBottomColor,borderBottomLeftRadius,borderBottomRightRadius,borderBottomStyle,borderBottomWidth,borderCollapse,borderColor,borderEndEndRadius,borderEndStartRadius,borderImage,borderImageOutset,borderImageRepeat,borderImageSlice,borderImageSource,borderImageWidth,borderInline,borderInlineColor,borderInlineEnd,borderInlineEndColor,borderInlineEndStyle,borderInlineEndWidth,borderInlineStart,borderInlineStartColor,borderInlineStartStyle,borderInlineStartWidth,borderInlineStyle,borderInlineWidth,borderLeft,borderLeftColor,borderLeftStyle,borderLeftWidth,borderRadius,borderRight,borderRightColor,borderRightStyle,borderRightWidth,borderSpacing,borderStartEndRadius,borderStartStartRadius,borderStyle,borderTop,borderTopColor,borderTopLeftRadius,borderTopRightRadius,borderTopStyle,borderTopWidth,borderWidth,bottom,boxAlign,boxDecorationBreak,boxDirection,boxFlex,boxFlexGroup,boxLines,boxOrdinalGroup,boxOrient,boxPack,boxShadow,boxSizing,breakAfter,breakBefore,breakInside,captionSide,caret,caretColor,caretShape,clear,clip,clipPath,clipRule,color,colorInterpolationFilters,colorScheme,columnCount,columnFill,columnGap,columnRule,columnRuleColor,columnRuleStyle,columnRuleWidth,columnSpan,columnWidth,columns,contain,containIntrinsicBlockSize,containIntrinsicHeight,containIntrinsicInlineSize,containIntrinsicSize,containIntrinsicWidth,container,containerName,containerType,content,contentVisibility,cornerShape,counterIncrement,counterReset,counterSet,cursor,cx,cy,d,direction,display,dominantBaseline,emptyCells,fieldSizing,fill,fillOpacity,fillRule,filter,flex,flexBasis,flexDirection,flexFlow,flexGrow,flexShrink,flexWrap,float,floodColor,floodOpacity,font,fontFamily,fontFeatureSettings,fontKerning,fontLanguageOverride,fontOpticalSizing,fontPalette,fontSize,fontSizeAdjust,fontSmooth,fontStretch,fontStyle,fontSynthesis,fontSynthesisPosition,fontSynthesisSmallCaps,fontSynthesisStyle,fontSynthesisWeight,fontVariant,fontVariantAlternates,fontVariantCaps,fontVariantEastAsian,fontVariantEmoji,fontVariantLigatures,fontVariantNumeric,fontVariantPosition,fontVariationSettings,fontWeight,forcedColorAdjust,gap,grid,gridArea,gridAutoColumns,gridAutoFlow,gridAutoRows,gridColumn,gridColumnEnd,gridColumnGap,gridColumnStart,gridGap,gridRow,gridRowEnd,gridRowGap,gridRowStart,gridTemplate,gridTemplateAreas,gridTemplateColumns,gridTemplateRows,hangingPunctuation,height,hyphenateCharacter,hyphenateLimitChars,hyphens,imageOrientation,imageRendering,imageResolution,imeMode,initialLetter,initialLetterAlign,inlineSize,inset,insetBlock,insetBlockEnd,insetBlockStart,insetInline,insetInlineEnd,insetInlineStart,interpolateSize,isolation,justifyContent,justifyItems,justifySelf,justifyTracks,left,letterSpacing,lightingColor,lineBreak,lineClamp,lineHeight,lineHeightStep,listStyle,listStyleImage,listStylePosition,listStyleType,margin,marginBlock,marginBlockEnd,marginBlockStart,marginBottom,marginInline,marginInlineEnd,marginInlineStart,marginLeft,marginRight,marginTop,marginTrim,marker,markerEnd,markerMid,markerStart,mask,maskBorder,maskBorderMode,maskBorderOutset,maskBorderRepeat,maskBorderSlice,maskBorderSource,maskBorderWidth,maskClip,maskComposite,maskImage,maskMode,maskOrigin,maskPosition,maskRepeat,maskSize,maskType,masonryAutoFlow,mathDepth,mathShift,mathStyle,maxBlockSize,maxHeight,maxInlineSize,maxLines,maxWidth,minBlockSize,minHeight,minInlineSize,minWidth,mixBlendMode,objectFit,objectPosition,offset,offsetAnchor,offsetDistance,offsetPath,offsetPosition,offsetRotate,opacity,order,orphans,outline,outlineColor,outlineOffset,outlineStyle,outlineWidth,overflow,overflowAnchor,overflowBlock,overflowClipBox,overflowClipMargin,overflowInline,overflowWrap,overflowX,overflowY,overlay,overscrollBehavior,overscrollBehaviorBlock,overscrollBehaviorInline,overscrollBehaviorX,overscrollBehaviorY,padding,paddingBlock,paddingBlockEnd,paddingBlockStart,paddingBottom,paddingInline,paddingInlineEnd,paddingInlineStart,paddingLeft,paddingRight,paddingTop,page,pageBreakAfter,pageBreakBefore,pageBreakInside,paintOrder,perspective,perspectiveOrigin,placeContent,placeItems,placeSelf,pointerEvents,position,positionAnchor,positionArea,positionTry,positionTryFallbacks,positionTryOrder,positionVisibility,printColorAdjust,quotes,r,resize,right,rotate,rowGap,rubyAlign,rubyMerge,rubyPosition,rx,ry,scale,scrollBehavior,scrollMargin,scrollMarginBlock,scrollMarginBlockEnd,scrollMarginBlockStart,scrollMarginBottom,scrollMarginInline,scrollMarginInlineEnd,scrollMarginInlineStart,scrollMarginLeft,scrollMarginRight,scrollMarginTop,scrollPadding,scrollPaddingBlock,scrollPaddingBlockEnd,scrollPaddingBlockStart,scrollPaddingBottom,scrollPaddingInline,scrollPaddingInlineEnd,scrollPaddingInlineStart,scrollPaddingLeft,scrollPaddingRight,scrollPaddingTop,scrollSnapAlign,scrollSnapCoordinate,scrollSnapDestination,scrollSnapPointsX,scrollSnapPointsY,scrollSnapStop,scrollSnapType,scrollSnapTypeX,scrollSnapTypeY,scrollTimeline,scrollTimelineAxis,scrollTimelineName,scrollbarColor,scrollbarGutter,scrollbarWidth,shapeImageThreshold,shapeMargin,shapeOutside,shapeRendering,stopColor,stopOpacity,stroke,strokeDasharray,strokeDashoffset,strokeLinecap,strokeLinejoin,strokeMiterlimit,strokeOpacity,strokeWidth,tabSize,tableLayout,textAlign,textAlignLast,textAnchor,textBox,textBoxEdge,textBoxTrim,textCombineUpright,textDecoration,textDecorationColor,textDecorationLine,textDecorationSkip,textDecorationSkipInk,textDecorationStyle,textDecorationThickness,textEmphasis,textEmphasisColor,textEmphasisPosition,textEmphasisStyle,textIndent,textJustify,textOrientation,textOverflow,textRendering,textShadow,textSizeAdjust,textSpacingTrim,textTransform,textUnderlineOffset,textUnderlinePosition,textWrap,textWrapMode,textWrapStyle,timelineScope,top,touchAction,transform,transformBox,transformOrigin,transformStyle,transition,transitionBehavior,transitionDelay,transitionDuration,transitionProperty,transitionTimingFunction,translate,unicodeBidi,userSelect,vectorEffect,verticalAlign,viewTimeline,viewTimelineAxis,viewTimelineInset,viewTimelineName,viewTransitionName,visibility,whiteSpace,whiteSpaceCollapse,widows,width,willChange,wordBreak,wordSpacing,wordWrap,writingMode,x,y,zIndex,zoom,alignmentBaseline,baselineShift,colorInterpolation,colorRendering,glyphOrientationVertical\".split(\",\").concat(userGenerated);\nconst properties = new Map(allCssProperties.map((prop) => [prop, true]));\nfunction memo(fn) {\n const cache = Object.create(null);\n return (arg) => {\n if (cache[arg] === void 0) cache[arg] = fn(arg);\n return cache[arg];\n };\n}\nconst cssPropertySelectorRegex = /&|@/;\nconst isCssProperty = /* @__PURE__ */ memo((prop) => {\n return properties.has(prop) || prop.startsWith(\"--\") || cssPropertySelectorRegex.test(prop);\n});\n//#endregion\nexport { allCssProperties, isCssProperty };\n";
426
445
  //#endregion
427
446
  //#region src/artifacts/js/is-valid-prop.ts
428
- const cssPropRegex = /var cssPropertiesStr = ".*?";/;
429
- const memoFnDeclarationRegex = /function memo(.+?)\nvar cssPropertySelectorRegex/s;
447
+ const cssPropListRegex = /const userGenerated = ".*?"\.split\(","\);\s*const allCssProperties = "(.*?)"\.split\(","\)\.concat\(userGenerated\);/;
448
+ const memoFnDeclarationRegex = /function memo(?:.+?)\n((?:var|const|let) cssPropertySelectorRegex)/s;
430
449
  function generateIsValidProp(ctx) {
431
450
  if (ctx.isTemplateLiteralSyntax) return;
432
451
  let content = content$8;
433
- content = content.replace("var userGeneratedStr = \"\";", `var userGeneratedStr = "${match(ctx.jsx.styleProps).with("all", () => Array.from(ctx.properties).join(",")).with("minimal", () => "css").with("none", () => "css").exhaustive()}"`);
434
- content = content.replace(memoFnDeclarationRegex, "var cssPropertySelectorRegex");
435
- if (ctx.jsx.styleProps === "minimal" || ctx.jsx.styleProps === "none") {
436
- content = content.replace("/* @__PURE__ */ memo(", "/* @__PURE__ */ (");
437
- content = content.replace(cssPropRegex, "var cssPropertiesStr = \"\";");
438
- } else content = ctx.file.import("memo", "../helpers") + "\n" + content;
452
+ const propertyList = content.match(cssPropListRegex);
453
+ if (!propertyList) throw new BambooError("NOT_FOUND", "Could not find the property list in the prebuilt is-valid-prop module. Its bundled shape has changed.");
454
+ const userProperties = match(ctx.jsx.styleProps).with("all", () => Array.from(ctx.properties)).with("minimal", () => ["css"]).with("none", () => ["css"]).exhaustive();
455
+ const browserProperties = ctx.jsx.styleProps === "all" ? propertyList[1].split(",") : [];
456
+ content = content.replace(cssPropListRegex, () => `const allCssProperties = "${uniq(browserProperties, userProperties).join(",")}".split(",");`);
457
+ content = content.replace(memoFnDeclarationRegex, "$1");
458
+ if (ctx.jsx.styleProps === "minimal" || ctx.jsx.styleProps === "none") content = content.replace("/* @__PURE__ */ memo(", "/* @__PURE__ */ (");
459
+ else content = ctx.file.import("memo", "../helpers") + "\n" + content;
439
460
  content = ctx.file.import("splitProps", "../helpers") + "\n" + content;
440
461
  content += `export const splitCssProps = (props) => splitProps(props, isCssProperty)`;
441
462
  return {
@@ -487,6 +508,56 @@ function generatedJsxHelpers(ctx) {
487
508
  `) };
488
509
  }
489
510
  //#endregion
511
+ //#region src/artifacts/js/package-json.ts
512
+ /**
513
+ * The generated output is a plain directory, not an installed package, so bundlers
514
+ * have no `sideEffects` hint for it and must assume every module mutates something.
515
+ * That keeps every module a barrel pulls in — importing a single component from
516
+ * `styled-system/jsx` retains all patterns.
517
+ *
518
+ * The CSS globs are required: `sideEffects: false` alone lets a bundler drop a bare
519
+ * `import 'styled-system/styles.css'`. Both shapes are listed because the stylesheet
520
+ * is emitted at the root (`styles.css`) and, under `splitting`, in `styles/`.
521
+ *
522
+ * `type: module` restates what the directory already is. Adding a package.json makes
523
+ * this directory its own package boundary, so `.js` output would otherwise stop
524
+ * inheriting the consumer's `type` and be re-read as CommonJS; the emitted code is
525
+ * always ESM. It is a no-op for the default `.mjs` extension.
526
+ *
527
+ * `private` is there because the same package boundary makes a workspace glob able to
528
+ * match this directory. It is never published.
529
+ *
530
+ * `name` has to be present for the same reason. This file used to be emitted without
531
+ * one, to keep two outputs in a single workspace from colliding — but a nameless
532
+ * package.json is not a package a workspace scanner skips, it is one it refuses:
533
+ * pnpm, npm and changesets all abort with `missing the "name" field` and no hint as
534
+ * to which directory produced it.
535
+ *
536
+ * The name is derived from `outdir` because that is the only input that is both
537
+ * deterministic and portable — `cwd` is absolute, so putting it here would make the
538
+ * generated output differ per machine. Two projects in one workspace that both keep
539
+ * the default `outdir` therefore still collide, but on a duplicate-name error that
540
+ * names both paths and is resolved by setting `outdir`, rather than on a missing
541
+ * field that points nowhere.
542
+ */
543
+ function generatePackageJson(ctx) {
544
+ return { json: JSON.stringify({
545
+ name: toPackageName(ctx.config.outdir),
546
+ type: "module",
547
+ private: true,
548
+ sideEffects: ["*.css", "**/*.css"]
549
+ }, null, 2) + "\n" };
550
+ }
551
+ /**
552
+ * `outdir` is a path, npm names are not: it may be nested (`src/styled-system`), and
553
+ * npm rejects uppercase, a leading dot or underscore, and anything outside its
554
+ * url-safe set. Path segments are joined rather than dropped so that nested outputs
555
+ * stay distinct from one another.
556
+ */
557
+ function toPackageName(outdir) {
558
+ return outdir.split(/[\\/]/).filter(Boolean).join("-").toLowerCase().replace(/[^a-z0-9\-._]/g, "-").replace(/^[._]+/, "") || "styled-system";
559
+ }
560
+ //#endregion
490
561
  //#region src/artifacts/js/pattern.ts
491
562
  function generatePattern(ctx, filters) {
492
563
  if (ctx.patterns.isEmpty()) return;
@@ -497,7 +568,7 @@ function generatePattern(ctx, filters) {
497
568
  transform,
498
569
  defaultValues
499
570
  })) ?? "";
500
- const helperImports = ["getPatternStyles, patternFns"];
571
+ const helperImports = ["getPatternStyles, patternFns, memo"];
501
572
  if (patternConfigFn.includes("__spreadValues")) helperImports.push("__spreadValues");
502
573
  if (patternConfigFn.includes("__objRest")) helperImports.push("__objRest");
503
574
  return {
@@ -550,7 +621,7 @@ function generatePattern(ctx, filters) {
550
621
  return ${baseName}Config.transform(_styles, patternFns)
551
622
  }
552
623
 
553
- export const ${baseName} = (styles) => css(${styleFnName}(styles))
624
+ export const ${baseName} = /* @__PURE__ */ memo((styles) => css(${styleFnName}(styles)))
554
625
  ${baseName}.raw = ${styleFnName}
555
626
  `
556
627
  };
@@ -950,7 +1021,6 @@ function generatePreactJsxFactory(ctx) {
950
1021
  return { js: outdent`
951
1022
  import { h } from 'preact'
952
1023
  import { forwardRef } from 'preact/compat'
953
- import { useMemo } from 'preact/hooks'
954
1024
  ${ctx.file.import("defaultShouldForwardProp, composeShouldForwardProps, composeCvaFn, getDisplayName", "./factory-helper")}
955
1025
  ${ctx.file.import("isCssProperty", "./is-valid-prop")}
956
1026
  ${ctx.file.import("css, cx, cva", "../css/index")}
@@ -978,11 +1048,13 @@ function generatePreactJsxFactory(ctx) {
978
1048
  const { as: Element = __base__, unstyled, children, ...restProps } = props
979
1049
 
980
1050
 
981
- const combinedProps = useMemo(() => Object.assign({}, defaultProps, restProps), [restProps])
1051
+ // Not memoized, deliberately. \`restProps\` is rest destructuring, so it is a fresh
1052
+ // object on every render and a dependency on it can never match — a memo here is a
1053
+ // guaranteed miss that still costs a hook slot, a deps array and a retained cell.
1054
+ const combinedProps = Object.assign({}, defaultProps, restProps)
982
1055
 
983
- const [htmlProps, forwardedProps, variantProps, styleProps, elementProps] = useMemo(() => {
984
- return splitProps(combinedProps, normalizeHTMLProps.keys, __shouldForwardProps__, __cvaFn__.variantKeys, isCssProperty)
985
- }, [combinedProps])
1056
+ const [htmlProps, forwardedProps, variantProps, styleProps, elementProps] =
1057
+ splitProps(combinedProps, normalizeHTMLProps.keys, __shouldForwardProps__, __cvaFn__.variantKeys, isCssProperty)
986
1058
 
987
1059
  function recipeClass() {
988
1060
  const { css: cssStyles, ...propStyles } = styleProps
@@ -1810,7 +1882,7 @@ export type ${typeName}<T extends ElementType> = ComponentProps<T>
1810
1882
  function generateReactJsxFactory(ctx) {
1811
1883
  const { factoryName, componentName } = ctx.jsx;
1812
1884
  return { js: outdent`
1813
- import { createElement, forwardRef, useMemo } from 'react'
1885
+ import { createElement, forwardRef } from 'react'
1814
1886
  ${ctx.file.import("css, cx, cva", "../css/index")}
1815
1887
  ${ctx.file.import("defaultShouldForwardProp, composeShouldForwardProps, composeCvaFn, getDisplayName", "./factory-helper")}
1816
1888
  ${ctx.file.import("splitProps, normalizeHTMLProps", "../helpers")}
@@ -1837,11 +1909,13 @@ function generateReactJsxFactory(ctx) {
1837
1909
  const ${componentName} = /* @__PURE__ */ forwardRef(function ${componentName}(props, ref) {
1838
1910
  const { as: Element = __base__, unstyled, children, ...restProps } = props
1839
1911
 
1840
- const combinedProps = useMemo(() => Object.assign({}, defaultProps, restProps), [restProps])
1912
+ // Not memoized, deliberately. \`restProps\` is rest destructuring, so it is a fresh
1913
+ // object on every render and a dependency on it can never match — a memo here is a
1914
+ // guaranteed miss that still costs a hook slot, a deps array and a retained cell.
1915
+ const combinedProps = Object.assign({}, defaultProps, restProps)
1841
1916
 
1842
- const [htmlProps, forwardedProps, variantProps, styleProps, elementProps] = useMemo(() => {
1843
- return splitProps(combinedProps, normalizeHTMLProps.keys, __shouldForwardProps__, __cvaFn__.variantKeys, isCssProperty)
1844
- }, [combinedProps])
1917
+ const [htmlProps, forwardedProps, variantProps, styleProps, elementProps] =
1918
+ splitProps(combinedProps, normalizeHTMLProps.keys, __shouldForwardProps__, __cvaFn__.variantKeys, isCssProperty)
1845
1919
 
1846
1920
  function recipeClass() {
1847
1921
  const { css: cssStyles, ...propStyles } = styleProps
@@ -4526,8 +4600,8 @@ function generatePropTypes(ctx) {
4526
4600
  * fontSize: '[123px]', // ⚠️ will not throw even if you haven't defined 123px as a token
4527
4601
  * })
4528
4602
  *
4529
- * @see https://bamboo-css.com/docs/concepts/writing-styles#stricttokens
4530
- * @see https://bamboo-css.com/docs/concepts/writing-styles#strictpropertyvalues
4603
+ * @see https://bamboocss.com/docs/concepts/writing-styles#stricttokens
4604
+ * @see https://bamboocss.com/docs/concepts/writing-styles#strictpropertyvalues
4531
4605
  */
4532
4606
  export type WithEscapeHatch<T> = T | \`[\${string}]\` | WithColorOpacityModifier<T> | WithImportant<T>
4533
4607
 
@@ -4539,7 +4613,7 @@ function generatePropTypes(ctx) {
4539
4613
  * display: 'abc', // ❌ will throw
4540
4614
  * })
4541
4615
  *
4542
- * @see https://bamboo-css.com/docs/concepts/writing-styles#strictpropertyvalues
4616
+ * @see https://bamboocss.com/docs/concepts/writing-styles#strictpropertyvalues
4543
4617
  */
4544
4618
  export type OnlyKnown<Key, Value> = Value extends boolean
4545
4619
  ? Value
@@ -4955,6 +5029,15 @@ function setupHelpers(ctx) {
4955
5029
  }]
4956
5030
  };
4957
5031
  }
5032
+ function setupPackageJson(ctx) {
5033
+ return {
5034
+ id: "package.json",
5035
+ files: [{
5036
+ file: "package.json",
5037
+ code: generatePackageJson(ctx).json
5038
+ }]
5039
+ };
5040
+ }
4958
5041
  function setupDesignTokens(ctx) {
4959
5042
  const code = generateTokenJs(ctx);
4960
5043
  return {
@@ -5410,6 +5493,7 @@ const filterArtifactsFiles = (artifacts, filters) => {
5410
5493
  });
5411
5494
  };
5412
5495
  const entries = [
5496
+ ["package.json", setupPackageJson],
5413
5497
  ["helpers", setupHelpers],
5414
5498
  ["design-tokens", setupDesignTokens],
5415
5499
  ["types-jsx", setupJsxTypes],
@@ -6053,6 +6137,136 @@ var Generator = class extends Context {
6053
6137
  const decoder = this.decoder.collect(this.encoder);
6054
6138
  sheet.processDecoder(decoder);
6055
6139
  };
6140
+ /**
6141
+ * Drop token css variables nothing can reach. Call this only once the sheet holds the
6142
+ * whole stylesheet — a baseline-only sheet has no utilities to reference anything, so
6143
+ * every token would look unused.
6144
+ *
6145
+ * `keep` carries references this cannot see for itself; see `collectTokenReferences`.
6146
+ */
6147
+ pruneTokens = (sheet, keep) => {
6148
+ if (!this.config.pruneUnusedTokens) return;
6149
+ const layers = sheet.layers;
6150
+ const result = pruneTokenVars({
6151
+ scan: [
6152
+ layers.reset,
6153
+ layers.base,
6154
+ layers.tokens,
6155
+ layers.recipes,
6156
+ layers.recipes_base,
6157
+ layers.recipes_slots,
6158
+ layers.recipes_slots_base,
6159
+ layers.utilities,
6160
+ layers.compositions
6161
+ ],
6162
+ target: layers.tokens,
6163
+ tokenVars: this.getTokenVarNames(),
6164
+ keep: new Set([
6165
+ ...this.getAlwaysKeptTokenVars(),
6166
+ ...this.getThemeTokenVars(),
6167
+ ...keep ?? []
6168
+ ])
6169
+ });
6170
+ logger.debug("prune:tokens", `Removed ${result.removed} unused token css variable(s)`);
6171
+ return result;
6172
+ };
6173
+ /**
6174
+ * Drop `@keyframes` nothing can reach. Same completeness requirement as
6175
+ * `pruneTokens`: the sheet has to hold the whole stylesheet, or every keyframe looks
6176
+ * unused for want of a utility to reference it.
6177
+ *
6178
+ * `keep` carries names this cannot see for itself; see `collectKeyframeReferences`.
6179
+ */
6180
+ pruneKeyframes = (sheet, keep) => {
6181
+ if (!this.config.pruneUnusedKeyframes) return;
6182
+ const layers = sheet.layers;
6183
+ const keyframeNames = new Set(Object.keys(this.config.theme?.keyframes ?? {}));
6184
+ const result = pruneKeyframes({
6185
+ scan: [
6186
+ layers.reset,
6187
+ layers.base,
6188
+ layers.tokens,
6189
+ layers.recipes,
6190
+ layers.recipes_base,
6191
+ layers.recipes_slots,
6192
+ layers.recipes_slots_base,
6193
+ layers.utilities,
6194
+ layers.compositions
6195
+ ],
6196
+ target: layers.tokens,
6197
+ keyframeNames,
6198
+ keep: new Set([...this.getThemeKeyframeNames(keyframeNames), ...keep ?? []])
6199
+ });
6200
+ logger.debug("prune:keyframes", `Removed ${result.removed} unused keyframe(s)`);
6201
+ return result;
6202
+ };
6203
+ /**
6204
+ * Keyframes the themes name.
6205
+ *
6206
+ * A theme is emitted as its own artifact and injected at runtime, so its css is not in
6207
+ * the sheet being pruned. A theme that points an animation token at a different
6208
+ * keyframe than the base does — `--animations-enter: fade-in` in the base and
6209
+ * `slide-up` under `dark` — would otherwise have that keyframe removed, because
6210
+ * nothing in the pruned sheet ever names it.
6211
+ */
6212
+ getThemeKeyframeNames = (keyframeNames) => {
6213
+ const names = /* @__PURE__ */ new Set();
6214
+ const themes = this.config.themes;
6215
+ if (!themes || !keyframeNames.size) return names;
6216
+ for (const themeName of Object.keys(themes)) for (const token of getThemeCss(this, themeName).split(/[^\w-]+/)) if (keyframeNames.has(token)) names.add(token);
6217
+ return names;
6218
+ };
6219
+ /**
6220
+ * Every custom property the token system declares. Used as the allow-list of what may
6221
+ * be removed, so custom properties from `globalCss` are never touched.
6222
+ */
6223
+ getTokenVarNames = () => {
6224
+ const names = /* @__PURE__ */ new Set();
6225
+ for (const values of this.tokens.view.vars.values()) for (const name of values.keys()) names.add(name);
6226
+ return names;
6227
+ };
6228
+ /**
6229
+ * Everything the themes refer to.
6230
+ *
6231
+ * A theme is emitted as its own artifact and injected at runtime, so its css is not in
6232
+ * the sheet being pruned and nothing there points at what it needs. A theme that maps a
6233
+ * token onto a base colour would otherwise be left referring to a declaration that has
6234
+ * been removed.
6235
+ */
6236
+ getThemeTokenVars = () => {
6237
+ const names = /* @__PURE__ */ new Set();
6238
+ const themes = this.config.themes;
6239
+ if (!themes) return names;
6240
+ for (const themeName of Object.keys(themes)) for (const name of cssVarRefs(getThemeCss(this, themeName))) names.add(name);
6241
+ return names;
6242
+ };
6243
+ /**
6244
+ * Tokens whose javascript value is a `var()` reference rather than a literal.
6245
+ * `token('colors.text')` hands those to the caller as a reference, so the declaration
6246
+ * has to survive whether or not the generated css mentions it. Ordinary tokens resolve
6247
+ * to a literal in javascript and need no such exemption.
6248
+ *
6249
+ * The two cases mirror `generateTokenJs`, which is what decides the value javascript
6250
+ * actually receives:
6251
+ *
6252
+ * - A virtual token, or one carrying a condition, is handed its own `varRef`.
6253
+ * - A negative token is handed `calc(var(--x) * -1)`, so it is a reference too — but to
6254
+ * the *positive* token's declaration. Its own var is never declared, so the name has
6255
+ * to come out of the value.
6256
+ */
6257
+ getAlwaysKeptTokenVars = () => {
6258
+ const names = /* @__PURE__ */ new Set();
6259
+ this.tokens.allTokens.forEach((token) => {
6260
+ const { isVirtual, isNegative, condition, var: varName } = token.extensions;
6261
+ if (isVirtual || condition !== "base") {
6262
+ if (varName) names.add(varName.startsWith("--") ? varName : `--${varName}`);
6263
+ return;
6264
+ }
6265
+ if (!isNegative) return;
6266
+ for (const name of cssVarRefs(token.value)) names.add(name);
6267
+ });
6268
+ return names;
6269
+ };
6056
6270
  getParserCss = (decoder) => {
6057
6271
  return generateParserCss(this, decoder);
6058
6272
  };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@bamboocss/generator",
3
- "version": "1.12.3",
3
+ "version": "1.13.1",
4
4
  "description": "The css generator for css bamboo",
5
- "homepage": "https://bamboo-css.com",
5
+ "homepage": "https://bamboocss.com",
6
6
  "license": "MIT",
7
- "author": "Segun Adebayo <joseshegs@gmail.com>",
7
+ "author": "Gajus Kuizinas <gajus@gajus.com>",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/bamboocss/bamboo.git",
@@ -38,12 +38,12 @@
38
38
  "pluralize": "8.0.0",
39
39
  "postcss": "8.5.14",
40
40
  "ts-pattern": "5.9.0",
41
- "@bamboocss/core": "1.12.3",
42
- "@bamboocss/is-valid-prop": "^1.12.3",
43
- "@bamboocss/logger": "1.12.3",
44
- "@bamboocss/shared": "1.12.3",
45
- "@bamboocss/types": "1.12.3",
46
- "@bamboocss/token-dictionary": "1.12.3"
41
+ "@bamboocss/is-valid-prop": "^1.13.1",
42
+ "@bamboocss/core": "1.13.1",
43
+ "@bamboocss/logger": "1.13.1",
44
+ "@bamboocss/shared": "1.13.1",
45
+ "@bamboocss/token-dictionary": "1.13.1",
46
+ "@bamboocss/types": "1.13.1"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/pluralize": "0.0.33"