@animus-ui/system 0.1.22 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/class-resolver.d.ts +2 -2
  2. package/dist/class-resolver.js +1 -1
  3. package/dist/{component-stJhG_A-.d.ts → component-D9Fie8Ci.d.ts} +58 -31
  4. package/dist/component-D9Fie8Ci.d.ts.map +1 -0
  5. package/dist/{compose-BQi7TDtL.d.ts → compose-A-kGRKdR.d.ts} +2 -2
  6. package/dist/{compose-BQi7TDtL.d.ts.map → compose-A-kGRKdR.d.ts.map} +1 -1
  7. package/dist/compose.d.ts +1 -1
  8. package/dist/composeWithContext.d.ts +1 -1
  9. package/dist/composeWithContext.d.ts.map +1 -1
  10. package/dist/composeWithContext.js +4 -4
  11. package/dist/createClassResolver-C-UOgE_d.d.ts +115 -0
  12. package/dist/createClassResolver-C-UOgE_d.d.ts.map +1 -0
  13. package/dist/{createClassResolver-DtHjTYa7.js → createClassResolver-DdrmIC5J.js} +321 -60
  14. package/dist/{createComposedFamily-BbA_d0QH.js → createComposedFamily-BFoaDqk7.js} +25 -8
  15. package/dist/{createComposedFamily-BkrvSsfl.d.ts → createComposedFamily-BJQ5Cn76.d.ts} +3 -3
  16. package/dist/createComposedFamily-BJQ5Cn76.d.ts.map +1 -0
  17. package/dist/groups/index.d.ts +1 -1
  18. package/dist/{index-C1AAiaiJ.d.ts → index-CQ7BFlKp.d.ts} +23 -23
  19. package/dist/index-CQ7BFlKp.d.ts.map +1 -0
  20. package/dist/index.d.ts +6 -6
  21. package/dist/index.js +26 -8
  22. package/dist/runtime-entry.d.ts +3 -3
  23. package/dist/runtime-entry.js +2 -2
  24. package/package.json +2 -2
  25. package/dist/component-stJhG_A-.d.ts.map +0 -1
  26. package/dist/createClassResolver-Do1nmvkf.d.ts +0 -71
  27. package/dist/createClassResolver-Do1nmvkf.d.ts.map +0 -1
  28. package/dist/createComposedFamily-BkrvSsfl.d.ts.map +0 -1
  29. package/dist/index-C1AAiaiJ.d.ts.map +0 -1
@@ -1,8 +1,12 @@
1
- import { componentValues, decodedIdentifier, importantPriority, isUnitlessProperty, tokenize, variableReads } from "@animus-ui/properties";
1
+ import { isUnitlessProperty } from "@animus-ui/properties";
2
2
  //#region src/runtime/is-dev.ts
3
3
  /**
4
4
  * The define token is tested as the initializer's own conditional, never from
5
- * inside the fallback: only there can a minifier drop the dev-gated code.
5
+ * inside the fallback: only there can a minifier fold it. Dev-only work tests
6
+ * the token in place as well, `typeof __ANIMUS_DEV__ === 'boolean' ?
7
+ * __ANIMUS_DEV__ : IS_DEV`: esbuild inlines no module-level constant into a
8
+ * chunk that starts with an import, so a gate on `IS_DEV` alone survives a
9
+ * production bundle.
6
10
  */
