@menapo/vicss 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,78 @@
1
- import { $ as keepAtom, A as getProperty, At as splitClasses, Ct as makeClassName, F as markerId, Ft as stableStringify, I as markerLabel, K as placeholder, L as markerSites, M as numberKind, Mt as toRecord, Nt as valueLabel, Ot as propertyKey, P as MarkerValue, Pt as hash, Tt as mergeClassNames, _t as VicssError, at as SPACING_STEP, b as hasPrivateAccess, ct as cssVarName, dt as isScaleName, ft as lookupToken, g as normalizeStyle, h as compileKeyframes, ht as suggestTokens, it as SCALE_NAMESPACES, jt as styleLiteral, k as PROPERTIES, kt as readableClass, lt as didYouMean, mt as scaleTokens, nt as typedPaletteNames, ot as closestNames, rt as SCALES, ut as isPlainObject, vt as classAttribute, x as privateTokenError, xt as fromRecord, yt as compareStrings } from "./print-Ds6fkgio.js";
2
- import { C as createLocator, _ as evaluate, a as buildRecipe, c as resolveRecipe, h as NOT_HANDLED, i as RecipeValue, m as shadowedByEarlier, n as compileTheme, o as printTable, p as isBaseClass, r as analyzeScopes, s as recipeKey, v as evaluateBinding, y as isDynamic } from "./theme-C-0LpsJx.js";
1
+ import { $ as isPaletteAtom, A as PROPERTIES, At as readableClass, Et as mergeClassNames, F as MarkerValue, Ft as hash, I as markerId, It as stableStringify, L as markerLabel, Mt as styleLiteral, N as numberKind, Nt as toRecord, Pt as valueLabel, R as markerSites, S as privateTokenError, St as fromRecord, _ as normalizeStyle, _t as VicssError, at as SCALE_NAMESPACES, bt as compareStrings, dt as isPlainObject, et as keepAtom, ft as isScaleName, g as compileKeyframes, gt as suggestTokens, ht as scaleTokens, it as SCALES, j as getProperty, jt as splitClasses, kt as propertyKey, lt as cssVarName, ot as SPACING_STEP, pt as lookupToken, q as placeholder, rt as typedPaletteNames, st as closestNames, ut as didYouMean, wt as makeClassName, x as hasPrivateAccess, yt as classAttribute } from "./print-B_mn7wBu.js";
2
+ import { C as createLocator, _ as evaluate, a as buildRecipe, c as resolveRecipe, h as NOT_HANDLED, i as RecipeValue, m as shadowedByEarlier, n as compileTheme, o as printTable, p as isBaseClass, r as analyzeScopes, s as recipeKey, v as evaluateBinding, y as isDynamic } from "./theme-zBcr5DHi.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import path from "node:path";
6
6
  import { availableParallelism } from "node:os";
7
7
  import { Worker } from "node:worker_threads";
