@aihu/css-engine 0.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.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +122 -0
  3. package/crates/aihu-css-core/Cargo.toml +22 -0
  4. package/crates/aihu-css-core/src/ast.rs +173 -0
  5. package/crates/aihu-css-core/src/bin/main.rs +73 -0
  6. package/crates/aihu-css-core/src/cache.rs +182 -0
  7. package/crates/aihu-css-core/src/emit.rs +236 -0
  8. package/crates/aihu-css-core/src/features/anchor.rs +41 -0
  9. package/crates/aihu-css-core/src/features/mod.rs +33 -0
  10. package/crates/aihu-css-core/src/features/popover.rs +40 -0
  11. package/crates/aihu-css-core/src/features/text_balance.rs +36 -0
  12. package/crates/aihu-css-core/src/features/view_transition.rs +38 -0
  13. package/crates/aihu-css-core/src/lib.rs +67 -0
  14. package/crates/aihu-css-core/src/progressive.rs +200 -0
  15. package/crates/aihu-css-core/src/scanner.rs +235 -0
  16. package/crates/aihu-css-core/src/theme.rs +179 -0
  17. package/crates/aihu-css-core/src/tokens.rs +470 -0
  18. package/crates/aihu-css-core/src/variants.rs +124 -0
  19. package/crates/aihu-css-core/tests/cache.rs +71 -0
  20. package/crates/aihu-css-core/tests/emit.rs +148 -0
  21. package/crates/aihu-css-core/tests/fixtures/button.ast.json +19 -0
  22. package/crates/aihu-css-core/tests/progressive_snapshot.rs +102 -0
  23. package/crates/aihu-css-core/tests/scanner.rs +99 -0
  24. package/crates/aihu-css-core/tests/scoped_snapshot.rs +73 -0
  25. package/crates/aihu-css-core/tests/snapshot.rs +24 -0
  26. package/crates/aihu-css-core/tests/snapshots/progressive_snapshot__anchor_snapshot.snap +26 -0
  27. package/crates/aihu-css-core/tests/snapshots/progressive_snapshot__popover_snapshot.snap +26 -0
  28. package/crates/aihu-css-core/tests/snapshots/progressive_snapshot__text_balance_snapshot.snap +23 -0
  29. package/crates/aihu-css-core/tests/snapshots/progressive_snapshot__view_transition_snapshot.snap +25 -0
  30. package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__flat_output_for_class_list.snap +6 -0
  31. package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_output_for_sfc.snap +25 -0
  32. package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_with_authored_style_block.snap +26 -0
  33. package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_with_global_style_block.snap +24 -0
  34. package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__standard_variants.snap +33 -0
  35. package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__theme_default_vs_override.snap +45 -0
  36. package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__wc_native_variants.snap +28 -0
  37. package/crates/aihu-css-core/tests/snapshots/snapshot__compiles_basic_class.snap +5 -0
  38. package/crates/aihu-css-core/tests/snapshots/snapshot__compiles_multiple_classes.snap +8 -0
  39. package/crates/aihu-css-core/tests/snapshots/tokens__arbitrary_values.snap +9 -0
  40. package/crates/aihu-css-core/tests/snapshots/tokens__category_borders.snap +8 -0
  41. package/crates/aihu-css-core/tests/snapshots/tokens__category_colors.snap +10 -0
  42. package/crates/aihu-css-core/tests/snapshots/tokens__category_effects.snap +8 -0
  43. package/crates/aihu-css-core/tests/snapshots/tokens__category_layout.snap +12 -0
  44. package/crates/aihu-css-core/tests/snapshots/tokens__category_spacing.snap +11 -0
  45. package/crates/aihu-css-core/tests/snapshots/tokens__category_typography.snap +11 -0
  46. package/crates/aihu-css-core/tests/tokens.rs +79 -0
  47. package/dist/index.d.ts +76 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +120 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/runtime/cn.d.ts +14 -0
  52. package/dist/runtime/cn.d.ts.map +1 -0
  53. package/dist/runtime/cn.js +107 -0
  54. package/dist/runtime/cn.js.map +1 -0
  55. package/dist/runtime/progressive.d.ts +54 -0
  56. package/dist/runtime/progressive.d.ts.map +1 -0
  57. package/dist/runtime/progressive.js +132 -0
  58. package/dist/runtime/progressive.js.map +1 -0
  59. package/package.json +54 -0
  60. package/styles/aihu-default.css +73 -0
  61. package/styles/aihu-graphite.css +71 -0