7
11
  const IS_DEV = typeof __ANIMUS_DEV__ === "boolean" ? __ANIMUS_DEV__ : (() => {
8
12
  try {
@@ -13,6 +17,117 @@ const IS_DEV = typeof __ANIMUS_DEV__ === "boolean" ? __ANIMUS_DEV__ : (() => {
13
17
  }
14
18
  })();
15
19
  //#endregion
20
+ //#region src/runtime/value-scan.ts
21
+ const ESCAPE = String.raw`\\(?:[\da-fA-F]{1,6}(?:\r\n|[ \t\n\r\f])?|[^\n\r\f\da-fA-F])`;
22
+ const NAME_CHAR = String.raw`(?:[\w\u0080-￿-]|${ESCAPE})`;
23
+ /** One unit per match: a comment (1), whitespace (2), an opaque string or
24
+ * `<!--` (3), a name (4), a `!` (5), or any other character. In a string, a
25
+ * backslash escapes a CRLF as one newline. */
26
+ const UNIT = new RegExp(String.raw`(\/\*[\s\S]*?(?:\*\/|$))|([ \t\n\r\f]+)|("(?:\\(?:\r\n|[\s\S])|[^"\\\n\r\f])*"?|'(?:\\(?:\r\n|[\s\S])|[^'\\\n\r\f])*'?|<!--)|((?:-?(?:[a-zA-Z_\u0080-￿]|${ESCAPE})|--)${NAME_CHAR}*)|(!)|[\s\S]`, "gy");
27
+ /** An unquoted `url(` body, through its `)`. */
28
+ const URL_BODY = /(?:\\[\s\S]|[^)\\])*\)?/y;
29
+ const decode = (name) => name.replace(/\\([\da-fA-F]{1,6})(?:\r\n|[ \t\n\r\f])?|\\([\s\S])/g, (_, hex, char) => {
30
+ if (hex === void 0) return char ?? "";
31
+ const code = Number.parseInt(hex, 16);
32
+ return code > 0 && code <= 1114111 && (code < 55296 || code > 57343) ? String.fromCodePoint(code) : "�";
33
+ });
34
+ function units(css) {
35
+ const out = [];
36
+ UNIT.lastIndex = 0;
37
+ for (let match; (match = UNIT.exec(css)) !== null;) {
38
+ const start = match.index;
39
+ let end = UNIT.lastIndex;
40
+ if (match[1] !== void 0) continue;
41
+ if (match[2] !== void 0) out.push([
42
+ 0,
43
+ start,
44
+ end
45
+ ]);
46
+ else if (match[5] !== void 0) out.push([
47
+ 1,
48
+ start,
49
+ end
50
+ ]);
51
+ else if (match[4] !== void 0) {
52
+ const name = decode(match[4]);
53
+ const previous = out.at(-1);
54
+ if (!(previous !== void 0 && previous[0] === 3 && previous[2] === start && /[\d#@]/.test(css[start - 1])) && css[end] === "(") {
55
+ let body = end + 1;
56
+ while (/[ \t\n\r\f]/.test(css[body] ?? "")) body += 1;
57
+ if (name.toLowerCase() === "url" && css[body] !== "\"" && css[body] !== "'") {
58
+ URL_BODY.lastIndex = end + 1;
59
+ URL_BODY.exec(css);
60
+ end = UNIT.lastIndex = URL_BODY.lastIndex;
61
+ out.push([
62
+ 3,
63
+ start,
64
+ end
65
+ ]);
66
+ } else {
67
+ UNIT.lastIndex = end + 1;
68
+ out.push([
69
+ 4,
70
+ start,
71
+ end + 1,
72
+ name
73
+ ]);
74
+ }
75
+ continue;
76
+ }
77
+ out.push([
78
+ 2,
79
+ start,
80
+ end,
81
+ name
82
+ ]);
83
+ } else out.push([
84
+ 3,
85
+ start,
86
+ end
87
+ ]);
88
+ }
89
+ return out;
90
+ }
91
+ /** A value's trailing priority, as the tokenizer's `importantPriority` reads
92
+ * it: exactly one `!`; `value!` with nothing between is the shorthand, and
93
+ * `value !important`, the keyword in any case or escaped, the full form. */
94
+ function trailingPriority(css) {
95
+ const trimmed = css.trimEnd();
96
+ const endsImportant = trimmed.length >= 9 && /^important$/i.test(trimmed.slice(-9));
97
+ if (!css.endsWith("!") && !endsImportant) return void 0;
98
+ const all = units(css);
99
+ const bangs = all.filter((unit) => unit[0] === 1);
100
+ if (bangs.length !== 1) return void 0;
101
+ const at = all.indexOf(bangs[0]);
102
+ if (at === all.length - 1) {
103
+ const previous = all[at - 1];
104
+ return previous !== void 0 && previous[0] !== 0 && previous[2] === bangs[0][1] ? {
105
+ spelling: "shorthand",
106
+ end: previous[2]
107
+ } : void 0;
108
+ }
109
+ const after = all.slice(at + 1).filter((unit) => unit[0] !== 0);
110
+ if (after.length !== 1 || after[0][0] !== 2 || !/^important$/i.test(after[0][3] ?? "")) return;
111
+ const value = all.slice(0, at).findLast((unit) => unit[0] !== 0);
112
+ return value && {
113
+ spelling: "important",
114
+ end: value[2]
115
+ };
116
+ }
117
+ /** Whether `css` reads `variable` through a `var()` at any depth, its
118
+ * fallbacks included: a `var` function, in any case or escaped, whose first
119
+ * argument is the variable's decoded name. */
120
+ function readsVariable(css, variable) {
121
+ if (!css.includes("(")) return false;
122
+ const target = decode(variable);
123
+ const all = units(css);
124
+ return all.some((unit, index) => {
125
+ if (unit[0] !== 4 || unit[3]?.toLowerCase() !== "var") return false;
126
+ const first = all.slice(index + 1).find((next) => next[0] !== 0);
127
+ return first !== void 0 && first[0] === 2 && first[3] === target;
128
+ });
129
+ }
130
+ //#endregion
16
131
  //#region src/runtime/witness.ts
17
132
  const WITNESS_CAP = 5e3;
18
133
  function recordWitness(component, prop, value, outcome) {
@@ -117,15 +232,6 @@ function isAdmittedWithoutToken(value, dc) {
117
232
  if (typeof value !== "string") return true;
118
233
  return value === "0" || dc.keywords?.includes(value) === true || value.includes("{") || CONTAINER_UNIT.test(value) || dc.property !== void 0 && SIZE_PROPERTY.test(dc.property) && (SIZE_LENGTH.test(value) || value.startsWith("calc("));
119
234
  }
120
- /** A CSS-wide keyword is a cascade instruction, not a value: no transform
121
- * sees it, and through an inline variable it would act on the variable. */
122
- const CSS_WIDE_KEYWORDS = /* @__PURE__ */ new Set([
123
- "initial",
124
- "inherit",
125
- "unset",
126
- "revert",
127
- "revert-layer"
128
- ]);
129
235
  function resolveEntry(value, dc) {
130
236
  const key = String(value);
131
237
  let scaleResolved = dc.scaleValues?.[key];
@@ -137,8 +243,7 @@ function resolveEntry(value, dc) {
137
243
  if (scaleResolved == null && dc.strict && dc.scaleValues && !isAdmittedWithoutToken(value, dc)) return { entry: value };
138
244
  const input = scaleResolved ?? value;
139
245
  let transformed = input;
140
- const keyword = scaleResolved == null && CSS_WIDE_KEYWORDS.has(value);
141
- if (dc.transform && !keyword) {
246
+ if (dc.transform) {
142
247
  try {
143
248
  transformed = dc.transform(input);
144
249
  } catch (cause) {
@@ -161,6 +266,29 @@ function warnDroppedValue(baseClassName, propName, serializedValue, customOwned)
161
266
  }
162
267
  }
163
268
  /**
269
+ * A runtime value that reaches a slot condition a production build removes:
270
+ * the build's usage analysis saw no runtime value for the prop there, so the
271
+ * value arrived in a way it does not follow, and in production it has
272
+ * neither a class nor a slot. An entry a literal class serves, as
273
+ * `applyDynamicProp` serves an `!important` literal, takes no slot.
274
+ */
275
+ function warnPrunedSlot(baseClassName, propName, serializedValue, propValue, dc, propClasses, typed) {
276
+ if (!IS_DEV || !dc.productionConditions) return;
277
+ const production = dc.productionConditions;
278
+ const [responsive, entries] = responsiveEntries(propValue);
279
+ const servedByClass = (bp, value) => {
280
+ if (dc.kind === "declarations" || !responsive || typeof value !== "string") return false;
281
+ const priority = trailingPriority(value);
282
+ return !!priority && (priority.spelling === "important" || !isCustomOnly(slotProperties(dc))) && entryClass(propClasses, typed, bp, value) !== void 0;
283
+ };
284
+ const pruned = entries.filter(([bp, value]) => !production.includes(bp) && !servedByClass(bp, value)).map(([bp]) => bp);
285
+ if (pruned.length === 0) return;
286
+ const dedupeKey = `${baseClassName}|${propName}|pruned`;
287
+ if (warnedDrops.has(dedupeKey)) return;
288
+ warnedDrops.add(dedupeKey);
289
+ console.warn(`[animus:drop] ${baseClassName}: value ${serializedValue} on prop '${propName}' uses its runtime slot at ${pruned.join(", ")}, which a production build removes — in production it will not render there. The build found no runtime value for this prop, so this one arrives through code it does not follow, such as a library that clones the element.`);
290
+ }
291
+ /**
164
292
  * A value taken from a variant default emits `--{prop}-default`, not the
165
293
  * value, so the compose override rule misses and the parent's value wins.
166
294
  * An explicit `undefined` takes the default as an omitted prop does.
@@ -172,7 +300,7 @@ function applyVariantClasses(classes, baseClassName, props, config) {
172
300
  if (value != null) {
173
301
  const isDefault = props[prop] == null && vc.default != null;
174
302
  classes.push(`${baseClassName}--${prop}-${isDefault ? "default" : value}`);
175
- recordWitness(baseClassName, prop, value, "static");
303
+ if (typeof __ANIMUS_DEV__ === "boolean" ? __ANIMUS_DEV__ : IS_DEV) recordWitness(baseClassName, prop, value, "static");
176
304
  }
177
305
  }
178
306
  }
@@ -195,7 +323,7 @@ function applyStateClasses(classes, baseClassName, props, config, activeStates)
195
323
  for (const state of config.states) if (props[state]) {
196
324
  classes.push(`${baseClassName}--${state}`);
197
325
  activeStates.push(state);
198
- recordWitness(baseClassName, state, "true", "static");
326
+ if (typeof __ANIMUS_DEV__ === "boolean" ? __ANIMUS_DEV__ : IS_DEV) recordWitness(baseClassName, state, "true", "static");
199
327
  }
200
328
  }
201
329
  const warnedInvalidResults = /* @__PURE__ */ new Set();
@@ -217,6 +345,23 @@ function warnIgnoredImportant(baseClassName, propName, value) {
217
345
  console.warn(`[animus:important] ${baseClassName}: !important is ignored on the runtime-generated value ${JSON.stringify(value)} of prop '${propName}' — a runtime value reaches CSS through a variable, which cannot carry it; declare the value literally to keep !important`);
218
346
  }
219
347
  }
348
+ const warnedUnitless = /* @__PURE__ */ new Set();
349
+ /**
350
+ * A number reaching a slot that only custom properties read, with no
351
+ * transform, stays unitless, as at build time; reported once per prop. Zero
352
+ * is a length without a unit, and a scale key is not written as is.
353
+ */
354
+ function warnUnitlessCustomProperty(baseClassName, propName, propValue, dc) {
355
+ if (!IS_DEV) return;
356
+ const properties = slotProperties(dc);
357
+ if (dc.transform || dc.transformId || dc.transformName || !isCustomOnly(properties)) return;
358
+ const number = responsiveEntries(propValue)[1].map(([, value]) => value).find((value) => typeof value === "number" && Number.isFinite(value) && value !== 0 && dc.scaleValues?.[String(value)] == null);
359
+ if (number === void 0) return;
360
+ const dedupeKey = `${baseClassName}|${propName}`;
361
+ if (warnedUnitless.has(dedupeKey)) return;
362
+ warnedUnitless.add(dedupeKey);
363
+ console.warn(`[animus:unit] ${baseClassName}: prop '${propName}' writes the number ${String(number)} to ${properties.join(", ")} without a unit — a custom property has no unit context, so the number stays unitless; give the value a unit, or bind a transform that adds one`);
364
+ }
220
365
  const warnedThrows = /* @__PURE__ */ new Set();
221
366
  const warnedStrictMisses = /* @__PURE__ */ new Set();
222
367
  /** Keyed by value, like a throw: each rejected input is reported once. */
@@ -264,29 +409,17 @@ function warnTransformThrow(baseClassName, propName, transformName, serializedVa
264
409
  }
265
410
  }
266
411
  /**
267
- * Whether `resolved` reads `currentVar` through a `var()` at any depth, its
268
- * fallbacks included: a function token whose decoded name is `var` in any
269
- * case, with the decoded variable name as its first argument. Comments,
270
- * quoted strings and `url()` read nothing. The extractor's static path skips
271
- * its `currentVar` write by the same predicate.
272
- */
273
- function readsCurrentVar(resolved, currentVar) {
274
- if (!resolved.includes("(")) return false;
275
- const destination = decodedIdentifier(currentVar);
276
- return variableReads(componentValues(tokenize(resolved))).some((read) => read.name === destination);
277
- }
278
- /**
279
412
  * A value that reads the prop's own `currentVar` takes the slot that leaves
280
- * it alone, since writing it would make the variable cyclic.
413
+ * it alone, since writing it would make the variable cyclic. The extractor's
414
+ * static path skips its `currentVar` write by the same predicate.
281
415
  */
282
416
  function slotClassFor(dc, resolved) {
283
- return dc.currentVar !== void 0 && readsCurrentVar(resolved, dc.currentVar) ? `${dc.slotClass}--keep` : dc.slotClass;
417
+ return dc.currentVar !== void 0 && readsVariable(resolved, dc.currentVar) ? `${dc.slotClass}--keep` : dc.slotClass;
284
418
  }
285
419
  /**
286
420
  * Resolution is staged so a drop is atomic: one entry that misses a strict
287
421
  * scale, or one transform that throws or returns an invalid result, leaves
288
- * `classes` and `dynStyle` untouched. A responsive entry that is a CSS-wide
289
- * keyword takes the class extraction emits for it at that breakpoint.
422
+ * `classes` and `dynStyle` untouched.
290
423
  *
291
424
  * A slot is a CSS variable, which cannot carry `!important`. A literal that
292
425
  * carries it, at a breakpoint, takes the class extraction emits for every
@@ -296,16 +429,15 @@ function slotClassFor(dc, resolved) {
296
429
  * priority, which `ignoredImportant` hears of. Where only custom properties
297
430
  * read the value, a trailing `!` is no priority, as at build time.
298
431
  */
299
- function applyDynamicProp(classes, dynStyle, propValue, dc, literalClass, ignoredImportant) {
432
+ function applyDynamicProp(classes, dynStyle, propValue, dc, propClasses, typed, ignoredImportant) {
300
433
  const staged = [];
301
434
  const [responsive, entries] = responsiveEntries(propValue);
302
- const lookup = (bp, value) => literalClass(bp === "_" ? value : { [bp]: value });
303
435
  for (const [bp, authored] of entries) {
304
436
  let value = authored;
305
437
  let ignored;
306
- const priority = typeof authored === "string" ? importantPriority(authored) : void 0;
438
+ const priority = typeof authored === "string" ? trailingPriority(authored) : void 0;
307
439
  if (typeof authored === "string" && priority && (priority.spelling === "important" || !isCustomOnly(slotProperties(dc)))) {
308
- const literal = responsive ? lookup(bp, authored) : void 0;
440
+ const literal = responsive ? entryClass(propClasses, typed, bp, authored) : void 0;
309
441
  if (literal) {
310
442
  staged.push([literal]);
311
443
  continue;
@@ -313,11 +445,6 @@ function applyDynamicProp(classes, dynStyle, propValue, dc, literalClass, ignore
313
445
  ignored = authored;
314
446
  if (priority.spelling === "shorthand") value = `${authored.slice(0, priority.end)} !important`;
315
447
  }
316
- const keywordClass = responsive && CSS_WIDE_KEYWORDS.has(value) ? lookup(bp, value) : void 0;
317
- if (keywordClass) {
318
- staged.push([keywordClass]);
319
- continue;
320
- }
321
448
  let resolved = resolveEntry(value, dc);
322
449
  if (typeof resolved !== "string") return "entry" in resolved && responsive ? {
323
450
  ...resolved,
@@ -325,13 +452,8 @@ function applyDynamicProp(classes, dynStyle, propValue, dc, literalClass, ignore
325
452
  } : resolved;
326
453
  if (ignored !== void 0) {
327
454
  ignoredImportant(ignored);
328
- const end = importantPriority(resolved)?.end;
455
+ const end = trailingPriority(resolved)?.end;
329
456
  resolved = end === void 0 ? resolved : resolved.slice(0, end);
330
- const bareKeyword = CSS_WIDE_KEYWORDS.has(resolved) ? lookup(bp, resolved) : void 0;
331
- if (bareKeyword) {
332
- staged.push([bareKeyword]);
333
- continue;
334
- }
335
457
  }
336
458
  const slotClass = slotClassFor(dc, resolved);
337
459
  staged.push(bp === "_" ? [
@@ -345,8 +467,16 @@ function applyDynamicProp(classes, dynStyle, propValue, dc, literalClass, ignore
345
467
  ]);
346
468
  }
347
469
  for (const [cls, varName, resolved] of staged) {
470
+ if (varName !== void 0 && resolved !== void 0) {
471
+ if (varName in dynStyle) {
472
+ const keep = `${dc.slotClass}--keep`;
473
+ const twin = cls.startsWith(keep) ? dc.slotClass + cls.slice(keep.length) : keep + cls.slice(dc.slotClass.length);
474
+ const at = classes.indexOf(cls);
475
+ classes.splice(at >= 0 ? at : classes.indexOf(twin), 1);
476
+ }
477
+ dynStyle[varName] = resolved;
478
+ }
348
479
  classes.push(cls);
349
- if (varName !== void 0 && resolved !== void 0) dynStyle[varName] = resolved;
350
480
  }
351
481
  return null;
352
482
  }
@@ -379,6 +509,29 @@ function applyDeclarationProp(classes, dynStyle, propValue, dc) {
379
509
  return null;
380
510
  }
381
511
  /**
512
+ * The static class of `value` written alone at breakpoint `bp` (`_` the
513
+ * base), keyed as extraction keys it.
514
+ */
515
+ function entryClass(propClasses, typed, bp, value) {
516
+ return propClasses?.[(typed ? typedValueKey : serializeValueKey)(bp === "_" ? value : { [bp]: value })];
517
+ }
518
+ /**
519
+ * The static classes of a responsive value's entries, when every entry has
520
+ * one: extraction gives a value proven to be one of a few at each
521
+ * breakpoint a class per entry, and no slot.
522
+ */
523
+ function entryClasses(propValue, propClasses, typed) {
524
+ const [responsive, entries] = responsiveEntries(propValue);
525
+ if (!responsive || entries.length === 0) return void 0;
526
+ const found = [];
527
+ for (const [bp, value] of entries) {
528
+ const cls = entryClass(propClasses, typed, bp, value);
529
+ if (!cls) return void 0;
530
+ found.push(cls);
531
+ }
532
+ return found;
533
+ }
534
+ /**
382
535
  * A responsive value without its nullish breakpoints, so it keys the same
383
536
  * static class as the value written without them; `undefined` when none
384
537
  * remain, as if the prop were omitted.
@@ -390,6 +543,20 @@ function withoutAbsentBreakpoints(value) {
390
543
  const present = entries.filter(([, entry]) => entry != null);
391
544
  return present.length > 0 ? Object.fromEntries(present) : void 0;
392
545
  }
546
+ /**
547
+ * The config with each admitted prop named once, at its first position. An
548
+ * extracted config lists every admitted group's props, and groups can share
549
+ * one, as flexbox and grid share `alignItems`.
550
+ */
551
+ function withUniqueSystemPropNames(config) {
552
+ const names = config.systemPropNames;
553
+ if (!names) return config;
554
+ const unique = [...new Set(names)];
555
+ return unique.length === names.length ? config : {
556
+ ...config,
557
+ systemPropNames: unique
558
+ };
559
+ }
393
560
  function resolveClasses(baseClassName, props, config, systemPropMap, dynamicPropConfig) {
394
561
  const classes = [baseClassName];
395
562
  let dynStyle;
@@ -399,33 +566,42 @@ function resolveClasses(baseClassName, props, config, systemPropMap, dynamicProp
399
566
  applyStateClasses(classes, baseClassName, props, config, activeStates);
400
567
  const systemPropNames = config.systemPropNames || [];
401
568
  if (systemPropNames.length > 0) {
402
- const { customPropMap, customDynamicConfig } = config;
569
+ const { customPropMap, customDynamicConfig, supersededBy } = config;
403
570
  for (const propName of systemPropNames) {
404
571
  const propValue = withoutAbsentBreakpoints(props[propName]);
405
572
  if (propValue == null) continue;
573
+ if (supersededBy?.[propName]?.some((later) => withoutAbsentBreakpoints(props[later]) != null)) continue;
406
574
  const key = serializeValueKey(propValue);
407
575
  const customOwned = customPropMap?.[propName] !== void 0 || customDynamicConfig?.[propName] !== void 0;
408
576
  const [classMap, typedProps] = customOwned ? [customPropMap, config.typedCustomProps] : [systemPropMap, config.typedSystemProps];
409
577
  const typed = typedProps?.includes(propName) === true;
410
- const cls = classMap?.[propName]?.[typed ? typedValueKey(propValue) : key];
411
- if (cls) {
412
- classes.push(cls);
413
- recordWitness(baseClassName, propName, key, "static");
578
+ const propClasses = classMap?.[propName];
579
+ const cls = propClasses?.[typed ? typedValueKey(propValue) : key];
580
+ const found = cls ? [cls] : propClasses && entryClasses(propValue, propClasses, typed);
581
+ if (found) {
582
+ classes.push(...found);
583
+ if (typeof __ANIMUS_DEV__ === "boolean" ? __ANIMUS_DEV__ : IS_DEV) recordWitness(baseClassName, propName, key, "static");
414
584
  } else {
415
585
  const dc = customOwned ? customDynamicConfig?.[propName] : dynamicPropConfig?.[propName];
416
586
  if (dc) {
417
587
  const staged = dynStyle ?? {};
418
- const failure = dc.kind === "declarations" ? applyDeclarationProp(classes, staged, propValue, dc) : applyDynamicProp(classes, staged, propValue, dc, (value) => classMap?.[propName]?.[(typed ? typedValueKey : serializeValueKey)(value)], (value) => warnIgnoredImportant(baseClassName, propName, value));
588
+ const failure = dc.kind === "declarations" ? applyDeclarationProp(classes, staged, propValue, dc) : applyDynamicProp(classes, staged, propValue, dc, propClasses, typed, (value) => {
589
+ if (typeof __ANIMUS_DEV__ === "boolean" ? __ANIMUS_DEV__ : IS_DEV) warnIgnoredImportant(baseClassName, propName, value);
590
+ });
419
591
  if (failure === null) {
420
592
  dynStyle = staged;
421
- recordWitness(baseClassName, propName, key, "dynamic");
422
- } else {
593
+ if (typeof __ANIMUS_DEV__ === "boolean" ? __ANIMUS_DEV__ : IS_DEV) {
594
+ recordWitness(baseClassName, propName, key, "dynamic");
595
+ warnPrunedSlot(baseClassName, propName, key, propValue, dc, propClasses, typed);
596
+ if (dc.kind !== "declarations") warnUnitlessCustomProperty(baseClassName, propName, propValue, dc);
597
+ }
598
+ } else if (typeof __ANIMUS_DEV__ === "boolean" ? __ANIMUS_DEV__ : IS_DEV) {
423
599
  if ("shape" in failure) warnInvalidTransformResult(baseClassName, propName, failure.shape);
424
600
  else if ("entry" in failure) warnStrictScaleMiss(baseClassName, propName, key, failure);
425
601
  else warnTransformThrow(baseClassName, propName, dc.transformName, key, failure.cause);
426
602
  recordWitness(baseClassName, propName, key, "drop");
427
603
  }
428
- } else {
604
+ } else if (typeof __ANIMUS_DEV__ === "boolean" ? __ANIMUS_DEV__ : IS_DEV) {
429
605
  warnDroppedValue(baseClassName, propName, key, customOwned);
430
606
  recordWitness(baseClassName, propName, key, "drop");
431
607
  }
@@ -439,19 +615,104 @@ function resolveClasses(baseClassName, props, config, systemPropMap, dynamicProp
439
615
  };
440
616
  }
441
617
  //#endregion
618
+ //#region src/runtime/uncompiled.ts
619
+ /**
620
+ * What rendering a component that Animus did not compile does: nothing, a
621
+ * console warning once per component, or a thrown error on every render.
622
+ * Without compilation its classes are empty and no CSS exists for it.
623
+ * Production stays silent until the project owner chooses its level, and its
624
+ * bundles carry no report: the calls in `createComponent` and
625
+ * `createClassResolver` test the define token in place, which a minifier
626
+ * removes. A production level changes this line and lifts those guards.
627
+ */
628
+ const UNCOMPILED_RENDER = IS_DEV ? "warn" : "off";
629
+ /**
630
+ * The record a builder terminal attaches to the component it builds at
631
+ * runtime. Only an uncompiled definition reaches a terminal: a compiled
632
+ * one is replaced with the component's extracted classes. `undefined` when
633
+ * uncompiled renders are not reported.
634
+ */
635
+ function uncompiledDefinition(target) {
636
+ if (UNCOMPILED_RENDER === "off") return void 0;
637
+ return {
638
+ target,
639
+ origin: declaringModule((/* @__PURE__ */ new Error()).stack),
640
+ reported: false
641
+ };
642
+ }
643
+ /**
644
+ * Reports a render of an uncompiled definition at the configured level.
645
+ * Defining or importing one reports nothing; only a render does.
646
+ */
647
+ function reportUncompiledRender(definition, displayName) {
648
+ if (!definition) return;
649
+ if (UNCOMPILED_RENDER === "error") throw new Error(uncompiledMessage(definition, displayName));
650
+ if (definition.reported) return;
651
+ definition.reported = true;
652
+ console.warn(uncompiledMessage(definition, displayName));
653
+ }
654
+ function uncompiledMessage({ target, origin }, displayName) {
655
+ const component = displayName ? `${displayName} (${target})` : target;
656
+ const pkg = origin && packageOf(origin);
657
+ const declared = origin ? ` declared in ${origin}` : "";
658
+ const subject = pkg ? `the package ${pkg}` : "its module";
659
+ return `[animus:uncompiled] ${component}${declared} rendered without Animus compiling it: its classes are empty and no CSS exists for it. Add the Animus plugin for this app's host (Vite, Next.js, Rollup) so it compiles ${subject}, or build ${subject} with the Animus CLI.`;
660
+ }
661
+ /**
662
+ * The caller of the builder terminal: the frame after the terminal's own.
663
+ * Engines differ in frame syntax, so only `file:line:column` is read.
664
+ */
665
+ function declaringModule(stack) {
666
+ return (stack ?? "").split("\n").map((line) => /((?:[a-z][\w+.-]*:\/\/|\/)[^\s()]+:\d+:\d+)/i.exec(line)?.[1]).filter((frame) => frame !== void 0)[2];
667
+ }
668
+ /**
669
+ * The installed package a path lies in, as `node_modules` names it, or as
670
+ * Vite's dependency optimizer names the file it bundles the package into:
671
+ * `@scope/name/sub` becomes `.vite/deps/@scope_name_sub.js`, a `.` becomes
672
+ * `__`, and a shared chunk names no package.
673
+ */
674
+ function packageOf(path) {
675
+ const installed = /\/node_modules\/((?:@[^/]+\/)?[^/.][^/]*)\//.exec(path);
676
+ if (installed) return installed[1];
677
+ const optimized = /\/node_modules\/\.vite\/deps(?:_[^/]*)?\/([^/?#:]+?)\.m?js\b/.exec(path)?.[1];
678
+ if (!optimized || optimized.startsWith("chunk-")) return void 0;
679
+ const flat = optimized.replace(/__/g, ".");
680
+ return /^(@[^_]+_)?[^_]+/.exec(flat)?.[0].replace("_", "/");
681
+ }
682
+ //#endregion
442
683
  //#region src/runtime/createClassResolver.ts
443
684
  function serializeDynamicStyle(style) {
444
685
  return Object.entries(style).map(([property, value]) => `${property}: ${value}`).join("; ");
445
686
  }
446
- function createClassResolver(className, config, systemPropMap, dynamicPropConfig) {
687
+ function createClassResolver(className, resolverConfig, systemPropMap, dynamicPropConfig) {
688
+ const config = withUniqueSystemPropNames(resolverConfig);
447
689
  const resolveAttributes = (props) => {
690
+ if (typeof __ANIMUS_DEV__ === "boolean" ? __ANIMUS_DEV__ : IS_DEV) reportUncompiledRender(config.uncompiled, void 0);
448
691
  const { classes, dynamicStyle } = resolveClasses(className, props || {}, config, systemPropMap, dynamicPropConfig);
449
692
  const attributes = { class: classes.join(" ") };
450
693
  if (dynamicStyle && Object.keys(dynamicStyle).length > 0) attributes.style = serializeDynamicStyle(dynamicStyle);
451
694
  return attributes;
452
695
  };
453
- const resolver = (props) => resolveClasses(className, props || {}, config, systemPropMap, dynamicPropConfig).classes.join(" ");
454
- return Object.assign(resolver, { attrs: resolveAttributes });
696
+ const resolveProps = (props) => {
697
+ if (typeof __ANIMUS_DEV__ === "boolean" ? __ANIMUS_DEV__ : IS_DEV) reportUncompiledRender(config.uncompiled, void 0);
698
+ const { classes, dynamicStyle } = resolveClasses(className, props || {}, config, systemPropMap, dynamicPropConfig);
699
+ if (props?.className) classes.push(props.className);
700
+ const attributes = { className: classes.join(" ") };
701
+ const style = props?.style ? {
702
+ ...dynamicStyle,
703
+ ...props.style
704
+ } : dynamicStyle;
705
+ if (style && Object.keys(style).length > 0) attributes.style = style;
706
+ return attributes;
707
+ };
708
+ const resolver = (props) => {
709
+ if (typeof __ANIMUS_DEV__ === "boolean" ? __ANIMUS_DEV__ : IS_DEV) reportUncompiledRender(config.uncompiled, void 0);
710
+ return resolveClasses(className, props || {}, config, systemPropMap, dynamicPropConfig).classes.join(" ");
711
+ };
712
+ return Object.assign(resolver, {
713
+ attrs: resolveAttributes,
714
+ props: resolveProps
715
+ });
455
716
  }
456
717
  //#endregion
457
- export { resolveClasses as n, createClassResolver as t };
718
+ export { withUniqueSystemPropNames as a, resolveClasses as i, reportUncompiledRender as n, IS_DEV as o, uncompiledDefinition as r, createClassResolver as t };
@@ -1,4 +1,4 @@
1
- import { n as resolveClasses } from "./createClassResolver-DtHjTYa7.js";
1
+ import { a as withUniqueSystemPropNames, i as resolveClasses, n as reportUncompiledRender, o as IS_DEV } from "./createClassResolver-DdrmIC5J.js";
2
2
  import { t as assertRootSlot } from "./assert-root-slot-C3Lp9MNJ.js";
3
3
  import { Children, cloneElement, createElement, forwardRef } from "react";
4
4
  //#region src/runtime/index.ts
@@ -117,25 +117,42 @@ function renderSlot(children, props, ref, variables) {
117
117
  function renderAsChild(children, props, ref) {
118
118
  return renderSlot(children, props, ref);
119
119
  }
120
+ /** Animus's classes, then the caller's. */
121
+ function joinClassNames(classes, className) {
122
+ return className ? `${classes} ${className}` : classes;
123
+ }
120
124
  /**
121
125
  * A string terminal renders `as` in place of its tag. A component terminal
122
126
  * always renders its component, which receives `as` and `asChild` like any
123
127
  * other prop, so its own wiring runs whatever element it ends up rendering.
128
+ *
129
+ * A component target may take `className` and `style` as callbacks that it
130
+ * calls with its own state, as Base UI's do. It gets a callback in their
131
+ * place, which merges the caller's result as a plain value merges: Animus's
132
+ * classes first, and Animus's dynamic style over the caller's style.
124
133
  */
125
134
  function renderElement(element, filterProps, props, ref, classes, dynamicStyle) {
126
- const target = typeof element === "string" ? props.as || element : element;
135
+ const ownsElement = typeof element === "string";
136
+ const target = ownsElement ? props.as || element : element;
137
+ const { className, style } = props;
138
+ const ownClassName = classes.join(" ");
127
139
  const domProps = {
128
140
  ref,
129
- className: classes.join(" ")
141
+ className: !ownsElement && typeof className === "function" ? (state) => joinClassNames(ownClassName, className(state)) : joinClassNames(ownClassName, className)
130
142
  };
131
143
  forwardProps(props, filterProps, domProps);
132
- if (dynamicStyle) domProps.style = props.style ? {
133
- ...props.style,
144
+ if (dynamicStyle && !ownsElement && typeof style === "function") domProps.style = (state) => ({
145
+ ...style(state),
146
+ ...dynamicStyle
147
+ });
148
+ else if (dynamicStyle) domProps.style = style ? {
149
+ ...style,
134
150
  ...dynamicStyle
135
151
  } : dynamicStyle;
136
152
  return createElement(target, domProps);
137
153
  }
138
- function createComponent(element, className, config, systemPropMap, dynamicPropConfig) {
154
+ function createComponent(element, className, componentConfig, systemPropMap, dynamicPropConfig) {
155
+ const config = withUniqueSystemPropNames(componentConfig);
139
156
  const variantProps = config.variants ? Object.keys(config.variants) : [];
140
157
  const stateProps = config.states || [];
141
158
  const systemPropNames = config.systemPropNames || [];
@@ -147,10 +164,10 @@ function createComponent(element, className, config, systemPropMap, dynamicPropC
147
164
  ...systemPropNames
148
165
  ]);
149
166
  const Component = forwardRef((props, ref) => {
167
+ if (typeof __ANIMUS_DEV__ === "boolean" ? __ANIMUS_DEV__ : IS_DEV) reportUncompiledRender(config.uncompiled, Component.displayName || void 0);
150
168
  const { classes, dynamicStyle } = resolveClasses(className, props, config, systemPropMap, dynamicPropConfig);
151
- if (props.className) classes.push(props.className);
152
169
  if (props.asChild && ownsPolymorphism) {
153
- const slotProps = { className: classes.join(" ") };
170
+ const slotProps = { className: joinClassNames(classes.join(" "), props.className) };
154
171
  forwardProps(props, filterProps, slotProps);
155
172
  return renderSlot(props.children, slotProps, ref, dynamicStyle);
156
173
  }
@@ -1,4 +1,4 @@
1
- import { a as DynamicPropConfig, i as ClassResolverConfig, o as SystemPropMap } from "./createClassResolver-Do1nmvkf.js";
1
+ import { a as ClassResolverConfig, o as DynamicPropConfig, s as SystemPropMap } from "./createClassResolver-C-UOgE_d.js";
2
2
  import { ForwardRefExoticComponent, ReactElement, ReactNode, Ref, forwardRef } from "react";
3
3
  //#region src/runtime/index.d.ts
4
4
  interface ComponentConfig extends ClassResolverConfig {}
@@ -28,7 +28,7 @@ type AnimusComponent = ReturnType<typeof forwardRef> & {
28
28
  * leaves its children out without `asChild`, where React would throw.
29
29
  */
30
30
  declare function renderAsChild(children: ReactNode, props: object, ref?: Ref<any>): ReactElement;
31
- declare function createComponent(element: ElementType, className: string, config: ComponentConfig, systemPropMap?: SystemPropMap, dynamicPropConfig?: DynamicPropConfig): AnimusComponent;
31
+ declare function createComponent(element: ElementType, className: string, componentConfig: ComponentConfig, systemPropMap?: SystemPropMap, dynamicPropConfig?: DynamicPropConfig): AnimusComponent;
32
32
  //#endregion
33
33
  //#region src/runtime/createComposedFamily.d.ts
34
34
  interface ComposedFamilyConfig {
@@ -37,4 +37,4 @@ interface ComposedFamilyConfig {
37
37
  declare function createComposedFamily(slots: Record<string, ForwardRefExoticComponent<any>>, config: ComposedFamilyConfig): Record<string, ForwardRefExoticComponent<any>>;
38
38
  //#endregion
39
39
  export { createComponent as n, renderAsChild as r, createComposedFamily as t };
40
- //# sourceMappingURL=createComposedFamily-BkrvSsfl.d.ts.map
40
+ //# sourceMappingURL=createComposedFamily-BJQ5Cn76.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createComposedFamily-BJQ5Cn76.d.ts","names":[],"sources":["../src/runtime/index.ts","../src/runtime/createComposedFamily.ts"],"mappings":";;;UAqBU,wBAAwB;KAE7B,uBAAuB,MAAM;KAE7B,YAAY,YAAY;KAExB,kBAAkB,kBAAkB;EACvC;EACA,iBAAiB,SAAS;EAC1B,kBAAkB,SAAS,eAAe;;;;;;;;;;;;;;;;;;;;iBAyJ5B,cACd,UAAU,WACV,eACA,MAAM,WACL;iBAkDa,gBACd,SAAS,aACT,mBACA,iBAAiB,iBACjB,gBAAgB,eAChB,oBAAoB,oBACnB;;;UCrOO;EACR;;iBAGc,qBACd,OAAO,eAAe,iCACtB,QAAQ,uBACP,eAAe"}
@@ -1,4 +1,4 @@
1
- import { J as NamedTransform } from "../index-C1AAiaiJ.js";
1
+ import { J as NamedTransform } from "../index-CQ7BFlKp.js";
2
2
  //#region src/groups/index.d.ts
3
3
  export declare const color: {
4
4
  readonly color: {