8
+ //#region src/core/attributes.ts
9
+ /**
10
+ * A compiled style written as an element's `style=` (PLAN §5.16): what goes
11
+ * inline, what stays a class, and how each framework writes an inline
12
+ * style. The compiler finds the attributes (core/compile.ts); this module
13
+ * holds what does not need the syntax tree.
14
+ *
15
+ * An inline declaration beats every class rule, so a property with a
16
+ * condition (`_hover`, `_md`, `_dark`) cannot be split between the two:
17
+ * its base value inline would win over the class of its hover value. Such
18
+ * a property goes to the classes whole, base value included, and so does
19
+ * every property related to it: a shorthand over a conditional longhand
20
+ * (`padding` inline would beat `_hover: { paddingTop }`), a longhand under a
21
+ * conditional shorthand, and any atom on the same CSS property.
22
+ */
23
+ /** `a` is `b`, or one lies below the other (`p` and `p-t`, `padding` and `padding-top`). */
24
+ const related = (a, b) => a === b || a.startsWith(`${b}-`) || b.startsWith(`${a}-`);
25
+ /**
26
+ * A compiled style's classes (`CompiledStyle.classes`) split for `style=`.
27
+ * `atomOf` finds a class's atom; a class with none (the app's own names, a
28
+ * theme, a marker, a flattened base) stays a class, and so does a palette
29
+ * atom, whose rule sets custom properties a declaration cannot name.
30
+ */
31
+ function splitStyle(classes, atomOf) {
32
+ const atoms = [];
33
+ const kept = [];
34
+ for (const cls of classes.split(" ")) {
35
+ if (!cls) continue;
36
+ const atom = atomOf(cls);
37
+ if (atom && !isPaletteAtom(atom)) atoms.push(atom);
38
+ else kept.push(cls);
39
+ }
40
+ const conditional = atoms.filter((atom) => atom.chain.length > 0);
41
+ const inline = [];
42
+ for (const atom of atoms) if (atom.chain.length > 0 || conditional.some((c) => related(c.key, atom.key) || related(c.property, atom.property))) kept.push(atom.className);
43
+ else inline.push(atom);
44
+ inline.sort((a, b) => a.tier - b.tier || (a.property < b.property ? -1 : 1));
45
+ return {
46
+ declarations: inline.map((atom) => [atom.property, atom.value]),
47
+ classes: kept
48
+ };
49
+ }
50
+ /** `padding-top` → `paddingTop`, `-webkit-box` → `WebkitBox`, `-ms-grid` → `msGrid`; custom properties stay. */
51
+ function camelProperty(property) {
52
+ if (property.startsWith("--")) return property;
53
+ return (property.startsWith("-ms-") ? property.slice(1) : property).replace(/-([a-z])/g, (_, ch) => ch.toUpperCase());
54
+ }
55
+ /**
56
+ * The declarations as an inline style's text, `a:b;c:d`. Double quotes in a
57
+ * value (a font family) become single ones, which CSS reads alike, so the
58
+ * text fits a double-quoted attribute in any template unescaped.
59
+ */
60
+ function inlineText(declarations) {
61
+ return declarations.map(([property, value]) => `${property}:${value.includes("'") ? value : value.replaceAll("\"", "'")}`).join(";");
62
+ }
63
+ /** The declarations as JS: an object in React and Preact's case or Solid's, else a string. */
64
+ function inlineStyle(declarations, shape) {
65
+ if (shape === "string") return JSON.stringify(inlineText(declarations));
66
+ return `{ ${declarations.map(([property, value]) => {
67
+ const key = shape === "camel" ? camelProperty(property) : property;
68
+ return `${/^[A-Za-z_$][\w$]*$/.test(key) ? key : JSON.stringify(key)}: ${JSON.stringify(value)}`;
69
+ }).join(", ")} }`;
70
+ }
71
+ /** Text for a template literal's raw part: what would end it or start a substitution, escaped. */
72
+ function templateText(text) {
73
+ return text.replace(/[`\\]|\$(?=\{)/g, (ch) => `\\${ch}`);
74
+ }
75
+ //#endregion
8
76
  //#region src/core/edits.ts
9
77
  /**
10
78
  * Source edits with a source map (PLAN D20): the few operations the rewrite
@@ -291,38 +359,55 @@ const RUNTIME_EXPORTS = /* @__PURE__ */ new Set([
291
359
  const SX_SHAPES = {
292
360
  react: {
293
361
  attribute: "className",
294
- dev: "__vxSxDev"
362
+ dev: "__vxSxDev",
363
+ inline: "camel"
295
364
  },
296
365
  preact: {
297
366
  attribute: "className",
298
- dev: "__vxSxDev"
367
+ dev: "__vxSxDev",
368
+ inline: "camel"
299
369
  },
300
370
  solid: {
301
371
  attribute: "class",
302
372
  target: "__vxSxClass",
303
- dev: "__vxSxDevClass"
373
+ dev: "__vxSxDevClass",
374
+ inline: "kebab"
304
375
  },
305
376
  vue: {
306
377
  attribute: "class",
307
378
  target: "__vxSxClass",
308
- dev: "__vxSxDevClass"
379
+ dev: "__vxSxDevClass",
380
+ inline: "string"
309
381
  },
310
382
  svelte: {
311
383
  attribute: "class",
312
384
  target: "__vxSxString",
313
- dev: "__vxSxDevString"
385
+ dev: "__vxSxDevString",
386
+ inline: "string"
314
387
  },
315
388
  astro: {
316
389
  attribute: "class",
317
390
  target: "__vxSxString",
318
- dev: "__vxSxDevString"
391
+ dev: "__vxSxDevString",
392
+ inline: "string"
319
393
  },
320
394
  vanilla: {
321
395
  attribute: "class",
322
396
  target: "__vxSxString",
323
- dev: "__vxSxDevString"
397
+ dev: "__vxSxDevString",
398
+ inline: "string"
324
399
  }
325
400
  };
401
+ /** The framework functions a compiled component sets `style` and `class` through. */
402
+ const VUE = /* @__PURE__ */ new Set([
403
+ "vue",
404
+ "vue/server-renderer",
405
+ "@vue/server-renderer"
406
+ ]);
407
+ const SVELTE = /* @__PURE__ */ new Set(["svelte/internal/client", "svelte/internal/server"]);
408
+ const ASTRO = /^astro\/(?:runtime\/server(?:\/index\.js)?|compiler-runtime)$/;
409
+ /** Template tags that read `style` as an object of their own (kit's templates). */
410
+ const OBJECT_STYLE_TAGS = /^@menapo\/(?:markup|og)(?:\/|$)/;
326
411
  /** D18: a folded `sx()` precomputes 2^n strings for n conditional arguments. */
327
412
  const MAX_FOLD_CONDITIONS = 2;
328
413
  /** Stands for a runtime value while the normaliser checks the rest of a function style. */
@@ -579,9 +664,11 @@ var Compiler = class {
579
664
  /** Rewrites the runtime calls under `node`. */
580
665
  rewrite(node, editor) {
581
666
  walk(this.root, node, (child) => {
582
- if (child.type !== "CallExpression") return true;
583
- const name = runtimeName(this.root, child.callee);
667
+ if (this.pass.skip.has(child)) return false;
584
668
  try {
669
+ if (editor === this.pass.edits) this.rewriteAttributes(child);
670
+ if (child.type !== "CallExpression") return true;
671
+ const name = runtimeName(this.root, child.callee);
585
672
  if (name === void 0) return !this.foldRecipeCall(child, editor);
586
673
  return !this.rewriteCall(child, name, editor);
587
674
  } catch (error) {
@@ -625,7 +712,7 @@ var Compiler = class {
625
712
  case "keyframes":
626
713
  case "theme": {
627
714
  const rule = this.compileRule(root, name, call.callee, call.arguments, evaluateHere);
628
- const text = name === "theme" ? `(${styleLiteral(rule.id, this.emit)})` : JSON.stringify(rule.id);
715
+ const text = name === "theme" ? this.styled(styleLiteral(rule.id, this.emit)) : JSON.stringify(rule.id);
629
716
  this.replace(editor, call.start, call.end, text);
630
717
  return true;
631
718
  }
@@ -649,12 +736,361 @@ var Compiler = class {
649
736
  case "group":
650
737
  case "peer": {
651
738
  const marker = this.markerOf(root, name, call.callee, call.arguments, evaluateHere);
652
- this.replace(editor, call.start, call.end, `(${styleLiteral(marker.className, this.emit)})`);
739
+ this.replace(editor, call.start, call.end, this.styled(styleLiteral(marker.className, this.emit)));
653
740
  return true;
654
741
  }
655
742
  default: return false;
656
743
  }
657
744
  }
745
+ /**
746
+ * A `style=` whose value is a compiled style the compiler can read: its
747
+ * properties without a condition become the inline style, in the shape
748
+ * the element's framework takes, and the rest become classes merged into
749
+ * the element's own (core/attributes.ts says why a conditional property
750
+ * moves whole). Elements are found where each framework's output leaves
751
+ * them: JSX attributes, Vue's vnode props, Svelte's setters, and the
752
+ * templates of Vue's and Svelte's server output, Astro and plain JS.
753
+ */
754
+ rewriteAttributes(node) {
755
+ switch (node.type) {
756
+ case "JSXOpeningElement":
757
+ this.jsxStyle(node);
758
+ return;
759
+ case "TaggedTemplateExpression": {
760
+ const tag = unwrap(node.tag);
761
+ const binding = tag.type === "Identifier" ? this.root.scopes.bindingOf(tag) : void 0;
762
+ if (binding?.kind === "import" && OBJECT_STYLE_TAGS.test(binding.source ?? "")) this.pass.objectStyles.add(node.quasi);
763
+ return;
764
+ }
765
+ case "TemplateLiteral":
766
+ if (!this.pass.objectStyles.has(node)) this.templateStyles(node);
767
+ return;
768
+ case "ObjectExpression":
769
+ if (this.root.vue) this.vueStyle(node);
770
+ return;
771
+ case "CallExpression":
772
+ this.svelteStyle(node);
773
+ return;
774
+ default: return;
775
+ }
776
+ }
777
+ /** The framework function a callee names: `_normalizeStyle`, `$.set_style`, `$$addAttribute`. */
778
+ frameworkFunction(callee) {
779
+ const target = unwrap(callee);
780
+ const { scopes } = this.root;
781
+ if (target.type === "Identifier") {
782
+ const binding = scopes.bindingOf(target);
783
+ return binding?.kind === "import" && binding.source && binding.imported ? {
784
+ source: binding.source,
785
+ name: binding.imported
786
+ } : void 0;
787
+ }
788
+ if (target.type !== "MemberExpression") return void 0;
789
+ const object = unwrap(target.object);
790
+ const key = memberKey(target);
791
+ if (object.type !== "Identifier" || key === void 0) return void 0;
792
+ const binding = scopes.bindingOf(object);
793
+ return binding?.kind === "import" && binding.imported === "*" && binding.source ? {
794
+ source: binding.source,
795
+ name: key
796
+ } : void 0;
797
+ }
798
+ /** The argument of a call of one of `names`, from a source `sources` takes; undefined otherwise. */
799
+ argumentOf(node, sources, names, at = 0) {
800
+ const call = unwrap(node);
801
+ if (call.type !== "CallExpression") return void 0;
802
+ const fn = this.frameworkFunction(call.callee);
803
+ if (!fn || !names.includes(fn.name)) return void 0;
804
+ if (sources instanceof RegExp ? !sources.test(fn.source) : !sources.has(fn.source)) return;
805
+ const arg = call.arguments[at];
806
+ return arg && arg.type !== "SpreadElement" ? arg : void 0;
807
+ }
808
+ /**
809
+ * The classes of a compiled style written as `style=`: a `css()` call or a
810
+ * binding of one (through Vue's `_unref(x)` and `$setup.x`), undefined for
811
+ * anything else, which the dev check reads at runtime (vite/attributes.ts).
812
+ */
813
+ styleClasses(node) {
814
+ const inner = unwrap(node);
815
+ const name = inner.type === "CallExpression" ? runtimeName(this.root, inner.callee) : void 0;
816
+ if (!(inner.type === "Identifier" || inner.type === "MemberExpression" || name === "css" || name === "theme" || inner.type === "CallExpression" && isUnref(this.root, inner.callee, inner.arguments))) return void 0;
817
+ const value = attempt(() => evaluate(this.setupValue(inner) ?? inner, this.root.ctx));
818
+ return value?.result instanceof CompiledStyle ? value.result.classes : void 0;
819
+ }
820
+ /**
821
+ * Vue's dev output: the script's value that a separate render function
822
+ * reads as `$setup.x`, which `setup()` returns in `__returned__`
823
+ * (`{ card, … }`). A build inlines the template, where `x` is the binding.
824
+ */
825
+ setupValue(node) {
826
+ if (!this.root.vue || node.type !== "MemberExpression") return void 0;
827
+ const object = unwrap(node.object);
828
+ const key = memberKey(node);
829
+ if (object.type !== "Identifier" || object.name !== "$setup" && object.name !== "_ctx") return;
830
+ let found;
831
+ walk(this.root, this.root.program, (child) => {
832
+ if (found) return false;
833
+ if (child.type !== "VariableDeclarator" || child.id.type !== "Identifier" || child.id.name !== "__returned__" || child.init?.type !== "ObjectExpression") return true;
834
+ for (const p of child.init.properties) if (p.type === "Property" && !p.computed && p.kind === "init" && patternKey(p) === key) found = p.value;
835
+ return false;
836
+ });
837
+ return found;
838
+ }
839
+ /**
840
+ * Writes a `style=` value `value` as an inline style in `shape` (`empty`
841
+ * where every property moved to classes) and merges the classes into
842
+ * `site`. Returns whether the value was a compiled style.
843
+ */
844
+ writeStyle(value, shape, empty, site) {
845
+ const classes = this.styleClasses(value);
846
+ if (classes === void 0) return false;
847
+ const split = splitStyle(classes, (cls) => this.atomIndex.get(cls));
848
+ const text = split.declarations.length > 0 ? inlineStyle(split.declarations, shape) : empty;
849
+ this.replace(this.pass.edits, value.start, value.end, text);
850
+ this.pass.skip.add(value);
851
+ if (split.classes.length > 0) this.mergeClasses(site, split.classes.join(" "));
852
+ return true;
853
+ }
854
+ /** `classes` (a class list) merged into an element's class, last, as `sx(own, classes)` merges. */
855
+ mergeClasses(site, classes) {
856
+ const { edits } = this.pass;
857
+ const emitted = (list) => {
858
+ for (const cls of list) if (cls.startsWith("!")) this.names.add(cls.slice(1));
859
+ else this.pass.folded.add(cls);
860
+ return list.map(this.emit).join(" ");
861
+ };
862
+ if (site.kind === "none") {
863
+ edits.insert(site.at, site.write(emitted(mergeClassNames([classes]))));
864
+ if (site.close) edits.insert(site.close.at, site.close.text);
865
+ return;
866
+ }
867
+ if (site.kind === "static") {
868
+ const own = splitClasses(site.text).map((cls) => "!" + cls).join(" ");
869
+ const merged = emitted(mergeClassNames([own, classes]));
870
+ this.replace(edits, site.start, site.end, site.write(merged));
871
+ return;
872
+ }
873
+ const known = this.known(site.node);
874
+ if (known !== void 0) {
875
+ this.replace(edits, site.node.start, site.node.end, JSON.stringify(emitted(mergeClassNames([known, classes]))));
876
+ this.pass.skip.add(site.node);
877
+ return;
878
+ }
879
+ for (const cls of splitClasses(classes)) this.pass.folded.add(cls);
880
+ const record = this.moduleConstant(styleLiteral(toRecord(classes), this.emit));
881
+ edits.insert(site.node.start, `${this.helper("__vxClassList")}(`);
882
+ edits.insert(site.node.end, `, ${record})`);
883
+ }
884
+ /** `text` as a module-level constant after the imports, by its name; inline when there are none. */
885
+ moduleConstant(text) {
886
+ const imports = this.root.program.body.filter((node) => node.type === "ImportDeclaration");
887
+ if (imports.length === 0) return text;
888
+ const local = this.hoistName("__vxClasses");
889
+ this.pass.edits.insert(imports.at(-1).end, this.options.keepLines ? `;const ${local} = ${text};` : `\nconst ${local} = ${text};`);
890
+ return local;
891
+ }
892
+ /** JSX: `style={…}` and the element's `className` or `class` attribute. */
893
+ jsxStyle(element) {
894
+ const named = (...names) => element.attributes.find((attr) => attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && names.includes(attr.name.name));
895
+ const style = named("style");
896
+ if (style?.value?.type !== "JSXExpressionContainer") return;
897
+ const value = style.value.expression;
898
+ if (value.type === "JSXEmptyExpression") return;
899
+ const own = named("className", "class");
900
+ let site;
901
+ if (!own) {
902
+ const attribute = this.shape.attribute;
903
+ site = {
904
+ kind: "none",
905
+ at: style.end,
906
+ write: (c) => ` ${attribute}={${JSON.stringify(c)}}`
907
+ };
908
+ } else if (own.value?.type === "Literal" && typeof own.value.value === "string") site = {
909
+ kind: "static",
910
+ start: own.value.start,
911
+ end: own.value.end,
912
+ text: own.value.value,
913
+ write: (c) => `{${JSON.stringify(c)}}`
914
+ };
915
+ else if (own.value?.type === "JSXExpressionContainer" && own.value.expression.type !== "JSXEmptyExpression") site = {
916
+ kind: "expr",
917
+ node: own.value.expression
918
+ };
919
+ else return;
920
+ this.writeStyle(value, this.shape.inline, "undefined", site);
921
+ }
922
+ /**
923
+ * Vue's render function: `{ class: …, style: _normalizeStyle(x) }`, the
924
+ * vnode's props, with the class as text or `_normalizeClass(…)`.
925
+ */
926
+ vueStyle(props) {
927
+ const named = (name) => props.properties.find((p) => p.type === "Property" && !p.computed && (p.key.type === "Identifier" && p.key.name === name || p.key.type === "Literal" && p.key.value === name));
928
+ const style = named("style");
929
+ const value = style && this.argumentOf(style.value, VUE, ["normalizeStyle"]);
930
+ if (!style || !value) return;
931
+ const own = named("class");
932
+ let site;
933
+ if (!own) site = {
934
+ kind: "none",
935
+ at: style.start,
936
+ write: (c) => `class: ${JSON.stringify(c)}, `
937
+ };
938
+ else if (own.value.type === "Literal" && typeof own.value.value === "string") {
939
+ const literal = own.value;
940
+ site = {
941
+ kind: "static",
942
+ start: literal.start,
943
+ end: literal.end,
944
+ text: literal.value,
945
+ write: (c) => JSON.stringify(c)
946
+ };
947
+ } else site = {
948
+ kind: "expr",
949
+ node: this.argumentOf(own.value, VUE, ["normalizeClass"]) ?? own.value
950
+ };
951
+ this.writeStyle(value, "string", "undefined", site);
952
+ }
953
+ /**
954
+ * Svelte's client output: `$.set_style(node, x)`, with the same node's
955
+ * `$.set_class(node, 1, …)` when the class is dynamic; a static class
956
+ * lives in the template's HTML, which `classList.add` keeps.
957
+ */
958
+ svelteStyle(call) {
959
+ const value = this.argumentOf(call, SVELTE, ["set_style"], 1);
960
+ const [element] = call.arguments;
961
+ if (!value || element?.type !== "Identifier") return;
962
+ const binding = this.root.scopes.bindingOf(element);
963
+ const own = binding && this.svelteClasses().get(binding);
964
+ const name = element.name;
965
+ const site = own ? {
966
+ kind: "expr",
967
+ node: own
968
+ } : {
969
+ kind: "none",
970
+ at: call.start,
971
+ write: (c) => `(${name}.classList.add(...${JSON.stringify(c)}.split(" ")), `,
972
+ close: {
973
+ at: call.end,
974
+ text: ")"
975
+ }
976
+ };
977
+ this.writeStyle(value, "string", "undefined", site);
978
+ }
979
+ /**
980
+ * Each node's class in Svelte's client output, by the node's binding: the
981
+ * value its `$.set_class(node, 1, …)` sets (inside `$.clsx(…)`). Read
982
+ * once per pass, for every `$.set_style` of the module.
983
+ */
984
+ svelteClasses() {
985
+ if (this.pass.svelteClasses) return this.pass.svelteClasses;
986
+ const { scopes } = this.root;
987
+ const out = /* @__PURE__ */ new Map();
988
+ walk(this.root, this.root.program, (node) => {
989
+ if (node.type !== "CallExpression") return true;
990
+ const target = node.arguments[0];
991
+ const cls = this.argumentOf(node, SVELTE, ["set_class"], 2);
992
+ const binding = target?.type === "Identifier" ? scopes.bindingOf(target) : void 0;
993
+ if (cls && binding && !out.has(binding)) out.set(binding, this.argumentOf(cls, SVELTE, ["clsx"]) ?? cls);
994
+ return true;
995
+ });
996
+ return this.pass.svelteClasses = out;
997
+ }
998
+ /**
999
+ * A template literal's tags: `style="${x}"` (plain JS, Vue's server
1000
+ * output through `_ssrRenderStyle(x)`), `${$.attr_style(x)}` (Svelte's
1001
+ * server output) and `${$$addAttribute(x, "style")}` (Astro), with the
1002
+ * tag's class written in the same three ways or as text.
1003
+ */
1004
+ templateStyles(template) {
1005
+ const { quasis, expressions } = template;
1006
+ if (expressions.length === 0) return;
1007
+ const raws = quasis.map((quasi) => quasi.value.raw);
1008
+ const shift = quasis[0].start === template.start ? 1 : 0;
1009
+ const startOf = (k) => quasis[k].start + shift;
1010
+ const ATTRIBUTE = /\s(style|class)=("?)([^"<>]*)$/;
1011
+ /** An expression's attribute, as the text before it or the call around it names it. */
1012
+ const attributeOf = (i) => {
1013
+ const expression = expressions[i];
1014
+ const before = ATTRIBUTE.exec(raws[i]);
1015
+ if (before) {
1016
+ const quoted = before[2] === "\"";
1017
+ if (before[1] === "style" && (before[3] !== "" || quoted && raws[i + 1][0] !== "\"")) return;
1018
+ const name = before[1];
1019
+ return {
1020
+ name,
1021
+ value: this.argumentOf(expression, VUE, name === "style" ? ["ssrRenderStyle"] : ["ssrRenderClass"]) ?? expression,
1022
+ quoted
1023
+ };
1024
+ }
1025
+ const svelte = this.argumentOf(expression, SVELTE, ["attr_style"]);
1026
+ if (svelte) return {
1027
+ name: "style",
1028
+ value: svelte,
1029
+ quoted: false
1030
+ };
1031
+ const svelteClass = this.argumentOf(expression, SVELTE, ["attr_class"]);
1032
+ if (svelteClass) return {
1033
+ name: "class",
1034
+ value: this.argumentOf(svelteClass, SVELTE, ["clsx"]) ?? svelteClass,
1035
+ quoted: false
1036
+ };
1037
+ const call = unwrap(expression);
1038
+ const astro = this.argumentOf(expression, ASTRO, ["addAttribute"]);
1039
+ const key = call.type === "CallExpression" ? call.arguments[1] : void 0;
1040
+ if (astro && key?.type === "Literal" && (key.value === "style" || key.value === "class")) return {
1041
+ name: key.value,
1042
+ value: astro,
1043
+ quoted: false
1044
+ };
1045
+ };
1046
+ for (let i = 0; i < expressions.length; i++) {
1047
+ const style = attributeOf(i);
1048
+ if (style?.name !== "style") continue;
1049
+ let first = i;
1050
+ while (first > 0 && !raws[first].includes("<")) first--;
1051
+ let last = i + 1;
1052
+ while (last < raws.length - 1 && !raws[last].includes(">")) last++;
1053
+ const from = raws[first].lastIndexOf("<");
1054
+ const to = raws[last].indexOf(">");
1055
+ let site;
1056
+ for (let j = first; j < last && !site; j++) {
1057
+ const attribute = j === i ? void 0 : attributeOf(j);
1058
+ if (attribute?.name === "class") site = {
1059
+ kind: "expr",
1060
+ node: attribute.value
1061
+ };
1062
+ }
1063
+ for (let k = first; k <= last && !site; k++) {
1064
+ const text = raws[k];
1065
+ const start = k === first ? from : 0;
1066
+ const end = k === last && to !== -1 ? to : text.length;
1067
+ const match = /\sclass="([^"]*)"/.exec(text.slice(start, end));
1068
+ if (match) {
1069
+ const at = startOf(k) + start + match.index + match[0].indexOf("\"") + 1;
1070
+ site = {
1071
+ kind: "static",
1072
+ start: at,
1073
+ end: at + match[1].length,
1074
+ text: match[1],
1075
+ write: (c) => templateText(c)
1076
+ };
1077
+ }
1078
+ }
1079
+ const at = startOf(i + 1) + (style.quoted ? 1 : 0);
1080
+ site ??= {
1081
+ kind: "none",
1082
+ at,
1083
+ write: (c) => ` class="${templateText(c)}"`
1084
+ };
1085
+ if (site.kind === "static" && site.start === site.end) site = {
1086
+ kind: "none",
1087
+ at: site.start,
1088
+ write: (c) => templateText(c)
1089
+ };
1090
+ const empty = style.value === expressions[i] ? "\"\"" : "undefined";
1091
+ this.writeStyle(style.value, "string", empty, site);
1092
+ }
1093
+ }
658
1094
  /** Whether `offset` lies in a function (or a class field), which may run many times. */
659
1095
  inFunction(offset) {
660
1096
  const { root } = this;
@@ -671,12 +1107,12 @@ var Compiler = class {
671
1107
  }
672
1108
  return root.functions.some(([start, end]) => offset > start && offset < end);
673
1109
  }
674
- /** A module-level name for a hoisted recipe, unused in the module. */
675
- hoistName() {
1110
+ /** A module-level name for a hoisted recipe (or `base`), unused in the module. */
1111
+ hoistName(base = "__vxRecipeTable") {
676
1112
  const { code } = this.root;
677
1113
  let name;
678
1114
  do
679
- name = `__vxRecipeTable${++this.pass.hoisted}`;
1115
+ name = `${base}${++this.pass.hoisted}`;
680
1116
  while (code.includes(name));
681
1117
  return name;
682
1118
  }
@@ -727,7 +1163,7 @@ var Compiler = class {
727
1163
  const classes = resolveRecipe(recipe, props.build(picks), fail);
728
1164
  const styles = slots ? Object.values(classes) : [classes];
729
1165
  for (const style of styles) for (const cls of Object.values(style)) this.pass.folded.add(cls);
730
- leaves.push(slots ? Object.fromEntries(slots.map((slot) => [slot, styleLiteral(classes[slot], this.emit)])) : { "": styleLiteral(classes, this.emit) });
1166
+ leaves.push(slots ? Object.fromEntries(slots.map((slot) => [slot, this.styled(styleLiteral(classes[slot], this.emit))])) : { "": this.styled(styleLiteral(classes, this.emit)) });
731
1167
  }
732
1168
  const tests = props.tests.map((test) => this.testText(test));
733
1169
  const choose = (texts, depth = 0, at = 0) => {
@@ -828,7 +1264,7 @@ var Compiler = class {
828
1264
  const style = this.compileStyle(this.root, call.callee, call.arguments);
829
1265
  const { fn, vars } = style;
830
1266
  if (!fn || !vars) {
831
- this.replace(editor, call.start, call.end, `(${styleLiteral(style.classes, this.emit)})`);
1267
+ this.replace(editor, call.start, call.end, this.styled(styleLiteral(style.classes, this.emit)));
832
1268
  return;
833
1269
  }
834
1270
  const constant = this.localName("__vxStyle");
@@ -848,7 +1284,16 @@ var Compiler = class {
848
1284
  body = `((${picks.join(", ")}) => (${style$}))(${palettes.map((p) => this.paletteClass(p.prefix, p.value)).join(", ")})`;
849
1285
  }
850
1286
  this.replace(editor, fn.body.start, fn.body.end, body);
851
- this.replace(editor, fn.end, call.end, `)(${styleLiteral(style.classes, this.emit)})`);
1287
+ this.replace(editor, fn.end, call.end, `)(${this.styled(styleLiteral(style.classes, this.emit))})`);
1288
+ }
1289
+ /**
1290
+ * A compiled style literal as the output writes it: on the prototype
1291
+ * every compiled style shares, whose `toString` is its class attribute
1292
+ * (runtime/style.ts), so `class={card}` works. One call per constant, a
1293
+ * few bytes, and pure, so an unused constant still drops.
1294
+ */
1295
+ styled(literal) {
1296
+ return `/*#__PURE__*/${this.helper("__vxStyled")}(${literal})`;
852
1297
  }
853
1298
  /** A name for a binding the output adds, unused in the module. */
854
1299
  localName(base) {
@@ -2027,7 +2472,9 @@ function newPass(code, keepLines) {
2027
2472
  helpers: /* @__PURE__ */ new Map(),
2028
2473
  hoisted: 0,
2029
2474
  devSx: false,
2030
- errors: []
2475
+ errors: [],
2476
+ skip: /* @__PURE__ */ new Set(),
2477
+ objectStyles: /* @__PURE__ */ new Set()
2031
2478
  };
2032
2479
  }
2033
2480
  function notSearched(source) {