@@ -0,0 +1,33 @@
1
+ ---
2
+ source: packages/css-engine/crates/aihu-css-core/tests/scoped_snapshot.rs
3
+ expression: "compile_sfc_scoped(&sfc(\"hover:bg-primary focus:text-accent dark:bg-surface md:p-8 [&>div]:text-primary md:hover:bg-primary\"))"
4
+ ---
5
+ :host {
6
+ --color-accent: #c8543a;
7
+ --color-accent-foreground: #faf8f4;
8
+ --color-background: #faf8f4;
9
+ --color-border: #ddd9d2;
10
+ --color-destructive: #a8432b;
11
+ --color-destructive-foreground: #faf8f4;
12
+ --color-foreground: #1a1d24;
13
+ --color-muted: #5a5a55;
14
+ --color-muted-foreground: #8a8880;
15
+ --color-primary: #1a1d24;
16
+ --color-primary-foreground: #faf8f4;
17
+ --color-ring: #c8543a;
18
+ --color-secondary: #5a5a55;
19
+ --color-secondary-foreground: #faf8f4;
20
+ --color-surface: #faf8f4;
21
+ --color-surface-foreground: #1a1d24;
22
+ }
23
+ .\[&>div\]\:text-primary>div { color: var(--color-primary); }
24
+ /* dark cascade (Firefox-safe; see decision-firefox-host-context-workaround) */
25
+ :host([data-theme="dark"]) .dark\:bg-surface, :root.dark .dark\:bg-surface { background-color: var(--color-surface); }
26
+ .focus\:text-accent:focus { color: var(--color-accent); }
27
+ .hover\:bg-primary:hover { background-color: var(--color-primary); }
28
+ @media (min-width: 48rem) {
29
+ .md\:hover\:bg-primary:hover { background-color: var(--color-primary); }
30
+ }
31
+ @media (min-width: 48rem) {
32
+ .md\:p-8 { padding: 2rem; }
33
+ }
@@ -0,0 +1,45 @@
1
+ ---
2
+ source: packages/css-engine/crates/aihu-css-core/tests/scoped_snapshot.rs
3
+ expression: "format!(\"--- default ---\\n{default}\\n--- override ---\\n{overridden}\")"
4
+ ---
5
+ --- default ---
6
+ :host {
7
+ --color-accent: #c8543a;
8
+ --color-accent-foreground: #faf8f4;
9
+ --color-background: #faf8f4;
10
+ --color-border: #ddd9d2;
11
+ --color-destructive: #a8432b;
12
+ --color-destructive-foreground: #faf8f4;
13
+ --color-foreground: #1a1d24;
14
+ --color-muted: #5a5a55;
15
+ --color-muted-foreground: #8a8880;
16
+ --color-primary: #1a1d24;
17
+ --color-primary-foreground: #faf8f4;
18
+ --color-ring: #c8543a;
19
+ --color-secondary: #5a5a55;
20
+ --color-secondary-foreground: #faf8f4;
21
+ --color-surface: #faf8f4;
22
+ --color-surface-foreground: #1a1d24;
23
+ }
24
+ .bg-primary { background-color: var(--color-primary); }
25
+
26
+ --- override ---
27
+ :host {
28
+ --color-accent: #c8543a;
29
+ --color-accent-foreground: #faf8f4;
30
+ --color-background: #faf8f4;
31
+ --color-border: #ddd9d2;
32
+ --color-destructive: #a8432b;
33
+ --color-destructive-foreground: #faf8f4;
34
+ --color-foreground: #1a1d24;
35
+ --color-muted: #5a5a55;
36
+ --color-muted-foreground: #8a8880;
37
+ --color-primary: oklch(0.55 0.18 28);
38
+ --color-primary-foreground: #faf8f4;
39
+ --color-ring: #c8543a;
40
+ --color-secondary: #5a5a55;
41
+ --color-secondary-foreground: #faf8f4;
42
+ --color-surface: #faf8f4;
43
+ --color-surface-foreground: #1a1d24;
44
+ }
45
+ .bg-primary { background-color: var(--color-primary); }
@@ -0,0 +1,28 @@
1
+ ---
2
+ source: packages/css-engine/crates/aihu-css-core/tests/scoped_snapshot.rs
3
+ expression: "compile_sfc_scoped(&sfc(\"host:bg-primary slotted:p-4 slotted-img:rounded-lg part-thumb:bg-accent host-context-dark:bg-surface\"))"
4
+ ---
5
+ :host {
6
+ --color-accent: #c8543a;
7
+ --color-accent-foreground: #faf8f4;
8
+ --color-background: #faf8f4;
9
+ --color-border: #ddd9d2;
10
+ --color-destructive: #a8432b;
11
+ --color-destructive-foreground: #faf8f4;
12
+ --color-foreground: #1a1d24;
13
+ --color-muted: #5a5a55;
14
+ --color-muted-foreground: #8a8880;
15
+ --color-primary: #1a1d24;
16
+ --color-primary-foreground: #faf8f4;
17
+ --color-ring: #c8543a;
18
+ --color-secondary: #5a5a55;
19
+ --color-secondary-foreground: #faf8f4;
20
+ --color-surface: #faf8f4;
21
+ --color-surface-foreground: #1a1d24;
22
+ }
23
+ /* dark cascade (Firefox-safe; see decision-firefox-host-context-workaround) */
24
+ :host([data-theme="dark"]) .host-context-dark\:bg-surface, :root.dark .host-context-dark\:bg-surface { background-color: var(--color-surface); }
25
+ :host(.host\:bg-primary) { background-color: var(--color-primary); }
26
+ ::part(thumb) { background-color: var(--color-accent); }
27
+ ::slotted(img.slotted-img\:rounded-lg) { border-radius: 0.5rem; }
28
+ ::slotted(.slotted\:p-4) { padding: 1rem; }
@@ -0,0 +1,5 @@
1
+ ---
2
+ source: tests/snapshot.rs
3
+ expression: output
4
+ ---
5
+ .bg-primary { background-color: var(--color-primary); }
@@ -0,0 +1,8 @@
1
+ ---
2
+ source: tests/snapshot.rs
3
+ expression: output
4
+ ---
5
+ .bg-primary { background-color: var(--color-primary); }
6
+ .text-primary-foreground { color: var(--color-primary-foreground); }
7
+ .rounded-md { border-radius: 0.375rem; }
8
+ .p-4 { padding: 1rem; }
@@ -0,0 +1,9 @@
1
+ ---
2
+ source: packages/css-engine/crates/aihu-css-core/tests/tokens.rs
3
+ expression: "css(&[\"bg-[#1a1d24]\", \"w-[34ch]\", \"text-[14px]\", \"p-[2.5rem]\",\n\"leading-[1.4]\",])"
4
+ ---
5
+ .bg-[#1a1d24] { background-color: #1a1d24; }
6
+ .w-[34ch] { width: 34ch; }
7
+ .text-[14px] { color: 14px; }
8
+ .p-[2.5rem] { padding: 2.5rem; }
9
+ .leading-[1.4] { line-height: 1.4; }
@@ -0,0 +1,8 @@
1
+ ---
2
+ source: packages/css-engine/crates/aihu-css-core/tests/tokens.rs
3
+ expression: "css(&[\"border\", \"rounded\", \"rounded-lg\", \"rounded-full\",])"
4
+ ---
5
+ .border { border-width: 1px; }
6
+ .rounded { border-radius: 0.25rem; }
7
+ .rounded-lg { border-radius: 0.5rem; }
8
+ .rounded-full { border-radius: 9999px; }
@@ -0,0 +1,10 @@
1
+ ---
2
+ source: packages/css-engine/crates/aihu-css-core/tests/tokens.rs
3
+ expression: "css(&[\"bg-primary\", \"text-accent\", \"border-muted\", \"bg-red-500\",\n\"text-slate-700\", \"bg-white\",])"
4
+ ---
5
+ .bg-primary { background-color: var(--color-primary); }
6
+ .text-accent { color: var(--color-accent); }
7
+ .border-muted { border-color: var(--color-muted); }
8
+ .bg-red-500 { background-color: var(--color-red-500); }
9
+ .text-slate-700 { color: var(--color-slate-700); }
10
+ .bg-white { background-color: #fff; }
@@ -0,0 +1,8 @@
1
+ ---
2
+ source: packages/css-engine/crates/aihu-css-core/tests/tokens.rs
3
+ expression: "css(&[\"shadow\", \"shadow-lg\", \"shadow-none\", \"opacity-50\"])"
4
+ ---
5
+ .shadow { box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1); }
6
+ .shadow-lg { box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1); }
7
+ .shadow-none { box-shadow: none; }
8
+ .opacity-50 { opacity: 0.5; }
@@ -0,0 +1,12 @@
1
+ ---
2
+ source: packages/css-engine/crates/aihu-css-core/tests/tokens.rs
3
+ expression: "css(&[\"flex\", \"grid\", \"hidden\", \"items-center\", \"justify-between\", \"absolute\",\n\"w-full\", \"w-1/2\",])"
4
+ ---
5
+ .flex { display: flex; }
6
+ .grid { display: grid; }
7
+ .hidden { display: none; }
8
+ .items-center { align-items: center; }
9
+ .justify-between { justify-content: space-between; }
10
+ .absolute { position: absolute; }
11
+ .w-full { width: 100%; }
12
+ .w-1/2 { width: 50%; }
@@ -0,0 +1,11 @@
1
+ ---
2
+ source: packages/css-engine/crates/aihu-css-core/tests/tokens.rs
3
+ expression: "css(&[\"p-4\", \"px-2\", \"py-8\", \"m-0\", \"mt-1\", \"gap-2\", \"p-0.5\"])"
4
+ ---
5
+ .p-4 { padding: 1rem; }
6
+ .px-2 { padding-inline: 0.5rem; }
7
+ .py-8 { padding-block: 2rem; }
8
+ .m-0 { margin: 0; }
9
+ .mt-1 { margin-top: 0.25rem; }
10
+ .gap-2 { gap: 0.5rem; }
11
+ .p-0.5 { padding: 0.125rem; }
@@ -0,0 +1,11 @@
1
+ ---
2
+ source: packages/css-engine/crates/aihu-css-core/tests/tokens.rs
3
+ expression: "css(&[\"text-lg\", \"text-center\", \"font-bold\", \"italic\", \"underline\",\n\"uppercase\", \"truncate\",])"
4
+ ---
5
+ .text-lg { font-size: 1.125rem; line-height: 1.75rem; }
6
+ .text-center { text-align: center; }
7
+ .font-bold { font-weight: 700; }
8
+ .italic { font-style: italic; }
9
+ .underline { text-decoration-line: underline; }
10
+ .uppercase { text-transform: uppercase; }
11
+ .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -0,0 +1,79 @@
1
+ //! Tailwind v4 utility-table coverage — one snapshot per category plus
2
+ //! arbitrary-value bracket syntax. Proves the table compiles all six
3
+ //! categories (Plan 2 Task 3).
4
+
5
+ use aihu_css_core::compile_classes;
6
+
7
+ fn css(classes: &[&str]) -> String {
8
+ compile_classes(&classes.iter().map(|s| s.to_string()).collect::<Vec<_>>())
9
+ }
10
+
11
+ #[test]
12
+ fn category_colors() {
13
+ insta::assert_snapshot!(css(&[
14
+ "bg-primary",
15
+ "text-accent",
16
+ "border-muted",
17
+ "bg-red-500",
18
+ "text-slate-700",
19
+ "bg-white",
20
+ ]));
21
+ }
22
+
23
+ #[test]
24
+ fn category_spacing() {
25
+ insta::assert_snapshot!(css(&["p-4", "px-2", "py-8", "m-0", "mt-1", "gap-2", "p-0.5"]));
26
+ }
27
+
28
+ #[test]
29
+ fn category_layout() {
30
+ insta::assert_snapshot!(css(&[
31
+ "flex",
32
+ "grid",
33
+ "hidden",
34
+ "items-center",
35
+ "justify-between",
36
+ "absolute",
37
+ "w-full",
38
+ "w-1/2",
39
+ ]));
40
+ }
41
+
42
+ #[test]
43
+ fn category_typography() {
44
+ insta::assert_snapshot!(css(&[
45
+ "text-lg",
46
+ "text-center",
47
+ "font-bold",
48
+ "italic",
49
+ "underline",
50
+ "uppercase",
51
+ "truncate",
52
+ ]));
53
+ }
54
+
55
+ #[test]
56
+ fn category_borders() {
57
+ insta::assert_snapshot!(css(&[
58
+ "border",
59
+ "rounded",
60
+ "rounded-lg",
61
+ "rounded-full",
62
+ ]));
63
+ }
64
+
65
+ #[test]
66
+ fn category_effects() {
67
+ insta::assert_snapshot!(css(&["shadow", "shadow-lg", "shadow-none", "opacity-50"]));
68
+ }
69
+
70
+ #[test]
71
+ fn arbitrary_values() {
72
+ insta::assert_snapshot!(css(&[
73
+ "bg-[#1a1d24]",
74
+ "w-[34ch]",
75
+ "text-[14px]",
76
+ "p-[2.5rem]",
77
+ "leading-[1.4]",
78
+ ]));
79
+ }
@@ -0,0 +1,76 @@
1
+ //#region src/define-style-pack.d.ts
2
+ /**
3
+ * `defineStylePack()` — the export hook for external orgs (Plan 3 Task 10).
4
+ *
5
+ * Lets an external org declare its own token bundle against the SAME token-name
6
+ * contract as the built-in `aihu-default` / `aihu-graphite` packs (see
7
+ * `styles/*.css`), so a custom pack slots into the engine exactly like the
8
+ * built-ins. Returns a `StylePack` descriptor the engine can register and emit
9
+ * as `:root` / `.dark` token blocks.
10
+ *
11
+ * The built-in packs are expressible through this same API — `defineStylePack`
12
+ * is just the typed, programmatic form of the shipped CSS bundles.
13
+ */
14
+ /** A design-token map: `name` → CSS value. Names omit the leading `--`. */
15
+ type TokenMap = Record<string, string>;
16
+ /** Input to {@link defineStylePack}. */
17
+ interface StylePackInput {
18
+ /** Pack name, e.g. `'acme'` (used for registration / debugging). */
19
+ name: string;
20
+ /** Light-theme tokens (the `:root` block). Names without the `--` prefix. */
21
+ tokens: TokenMap;
22
+ /** Optional dark-theme overrides (the `.dark` block). */
23
+ dark?: TokenMap;
24
+ }
25
+ /** A registered, validated style-pack descriptor. */
26
+ interface StylePack {
27
+ readonly name: string;
28
+ readonly tokens: TokenMap;
29
+ readonly dark: TokenMap;
30
+ /**
31
+ * Serialize the pack to a `:root { … }` (+ `.dark { … }`) CSS string — the
32
+ * same shape as the shipped `styles/*.css` bundles.
33
+ */
34
+ toCss(): string;
35
+ }
36
+ /**
37
+ * Define a style pack from a token map.
38
+ *
39
+ * @example
40
+ * const acme = defineStylePack({
41
+ * name: 'acme',
42
+ * tokens: { 'color-primary': '#0a7', 'radius-md': '6px' },
43
+ * dark: { 'color-primary': '#3fc' },
44
+ * })
45
+ * acme.toCss() // => ":root { --color-primary: #0a7; … } .dark { … }"
46
+ */
47
+ declare function defineStylePack(input: StylePackInput): StylePack;
48
+ //#endregion
49
+ //#region src/index.d.ts
50
+ /**
51
+ * Compile a list of utility class names to CSS.
52
+ *
53
+ * Plan 1 bootstrap — supports a hardcoded subset; see crates/aihu-css-core/src/tokens.rs.
54
+ * Plan 2 wires the AST scanner so callers pass `.aihu` SFC ASTs instead of raw class lists.
55
+ *
56
+ * @param classes - utility class names like `['bg-primary', 'p-4']`
57
+ * @returns CSS string with one rule per known class
58
+ */
59
+ declare function compile(classes: string[]): string;
60
+ /**
61
+ * Compile a `.aihu` SFC source string to scoped, shadow-DOM-embedded CSS.
62
+ *
63
+ * Pipeline (Plan 2 Task 9): `compileToAst(source)` (from `@aihu/compiler`)
64
+ * → AST JSON → `aihu-css-compile --ast-json` → scoped CSS. The output is the
65
+ * per-SFC stylesheet the compiler folds into the component's shadow `<style>`:
66
+ * `:host`-level theme tokens, variant-resolved utility rules, and the folded
67
+ * authored `@style` block. There is NO global utility stylesheet.
68
+ *
69
+ * @param source - the `.aihu` SFC source text
70
+ * @param id - optional file path/id (used to derive the tag stem + `@route` checks)
71
+ * @returns the scoped CSS string for the SFC
72
+ */
73
+ declare function compileSfc(source: string, id?: string): string;
74
+ //#endregion
75
+ export { type StylePack, type StylePackInput, type TokenMap, compile, compileSfc, defineStylePack };
76
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/define-style-pack.ts","../src/index.ts"],"mappings":";;AAcA;;;;;AAGA;;;;;;;KAHY,QAAA,GAAW,MAAA;;UAGN,cAAA;EAMA;EAJf,IAAA;EAQwB;EANxB,MAAA,EAAQ,QAAA;EASe;EAPvB,IAAA,GAAO,QAAA;AAAA;;UAIQ,SAAA;EAAA,SACN,IAAA;EAAA,SACA,MAAA,EAAQ,QAAA;EAAA,SACR,IAAA,EAAM,QAAA;EAKV;AA0BP;;;EA1BE,KAAA;AAAA;;;;;;;;ACUF;;;;iBDgBgB,eAAA,CAAgB,KAAA,EAAO,cAAA,GAAiB,SAAA;;;AA/CxD;;;;;AAGA;;;;AAHA,iBC+BgB,OAAA,CAAQ,OAAA;;;;;;;ADlBxB;;;;;;;iBC4CgB,UAAA,CAAW,MAAA,UAAgB,EAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,120 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { basename, dirname, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ //#region ../compiler/dist/index.js
6
+ const a = process.platform === `win32` ? `.exe` : ``, o = process.env.SCRIBE_COMPILE_BIN ?? resolve(dirname(fileURLToPath(import.meta.url)), `../bin/aihu-compile${a}`);
7
+ function m(n, r) {
8
+ let i = [
9
+ `--stdin`,
10
+ `--tag`,
11
+ r ? basename(r, `.aihu`) : `Component`,
12
+ `--ast-json`
13
+ ];
14
+ r && i.push(`--path`, r);
15
+ let a = execFileSync(o, i, {
16
+ input: n,
17
+ encoding: `utf8`
18
+ });
19
+ return JSON.parse(a);
20
+ }
21
+ //#endregion
22
+ //#region src/define-style-pack.ts
23
+ /** Normalize a token name to its `--`-prefixed custom-property form. */
24
+ function asCustomProp(name) {
25
+ return name.startsWith("--") ? name : `--${name}`;
26
+ }
27
+ function emitBlock(selector, tokens) {
28
+ return `${selector} {\n${Object.entries(tokens).map(([name, value]) => ` ${asCustomProp(name)}: ${value};`).join("\n")}\n}\n`;
29
+ }
30
+ /**
31
+ * Define a style pack from a token map.
32
+ *
33
+ * @example
34
+ * const acme = defineStylePack({
35
+ * name: 'acme',
36
+ * tokens: { 'color-primary': '#0a7', 'radius-md': '6px' },
37
+ * dark: { 'color-primary': '#3fc' },
38
+ * })
39
+ * acme.toCss() // => ":root { --color-primary: #0a7; … } .dark { … }"
40
+ */
41
+ function defineStylePack(input) {
42
+ if (!input.name) throw new Error("defineStylePack: `name` is required");
43
+ if (!input.tokens || Object.keys(input.tokens).length === 0) throw new Error(`defineStylePack("${input.name}"): tokens must be a non-empty map`);
44
+ const tokens = { ...input.tokens };
45
+ const dark = { ...input.dark ?? {} };
46
+ return {
47
+ name: input.name,
48
+ tokens,
49
+ dark,
50
+ toCss() {
51
+ let css = emitBlock(":root", tokens);
52
+ if (Object.keys(dark).length > 0) css += emitBlock(".dark", dark);
53
+ return css;
54
+ }
55
+ };
56
+ }
57
+ //#endregion
58
+ //#region src/index.ts
59
+ const __dirname = dirname(fileURLToPath(import.meta.url));
60
+ /**
61
+ * Resolve the path to the `aihu-css-compile` binary.
62
+ * Looks for it relative to the package, then in the workspace target/release.
63
+ * Plan 4 will replace this with a prebuilt binary shipped with the package.
64
+ */
65
+ function resolveBinary() {
66
+ const ext = process.platform === "win32" ? ".exe" : "";
67
+ const candidates = [resolve(__dirname, "../../../target/release", `aihu-css-compile${ext}`), resolve(__dirname, "../../../target/debug", `aihu-css-compile${ext}`)];
68
+ for (const c of candidates) if (existsSync(c)) return c;
69
+ throw new Error(`aihu-css-compile binary not found. Run \`cargo build --release -p aihu-css-core\` from the repo root first. Checked: ${candidates.join(", ")}`);
70
+ }
71
+ /**
72
+ * Compile a list of utility class names to CSS.
73
+ *
74
+ * Plan 1 bootstrap — supports a hardcoded subset; see crates/aihu-css-core/src/tokens.rs.
75
+ * Plan 2 wires the AST scanner so callers pass `.aihu` SFC ASTs instead of raw class lists.
76
+ *
77
+ * @param classes - utility class names like `['bg-primary', 'p-4']`
78
+ * @returns CSS string with one rule per known class
79
+ */
80
+ function compile(classes) {
81
+ if (classes.length === 0) return "";
82
+ return execFileSync(resolveBinary(), [], {
83
+ input: classes.join("\n"),
84
+ encoding: "utf-8",
85
+ stdio: [
86
+ "pipe",
87
+ "pipe",
88
+ "inherit"
89
+ ]
90
+ });
91
+ }
92
+ /**
93
+ * Compile a `.aihu` SFC source string to scoped, shadow-DOM-embedded CSS.
94
+ *
95
+ * Pipeline (Plan 2 Task 9): `compileToAst(source)` (from `@aihu/compiler`)
96
+ * → AST JSON → `aihu-css-compile --ast-json` → scoped CSS. The output is the
97
+ * per-SFC stylesheet the compiler folds into the component's shadow `<style>`:
98
+ * `:host`-level theme tokens, variant-resolved utility rules, and the folded
99
+ * authored `@style` block. There is NO global utility stylesheet.
100
+ *
101
+ * @param source - the `.aihu` SFC source text
102
+ * @param id - optional file path/id (used to derive the tag stem + `@route` checks)
103
+ * @returns the scoped CSS string for the SFC
104
+ */
105
+ function compileSfc(source, id) {
106
+ const ast = m(source, id);
107
+ return execFileSync(resolveBinary(), ["--ast-json"], {
108
+ input: JSON.stringify(ast),
109
+ encoding: "utf-8",
110
+ stdio: [
111
+ "pipe",
112
+ "pipe",
113
+ "inherit"
114
+ ]
115
+ });
116
+ }
117
+ //#endregion
118
+ export { compile, compileSfc, defineStylePack };
119
+
120
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["r","n","i","t","e","compileToAst"],"sources":["../../compiler/dist/index.js","../src/define-style-pack.ts","../src/index.ts"],"sourcesContent":["import{execFileSync as e}from\"node:child_process\";import{basename as t,dirname as n,resolve as r}from\"node:path\";import{fileURLToPath as i}from\"node:url\";const a=process.platform===`win32`?`.exe`:``,o=process.env.SCRIBE_COMPILE_BIN??r(n(i(import.meta.url)),`../bin/aihu-compile${a}`);function s(e,t){return e.replace(/(defineElement\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*defineComponent\\([^]*\\))\\s*\\)/,(e,n)=>`${n}, { shadowMode: '${t}' })`)}function c(e){return/\\b(?:signal|computed|effect|setSignal|onMount|onCleanup)\\s*\\(/.test(e)?`interactive`:`static`}function l(e){let t=/defineElement\\(\\s*['\"]([^'\"]+)['\"]/m.exec(e);return t?t[1]??null:null}function u(e,t){let n=e.replace(/import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/runtime'/,(e,t)=>{let n=t.split(`,`).map(e=>e.trim()).filter(Boolean);return n.includes(`_hmrReplace`)||n.push(`_hmrReplace`),`import { ${n.join(`, `)} } from '@aihu/runtime'`}).replace(/\\bdefineComponent\\(/,`defineComponent(__aihu_setup__ = `),r=`\nexport { __aihu_setup__ as default }\n\nif (typeof __DEV__ !== 'undefined' && __DEV__ && import.meta.hot) {\n import.meta.hot.accept((newModule) => {\n if (!newModule) return\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const newSetup = (newModule as any)['default']\n if (typeof newSetup !== 'function') return\n document.querySelectorAll(${JSON.stringify(t)}).forEach((el) => {\n _hmrReplace(el as HTMLElement, newSetup)\n })\n })\n}\n`;return`let __aihu_setup__: ((ctx: any) => any) | undefined\n`+n+r}function d(e,t){let n=e.replace(/import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/runtime'/,(e,t)=>{let n=t.split(`,`).map(e=>e.trim()).filter(Boolean);return n.includes(`_hydrateOnVisible`)||n.push(`_hydrateOnVisible`),`import { ${n.join(`, `)} } from '@aihu/runtime'`}),r=n.replace(/defineElement\\(\\s*('[^']+'|\"[^\"]+\")\\s*,\\s*defineComponent\\(/,(e,t)=>`defineElement(${t}, __aihu_wrap_defer__(defineComponent(`);if(r===n)return e;let i=r.replace(/\\)\\s*\\)\\s*\\nexport\\s/,`)))\nexport `);return i===r&&(i=r.replace(/\\)\\s*\\)\\s*$/,`)))\n`)),i===r?e:`\n// Plan 3.3 (Islands) — defer attribute support. Wraps the constructor\n// returned by defineComponent so instances bearing the \\`defer\\` attribute\n// hydrate lazily via IntersectionObserver. Bare instances retain the\n// eager Plan 3.2 hydration path.\nfunction __aihu_wrap_defer__<T extends typeof HTMLElement>(Ctor: T): T {\n const orig = (Ctor.prototype as unknown as { connectedCallback?: () => void }).connectedCallback\n if (typeof orig !== 'function') return Ctor\n ;(Ctor.prototype as unknown as { connectedCallback: () => void }).connectedCallback = function (this: HTMLElement) {\n if (this.hasAttribute('defer')) {\n _hydrateOnVisible(this, () => orig.call(this))\n } else {\n orig.call(this)\n }\n }\n return Ctor\n}\n`+i}function f(e,t){if(!/defineElement\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*defineComponent\\(/.test(e))return e;let n=e.replace(/^\\s*import\\s*\\{[^}]*\\}\\s*from\\s*'@aihu\\/runtime'\\s*;?\\s*$/m,``).replace(/import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/arbor'/,(e,t)=>{let n=t.split(`,`).map(e=>e.trim()).filter(Boolean);return n.includes(`mount`)||n.push(`mount`),`import { ${n.join(`, `)} } from '@aihu/arbor'`}),r=JSON.stringify(t);return`// SCRIBE_STATIC_ISLAND — zero @aihu/runtime references\\n${n.replace(/defineElement\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*defineComponent\\(/,`customElements.define(${r}, class extends HTMLElement {\\n connectedCallback() {\\n const root = this.attachShadow({ mode: 'open' })\\n const __aihu_setup__ = (`).replace(/\\)\\s*\\)\\s*$/,`)\n mount(__aihu_setup__({ host: root, element: this }), root)\n }\n})\n`)}`}function p(n,r,i){let a=[`--stdin`,`--tag`,t(r,`.aihu`),`--path`,r];return i?.sidecarOut&&a.push(`--sidecar-out`,i.sidecarOut),{code:e(o,a,{input:n,encoding:`utf8`}),map:null}}function m(n,r){let i=[`--stdin`,`--tag`,r?t(r,`.aihu`):`Component`,`--ast-json`];r&&i.push(`--path`,r);let a=e(o,i,{input:n,encoding:`utf8`});return JSON.parse(a)}function h(e){let t;t=e.includes(`from '@aihu/arbor'`)?e.replace(/import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/arbor'/,(e,t)=>{let n=t.split(`,`).map(e=>e.trim()).filter(Boolean);return n.includes(`mount`)||n.push(`mount`),`import { ${n.join(`, `)} } from '@aihu/arbor'`}):`import { mount } from '@aihu/arbor'\\n${e}`,/import\\s+\\{[^}]*\\}\\s+from\\s+'@aihu\\/signals'/.test(t)?t=t.replace(/import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/signals'/,(e,t)=>{if(e.startsWith(`import type`))return e;let n=t.split(`,`).map(e=>e.trim()).filter(Boolean);return n.includes(`signal`)||n.push(`signal`),`import { ${n.join(`, `)} } from '@aihu/signals'`}):/import.*from\\s*'@aihu\\/signals'/.test(t)?/import\\s+type\\s+\\{[^}]*\\}\\s+from\\s+'@aihu\\/signals'/.test(t)&&!t.match(/import\\s+\\{[^}]*\\}\\s+from\\s+'@aihu\\/signals'/)&&(t=t.replace(/(import\\s+type\\s+\\{[^}]*\\}\\s+from\\s+'@aihu\\/signals')/,(e,t)=>`${t}\\nimport { signal } from '@aihu/signals'`)):t=t.replace(/import\\s*\\{[^}]*\\}\\s*from\\s*'@aihu\\/arbor'/,e=>`${e}\\nimport { signal } from '@aihu/signals'`),t=t.replace(/import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/runtime'/,(e,t)=>{let n=t.split(`,`).map(e=>e.trim()).filter(Boolean);return n.includes(`_setMount`)||n.push(`_setMount`),n.includes(`_setSignal`)||n.push(`_setSignal`),`import { ${n.join(`, `)} } from '@aihu/runtime'`});let n=t.split(`\n`),r=-1;for(let e=n.length-1;e>=0;e--){let t=(n[e]??``).trim();if(t.startsWith(`import `)||t.startsWith(`import{`)){r=e;break}}return r!==-1&&(n.splice(r+1,0,`_setMount(mount)`,`_setSignal(signal)`,``),t=n.join(`\n`)),t}function g(e){let t=e?.islands!==!1,n=e?.shadowMode;return{name:`aihu-compiler`,enforce:`pre`,transform(e,r){let i=r.split(`?`)[0];if(i.endsWith(`.aihu`))return(async()=>{let r=p(e,i,{sidecarOut:`${i}.ts`}),a=n==null?r.code:s(r.code,n),o=l(a),m;t&&o!==null&&c(a)===`static`?m=f(a,o):o===null?(m=a,m=h(m)):(m=u(a,o),m=d(m,o),m=h(m));try{let e=await import(`vite`);return`transformWithEsbuild`in e&&typeof e.transformWithEsbuild==`function`?{code:(await e.transformWithEsbuild(m,`component.ts`,{target:`esnext`,sourcemap:!1})).code,map:null}:{code:m,moduleType:`ts`,map:null}}catch{return{code:m,map:null}}})()}}}export{d as _buildDeferredHydration,f as _buildStaticIsland,c as _classifyIsland,h as _injectAutoWiring,s as _injectShadowMode,g as aihuCompilerPlugin,m as compileToAst,p as transform};\n//# sourceMappingURL=index.js.map","/**\n * `defineStylePack()` — the export hook for external orgs (Plan 3 Task 10).\n *\n * Lets an external org declare its own token bundle against the SAME token-name\n * contract as the built-in `aihu-default` / `aihu-graphite` packs (see\n * `styles/*.css`), so a custom pack slots into the engine exactly like the\n * built-ins. Returns a `StylePack` descriptor the engine can register and emit\n * as `:root` / `.dark` token blocks.\n *\n * The built-in packs are expressible through this same API — `defineStylePack`\n * is just the typed, programmatic form of the shipped CSS bundles.\n */\n\n/** A design-token map: `name` → CSS value. Names omit the leading `--`. */\nexport type TokenMap = Record<string, string>\n\n/** Input to {@link defineStylePack}. */\nexport interface StylePackInput {\n /** Pack name, e.g. `'acme'` (used for registration / debugging). */\n name: string\n /** Light-theme tokens (the `:root` block). Names without the `--` prefix. */\n tokens: TokenMap\n /** Optional dark-theme overrides (the `.dark` block). */\n dark?: TokenMap\n}\n\n/** A registered, validated style-pack descriptor. */\nexport interface StylePack {\n readonly name: string\n readonly tokens: TokenMap\n readonly dark: TokenMap\n /**\n * Serialize the pack to a `:root { … }` (+ `.dark { … }`) CSS string — the\n * same shape as the shipped `styles/*.css` bundles.\n */\n toCss(): string\n}\n\n/** Normalize a token name to its `--`-prefixed custom-property form. */\nfunction asCustomProp(name: string): string {\n return name.startsWith('--') ? name : `--${name}`\n}\n\nfunction emitBlock(selector: string, tokens: TokenMap): string {\n const decls = Object.entries(tokens)\n .map(([name, value]) => ` ${asCustomProp(name)}: ${value};`)\n .join('\\n')\n return `${selector} {\\n${decls}\\n}\\n`\n}\n\n/**\n * Define a style pack from a token map.\n *\n * @example\n * const acme = defineStylePack({\n * name: 'acme',\n * tokens: { 'color-primary': '#0a7', 'radius-md': '6px' },\n * dark: { 'color-primary': '#3fc' },\n * })\n * acme.toCss() // => \":root { --color-primary: #0a7; … } .dark { … }\"\n */\nexport function defineStylePack(input: StylePackInput): StylePack {\n if (!input.name) {\n throw new Error('defineStylePack: `name` is required')\n }\n if (!input.tokens || Object.keys(input.tokens).length === 0) {\n throw new Error(`defineStylePack(\"${input.name}\"): tokens must be a non-empty map`)\n }\n\n const tokens = { ...input.tokens }\n const dark = { ...(input.dark ?? {}) }\n\n return {\n name: input.name,\n tokens,\n dark,\n toCss(): string {\n let css = emitBlock(':root', tokens)\n if (Object.keys(dark).length > 0) {\n css += emitBlock('.dark', dark)\n }\n return css\n },\n }\n}\n","import { execFileSync } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { compileToAst } from '@aihu/compiler'\n\nexport {\n defineStylePack,\n type StylePack,\n type StylePackInput,\n type TokenMap,\n} from './define-style-pack.ts'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\n/**\n * Resolve the path to the `aihu-css-compile` binary.\n * Looks for it relative to the package, then in the workspace target/release.\n * Plan 4 will replace this with a prebuilt binary shipped with the package.\n */\nfunction resolveBinary(): string {\n const ext = process.platform === 'win32' ? '.exe' : ''\n const candidates = [\n // dev: workspace target/release (most common during development)\n resolve(__dirname, '../../../target/release', `aihu-css-compile${ext}`),\n // ci: same with debug\n resolve(__dirname, '../../../target/debug', `aihu-css-compile${ext}`),\n ]\n for (const c of candidates) {\n if (existsSync(c)) return c\n }\n throw new Error(\n `aihu-css-compile binary not found. Run \\`cargo build --release -p aihu-css-core\\` from the repo root first. Checked: ${candidates.join(', ')}`,\n )\n}\n\n/**\n * Compile a list of utility class names to CSS.\n *\n * Plan 1 bootstrap — supports a hardcoded subset; see crates/aihu-css-core/src/tokens.rs.\n * Plan 2 wires the AST scanner so callers pass `.aihu` SFC ASTs instead of raw class lists.\n *\n * @param classes - utility class names like `['bg-primary', 'p-4']`\n * @returns CSS string with one rule per known class\n */\nexport function compile(classes: string[]): string {\n if (classes.length === 0) return ''\n\n const bin = resolveBinary()\n const input = classes.join('\\n')\n const result = execFileSync(bin, [], {\n input,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'inherit'],\n })\n return result\n}\n\n/**\n * Compile a `.aihu` SFC source string to scoped, shadow-DOM-embedded CSS.\n *\n * Pipeline (Plan 2 Task 9): `compileToAst(source)` (from `@aihu/compiler`)\n * → AST JSON → `aihu-css-compile --ast-json` → scoped CSS. The output is the\n * per-SFC stylesheet the compiler folds into the component's shadow `<style>`:\n * `:host`-level theme tokens, variant-resolved utility rules, and the folded\n * authored `@style` block. There is NO global utility stylesheet.\n *\n * @param source - the `.aihu` SFC source text\n * @param id - optional file path/id (used to derive the tag stem + `@route` checks)\n * @returns the scoped CSS string for the SFC\n */\nexport function compileSfc(source: string, id?: string): string {\n const ast = compileToAst(source, id)\n const bin = resolveBinary()\n return execFileSync(bin, ['--ast-json'], {\n input: JSON.stringify(ast),\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'inherit'],\n })\n}\n"],"mappings":";;;;;AAA0J,MAAM,IAAE,QAAQ,aAAW,UAAQ,SAAO,IAAG,IAAE,QAAQ,IAAI,sBAAoBA,QAAEC,QAAEC,cAAE,OAAO,KAAK,IAAI,CAAC,EAAC,sBAAsB,IAAI;AAsCtG,SAAS,EAAE,GAAE,GAAE;CAAC,IAAI,IAAE;EAAC;EAAU;EAAQ,IAAEC,SAAE,GAAE,QAAQ,GAAC;EAAY;EAAa;CAAC,KAAG,EAAE,KAAK,UAAS,EAAE;CAAC,IAAI,IAAEC,aAAE,GAAE,GAAE;EAAC,OAAM;EAAE,UAAS;EAAO,CAAC;CAAC,OAAO,KAAK,MAAM,EAAE;;;;;ACCxV,SAAS,aAAa,MAAsB;CAC1C,OAAO,KAAK,WAAW,KAAK,GAAG,OAAO,KAAK;;AAG7C,SAAS,UAAU,UAAkB,QAA0B;CAI7D,OAAO,GAAG,SAAS,MAHL,OAAO,QAAQ,OAAO,CACjC,KAAK,CAAC,MAAM,WAAW,KAAK,aAAa,KAAK,CAAC,IAAI,MAAM,GAAG,CAC5D,KAAK,KACsB,CAAC;;;;;;;;;;;;;AAcjC,SAAgB,gBAAgB,OAAkC;CAChE,IAAI,CAAC,MAAM,MACT,MAAM,IAAI,MAAM,sCAAsC;CAExD,IAAI,CAAC,MAAM,UAAU,OAAO,KAAK,MAAM,OAAO,CAAC,WAAW,GACxD,MAAM,IAAI,MAAM,oBAAoB,MAAM,KAAK,oCAAoC;CAGrF,MAAM,SAAS,EAAE,GAAG,MAAM,QAAQ;CAClC,MAAM,OAAO,EAAE,GAAI,MAAM,QAAQ,EAAE,EAAG;CAEtC,OAAO;EACL,MAAM,MAAM;EACZ;EACA;EACA,QAAgB;GACd,IAAI,MAAM,UAAU,SAAS,OAAO;GACpC,IAAI,OAAO,KAAK,KAAK,CAAC,SAAS,GAC7B,OAAO,UAAU,SAAS,KAAK;GAEjC,OAAO;;EAEV;;;;ACtEH,MAAM,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;;;;;;AAOzD,SAAS,gBAAwB;CAC/B,MAAM,MAAM,QAAQ,aAAa,UAAU,SAAS;CACpD,MAAM,aAAa,CAEjB,QAAQ,WAAW,2BAA2B,mBAAmB,MAAM,EAEvE,QAAQ,WAAW,yBAAyB,mBAAmB,MAAM,CACtE;CACD,KAAK,MAAM,KAAK,YACd,IAAI,WAAW,EAAE,EAAE,OAAO;CAE5B,MAAM,IAAI,MACR,wHAAwH,WAAW,KAAK,KAAK,GAC9I;;;;;;;;;;;AAYH,SAAgB,QAAQ,SAA2B;CACjD,IAAI,QAAQ,WAAW,GAAG,OAAO;CASjC,OALe,aAFH,eAEmB,EAAE,EAAE,EAAE;EACnC,OAFY,QAAQ,KAAK,KAEpB;EACL,UAAU;EACV,OAAO;GAAC;GAAQ;GAAQ;GAAU;EACnC,CACY;;;;;;;;;;;;;;;AAgBf,SAAgB,WAAW,QAAgB,IAAqB;CAC9D,MAAM,MAAMC,EAAa,QAAQ,GAAG;CAEpC,OAAO,aADK,eACW,EAAE,CAAC,aAAa,EAAE;EACvC,OAAO,KAAK,UAAU,IAAI;EAC1B,UAAU;EACV,OAAO;GAAC;GAAQ;GAAQ;GAAU;EACnC,CAAC"}
@@ -0,0 +1,14 @@
1
+ //#region src/runtime/cn.d.ts
2
+ /** A class value: string, falsy (dropped), or a nested array of the same. */
3
+ type ClassValue = string | number | null | undefined | false | ClassValue[];
4
+ /**
5
+ * Merge class values, resolving last-wins conflicts per property group.
6
+ *
7
+ * @example cn('p-2', 'p-4') // 'p-4'
8
+ * @example cn('a', false && 'b', ['c']) // 'a c'
9
+ * @example cn('bg-red-500', 'bg-blue-500') // 'bg-blue-500'
10
+ */
11
+ declare function cn(...inputs: ClassValue[]): string;
12
+ //#endregion
13
+ export { ClassValue, cn };
14
+ //# sourceMappingURL=cn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cn.d.ts","names":[],"sources":["../../src/runtime/cn.ts"],"mappings":";;KAoBY,UAAA,gDAA0D,UAAA;;;;;AAyCtE;;;iBAAgB,EAAA,CAAA,GAAM,MAAA,EAAQ,UAAA"}
@@ -0,0 +1,107 @@
1
+ //#region src/runtime/cn-conflict-map.generated.ts
2
+ /** Prefix → conflict-group key, sorted longest-prefix-first. */
3
+ const CONFLICT_GROUPS = {
4
+ outline: "outline-color",
5
+ opacity: "opacity",
6
+ rounded: "border-radius",
7
+ border: "border-color",
8
+ stroke: "stroke",
9
+ shadow: "box-shadow",
10
+ "gap-x": "column-gap",
11
+ "gap-y": "row-gap",
12
+ "min-w": "min-width",
13
+ "max-w": "max-width",
14
+ "min-h": "min-height",
15
+ "max-h": "max-height",
16
+ text: "color",
17
+ fill: "fill",
18
+ ring: "--tw-ring-color",
19
+ font: "font-weight",
20
+ gap: "gap",
21
+ px: "padding-inline",
22
+ py: "padding-block",
23
+ pt: "padding-top",
24
+ pr: "padding-right",
25
+ pb: "padding-bottom",
26
+ pl: "padding-left",
27
+ mx: "margin-inline",
28
+ my: "margin-block",
29
+ mt: "margin-top",
30
+ mr: "margin-right",
31
+ mb: "margin-bottom",
32
+ ml: "margin-left",
33
+ bg: "background-color",
34
+ p: "padding",
35
+ m: "margin",
36
+ w: "width",
37
+ h: "height",
38
+ z: "z-index"
39
+ };
40
+ //#endregion
41
+ //#region src/runtime/cn.ts
42
+ /**
43
+ * `@aihu/css-engine/runtime/cn` — the class-merge runtime helper (Plan 3 Task 9).
44
+ *
45
+ * `cn(...inputs)` merges class strings / arrays / conditionals into a single
46
+ * deduplicated class string, resolving Tailwind-style conflicts last-wins per
47
+ * property group (`cn('p-2', 'p-4')` → `'p-4'`).
48
+ *
49
+ * The conflict map (`CONFLICT_GROUPS`) is GENERATED at engine build time from
50
+ * the utility registry (`scripts/gen-cn-conflict-map.ts` → Rust
51
+ * `tokens::conflict_groups()`), NOT hand-maintained — so it never drifts from
52
+ * the utility table. Separate < 1 KB gz sub-export from `runtime/progressive`
53
+ * (Risk #4 size-split).
54
+ *
55
+ * This is the runtime-merge helper for consumer-provided overrides (spec §9.3):
56
+ * recipes use static utility strings at compile time; `cn()` is only for the
57
+ * runtime override case.
58
+ */
59
+ /** Flatten the (possibly nested / conditional) inputs into a token list. */
60
+ function tokens(inputs, out) {
61
+ for (const input of inputs) {
62
+ if (!input) continue;
63
+ if (Array.isArray(input)) tokens(input, out);
64
+ else for (const t of String(input).split(" ")) if (t) out.push(t);
65
+ }
66
+ }
67
+ /**
68
+ * The conflict-group key for a class, or the class itself when it belongs to no
69
+ * known group (so unrelated classes always coexist). Strips variant prefixes
70
+ * (`md:`, `hover:`, …) so `hover:p-2` and `hover:p-4` still conflict, while
71
+ * `p-2` and `hover:p-4` do not (different variant scope).
72
+ */
73
+ function groupKey(cls) {
74
+ const colon = cls.lastIndexOf(":");
75
+ const variant = colon === -1 ? "" : cls.slice(0, colon + 1);
76
+ const base = colon === -1 ? cls : cls.slice(colon + 1);
77
+ const dash = base.indexOf("-");
78
+ const group = CONFLICT_GROUPS[dash === -1 ? base : base.slice(0, dash)];
79
+ return group ? `${variant}${group}` : cls;
80
+ }
81
+ /**
82
+ * Merge class values, resolving last-wins conflicts per property group.
83
+ *
84
+ * @example cn('p-2', 'p-4') // 'p-4'
85
+ * @example cn('a', false && 'b', ['c']) // 'a c'
86
+ * @example cn('bg-red-500', 'bg-blue-500') // 'bg-blue-500'
87
+ */
88
+ function cn(...inputs) {
89
+ const flat = [];
90
+ tokens(inputs, flat);
91
+ const winner = /* @__PURE__ */ new Map();
92
+ for (const t of flat) winner.set(groupKey(t), t);
93
+ const seen = /* @__PURE__ */ new Set();
94
+ const result = [];
95
+ for (const t of flat) {
96
+ const key = groupKey(t);
97
+ if (winner.get(key) === t && !seen.has(t)) {
98
+ seen.add(t);
99
+ result.push(t);
100
+ }
101
+ }
102
+ return result.join(" ");
103
+ }
104
+ //#endregion
105
+ export { cn };
106
+
107
+ //# sourceMappingURL=cn.js.map