@decantr/css 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Decantr AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # @decantr/css
2
2
 
3
+ Support status: `core-supported`
4
+ Release channel: `stable`
5
+
3
6
  Framework-agnostic CSS atoms runtime for Decantr projects.
4
7
 
5
8
  ## Installation
@@ -120,7 +123,7 @@ When you scaffold a project with `@decantr/cli`, it generates:
120
123
 
121
124
  - `src/styles/tokens.css` - Theme tokens (colors, spacing, radii)
122
125
  - `src/styles/treatments.css` - Visual treatment classes (interactive, surface, data, control, section, annotation)
123
- - `src/styles/decorators.css` - Recipe decorator classes
126
+ - `src/styles/decorators.css` - Optional decorator classes
124
127
 
125
128
  Import these alongside @decantr/css:
126
129
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,15 @@
1
+ /**
2
+ * Decantr CSS Atoms - Direct atom definitions and algorithmic resolution.
3
+ */
4
+ /**
5
+ * Resolve an atom name to its CSS declaration.
6
+ * @param atom - Atom name (e.g., '_flex', '_gap4', '_p2')
7
+ * @returns CSS declaration or null if not recognized
8
+ */
9
+ declare function resolveAtomDecl(atom: string): string | null;
10
+
1
11
  /**
2
12
  * Decantr CSS - Main css() function.
3
- * Ported from decantr-framework/src/css/index.js
4
13
  */
5
14
  /**
6
15
  * Process atom strings and inject CSS.
@@ -17,7 +26,6 @@ declare function define(name: string, declaration: string): void;
17
26
 
18
27
  /**
19
28
  * Decantr CSS Runtime - CSS injection and extraction.
20
- * Ported from decantr-framework/src/css/runtime.js
21
29
  */
22
30
  /** Responsive breakpoints (mobile-first, min-width) */
23
31
  declare const BREAKPOINTS: {
@@ -42,6 +50,14 @@ declare function inject(className: string, declaration: string, escapedName?: st
42
50
  * @param bp - breakpoint key (sm|md|lg|xl)
43
51
  */
44
52
  declare function injectResponsive(className: string, declaration: string, bp: string): void;
53
+ /**
54
+ * Inject a max-width responsive atom.
55
+ * @param className - e.g., '_mdmax:none' (hide below md)
56
+ * @param declaration - CSS declaration(s)
57
+ * @param bp - breakpoint key (sm|md|lg|xl). The max-width query is (breakpoint - 0.02)px
58
+ * so it doesn't overlap with the matching min-width variant.
59
+ */
60
+ declare function injectResponsiveMax(className: string, declaration: string, bp: string): void;
45
61
  /**
46
62
  * Inject a pseudo-class atom.
47
63
  * @param className - e.g., '_h:bgprimary'
@@ -49,6 +65,22 @@ declare function injectResponsive(className: string, declaration: string, bp: st
49
65
  * @param prefix - 'h'|'f'|'fv'|'a'|'fw'
50
66
  */
51
67
  declare function injectPseudo(className: string, declaration: string, prefix: string): void;
68
+ /**
69
+ * Inject a responsive + pseudo-class atom.
70
+ * @param className - e.g., '_sm:h:bgprimary'
71
+ * @param declaration - CSS declaration(s)
72
+ * @param bp - breakpoint key
73
+ * @param pseudo - pseudo-class name
74
+ */
75
+ declare function injectResponsivePseudo(className: string, declaration: string, bp: string, pseudo: string): void;
76
+ /**
77
+ * Inject a max-width responsive + pseudo-class atom.
78
+ * @param className - e.g., '_mdmax:h:fgmuted'
79
+ * @param declaration - CSS declaration(s)
80
+ * @param bp - breakpoint key (sm|md|lg|xl)
81
+ * @param pseudo - pseudo-class name
82
+ */
83
+ declare function injectResponsiveMaxPseudo(className: string, declaration: string, bp: string, pseudo: string): void;
52
84
  /**
53
85
  * Inject a container query-wrapped atom.
54
86
  * @param className - e.g., '_cq640:gc3'
@@ -83,20 +115,9 @@ declare function getInjectedClasses(): string[];
83
115
  */
84
116
  declare function reset(): void;
85
117
 
86
- /**
87
- * Decantr CSS Atoms - Direct atom definitions and algorithmic resolution.
88
- * Ported from decantr-framework/src/css/atoms.js
89
- */
90
- /**
91
- * Resolve an atom name to its CSS declaration.
92
- * @param atom - Atom name (e.g., '_flex', '_gap4', '_p2')
93
- * @returns CSS declaration or null if not recognized
94
- */
95
- declare function resolveAtomDecl(atom: string): string | null;
96
-
97
118
  declare function getAnimations(): boolean;
98
119
  declare function setAnimations(v: boolean): void;
99
120
  declare function getTheme(): string | null;
100
121
  declare function setTheme(id: string | null): void;
101
122
 
102
- export { BREAKPOINTS, CQ_WIDTHS, css, define, extractCSS, getAnimations, getInjectedClasses, getTheme, inject, injectContainer, injectGroupPeer, injectMediaQuery, injectPseudo, injectResponsive, reset, resolveAtomDecl, setAnimations, setTheme };
123
+ export { BREAKPOINTS, CQ_WIDTHS, css, define, extractCSS, getAnimations, getInjectedClasses, getTheme, inject, injectContainer, injectGroupPeer, injectMediaQuery, injectPseudo, injectResponsive, injectResponsiveMax, injectResponsiveMaxPseudo, injectResponsivePseudo, reset, resolveAtomDecl, setAnimations, setTheme };
package/dist/index.js CHANGED
@@ -61,6 +61,11 @@ var DIRECT = {
61
61
  // Sizing
62
62
  wfull: "width:100%",
63
63
  hfull: "height:100%",
64
+ // Hyphenated aliases — DECANTR.md examples used `_w-full` / `_h-full`
65
+ // historically; cold-LLM scaffolds fall back to the doc-spelling. Accept
66
+ // both so the doc and runtime can never silently disagree.
67
+ "w-full": "width:100%",
68
+ "h-full": "height:100%",
64
69
  w100: "width:100%",
65
70
  h100: "height:100%",
66
71
  wscreen: "width:100vw",
@@ -79,6 +84,20 @@ var DIRECT = {
79
84
  maxhfull: "max-height:100%",
80
85
  mw640: "max-width:40rem",
81
86
  mw480: "max-width:30rem",
87
+ // Item alignment aliases — DECANTR.md examples used `_items-center` /
88
+ // `_items-start` etc. (Tailwind-style); accept both alongside the
89
+ // canonical `_aic` / `_aifs` etc. so doc-driven AI scaffolds resolve.
90
+ "items-center": "align-items:center",
91
+ "items-start": "align-items:flex-start",
92
+ "items-end": "align-items:flex-end",
93
+ "items-stretch": "align-items:stretch",
94
+ "items-baseline": "align-items:baseline",
95
+ "justify-center": "justify-content:center",
96
+ "justify-start": "justify-content:flex-start",
97
+ "justify-end": "justify-content:flex-end",
98
+ "justify-between": "justify-content:space-between",
99
+ "justify-around": "justify-content:space-around",
100
+ "justify-evenly": "justify-content:space-evenly",
82
101
  // Overflow
83
102
  overhidden: "overflow:hidden",
84
103
  overauto: "overflow:auto",
@@ -484,6 +503,17 @@ function resolveAtomDecl(atom) {
484
503
  if (dir === "y") return `margin-block:${value}`;
485
504
  }
486
505
  }
506
+ const marginAutoMatch = name.match(/^m([trblxy]?)auto$/);
507
+ if (marginAutoMatch) {
508
+ const [, dir] = marginAutoMatch;
509
+ if (!dir || dir === "") return `margin:auto`;
510
+ if (dir === "t") return `margin-top:auto`;
511
+ if (dir === "r") return `margin-right:auto`;
512
+ if (dir === "b") return `margin-bottom:auto`;
513
+ if (dir === "l") return `margin-left:auto`;
514
+ if (dir === "x") return `margin-inline:auto`;
515
+ if (dir === "y") return `margin-block:auto`;
516
+ }
487
517
  const negMarginMatch = name.match(/^-m([trblxy]?)(\d+(?:\.\d+)?)$/);
488
518
  if (negMarginMatch) {
489
519
  const [, dir, num] = negMarginMatch;
@@ -590,6 +620,7 @@ function resolveAtomDecl(atom) {
590
620
  var injected = /* @__PURE__ */ new Set();
591
621
  var BREAKPOINTS = { sm: 640, md: 768, lg: 1024, xl: 1280 };
592
622
  var BP_ORDER = ["sm", "md", "lg", "xl"];
623
+ var BP_MAX_ORDER = ["xlmax", "lgmax", "mdmax", "smmax"];
593
624
  var CQ_WIDTHS = [320, 480, 640, 768, 1024];
594
625
  var LAYER_ORDER = "@layer d.base,d.theme,d.atoms,d.user;";
595
626
  var atomBuffer = [];
@@ -625,6 +656,12 @@ function flushBuffers() {
625
656
  bpBuffers[bp] = [];
626
657
  }
627
658
  }
659
+ for (const bp of BP_MAX_ORDER) {
660
+ if (bpBuffers[bp]?.length) {
661
+ els[bp].textContent = (els[bp].textContent || "") + bpBuffers[bp].join("");
662
+ bpBuffers[bp] = [];
663
+ }
664
+ }
628
665
  }
629
666
  bpBuffers = {};
630
667
  }
@@ -663,6 +700,12 @@ function ensureBpElements() {
663
700
  document.head.appendChild(el);
664
701
  bpEls[bp] = el;
665
702
  }
703
+ for (const bp of BP_MAX_ORDER) {
704
+ const el = document.createElement("style");
705
+ el.setAttribute(`data-decantr-${bp}`, "");
706
+ document.head.appendChild(el);
707
+ bpEls[bp] = el;
708
+ }
666
709
  return bpEls;
667
710
  }
668
711
  function ensureCqElement() {
@@ -688,7 +731,20 @@ function injectResponsive(className, declaration, bp) {
688
731
  if (typeof document === "undefined") return;
689
732
  const escaped = className.replace(/:/g, "\\:");
690
733
  if (!bpBuffers[bp]) bpBuffers[bp] = [];
691
- bpBuffers[bp].push(`@layer d.atoms{@media(min-width:${BREAKPOINTS[bp]}px){.${escaped}{${declaration}}}}`);
734
+ bpBuffers[bp].push(
735
+ `@layer d.atoms{@media(min-width:${BREAKPOINTS[bp]}px){.${escaped}{${declaration}}}}`
736
+ );
737
+ scheduleFlush();
738
+ }
739
+ function injectResponsiveMax(className, declaration, bp) {
740
+ if (injected.has(className)) return;
741
+ injected.add(className);
742
+ if (typeof document === "undefined") return;
743
+ const escaped = className.replace(/:/g, "\\:");
744
+ const key = `${bp}max`;
745
+ if (!bpBuffers[key]) bpBuffers[key] = [];
746
+ const maxPx = BREAKPOINTS[bp] - 0.02;
747
+ bpBuffers[key].push(`@layer d.atoms{@media(max-width:${maxPx}px){.${escaped}{${declaration}}}}`);
692
748
  scheduleFlush();
693
749
  }
694
750
  function injectPseudo(className, declaration, prefix) {
@@ -713,7 +769,22 @@ function injectResponsivePseudo(className, declaration, bp, pseudo) {
713
769
  if (typeof document === "undefined") return;
714
770
  const escaped = escapeSelector(className);
715
771
  if (!bpBuffers[bp]) bpBuffers[bp] = [];
716
- bpBuffers[bp].push(`@layer d.atoms{@media(min-width:${BREAKPOINTS[bp]}px){.${escaped}:${pseudo}{${declaration}}}}`);
772
+ bpBuffers[bp].push(
773
+ `@layer d.atoms{@media(min-width:${BREAKPOINTS[bp]}px){.${escaped}:${pseudo}{${declaration}}}}`
774
+ );
775
+ scheduleFlush();
776
+ }
777
+ function injectResponsiveMaxPseudo(className, declaration, bp, pseudo) {
778
+ if (injected.has(className)) return;
779
+ injected.add(className);
780
+ if (typeof document === "undefined") return;
781
+ const escaped = escapeSelector(className);
782
+ const key = `${bp}max`;
783
+ if (!bpBuffers[key]) bpBuffers[key] = [];
784
+ const maxPx = BREAKPOINTS[bp] - 0.02;
785
+ bpBuffers[key].push(
786
+ `@layer d.atoms{@media(max-width:${maxPx}px){.${escaped}:${pseudo}{${declaration}}}}`
787
+ );
717
788
  scheduleFlush();
718
789
  }
719
790
  function injectContainer(className, declaration, width) {
@@ -761,6 +832,9 @@ function extractCSS() {
761
832
  for (const bp of BP_ORDER) {
762
833
  if (bpEls[bp]) css2 += bpEls[bp].textContent || "";
763
834
  }
835
+ for (const bp of BP_MAX_ORDER) {
836
+ if (bpEls[bp]) css2 += bpEls[bp].textContent || "";
837
+ }
764
838
  }
765
839
  if (cqEl) css2 += cqEl.textContent || "";
766
840
  return css2;
@@ -780,6 +854,9 @@ function reset() {
780
854
  for (const bp of BP_ORDER) {
781
855
  if (bpEls[bp]) bpEls[bp].textContent = "";
782
856
  }
857
+ for (const bp of BP_MAX_ORDER) {
858
+ if (bpEls[bp]) bpEls[bp].textContent = "";
859
+ }
783
860
  }
784
861
  if (cqEl) cqEl.textContent = "";
785
862
  }
@@ -787,6 +864,7 @@ function reset() {
787
864
  // src/css.ts
788
865
  var customAtoms = /* @__PURE__ */ new Map();
789
866
  var BP_RE = /^_(sm|md|lg|xl):(.+)$/;
867
+ var BP_MAX_RE = /^_(sm|md|lg|xl)max:(.+)$/;
790
868
  var CQ_RE = /^_cq(\d+):(.+)$/;
791
869
  var GP_RE = /^_(gh|gf|ga|ph|pf|pa):(.+)$/;
792
870
  var PSEUDO_RE = /^_(h|f|fv|a|fw):(.+)$/;
@@ -810,6 +888,8 @@ var ARB_PROPS = {
810
888
  h: "height",
811
889
  mw: "max-width",
812
890
  mh: "max-height",
891
+ maxw: "max-width",
892
+ maxh: "max-height",
813
893
  minw: "min-width",
814
894
  minh: "min-height",
815
895
  p: "padding",
@@ -834,7 +914,10 @@ var ARB_PROPS = {
834
914
  lh: "line-height",
835
915
  fw: "font-weight",
836
916
  ls: "letter-spacing",
917
+ leading: "line-height",
918
+ tracking: "letter-spacing",
837
919
  r: "border-radius",
920
+ rounded: "border-radius",
838
921
  bg: "background",
839
922
  fg: "color",
840
923
  bc: "border-color",
@@ -843,6 +926,7 @@ var ARB_PROPS = {
843
926
  bb: "border-bottom",
844
927
  br: "border-right",
845
928
  bl: "border-left",
929
+ border: "border",
846
930
  z: "z-index",
847
931
  op: "opacity",
848
932
  top: "top",
@@ -856,7 +940,28 @@ var ARB_PROPS = {
856
940
  trans: "transition",
857
941
  object: "object-fit",
858
942
  gc: "grid-template-columns",
859
- gr: "grid-template-rows"
943
+ gr: "grid-template-rows",
944
+ // Additional content-observed bracket atoms (P0-3 expansion). The v1
945
+ // harness report flagged the page-pack Surface emitting atoms the
946
+ // runtime didn't resolve; same class of silent-failure applies when
947
+ // pattern JSONs use prefixes not in this map. These additions cover
948
+ // every bracket prefix observed across the 209 archetype + 80+ pattern
949
+ // JSONs in decantr-content.
950
+ overflow: "overflow",
951
+ pointer: "pointer-events",
952
+ text: "text-align",
953
+ whitespace: "white-space",
954
+ items: "align-items",
955
+ justify: "justify-content",
956
+ aspect: "aspect-ratio",
957
+ snap: "scroll-snap-type",
958
+ // Note: 'scale' intentionally omitted — use the numeric `_scale95` /
959
+ // `_scale100` / `_scale105` atoms. Bracket form would need value wrapping
960
+ // (`scale(1.05)` vs `1.05`) which the generic emitter can't do.
961
+ display: "display",
962
+ position: "position",
963
+ pos: "position",
964
+ cursor: "cursor"
860
965
  };
861
966
  function sanitizeArbValue(val) {
862
967
  let safe = val.replace(/[{}<>;]/g, "");
@@ -941,6 +1046,26 @@ function css(...classes) {
941
1046
  result.push(part);
942
1047
  continue;
943
1048
  }
1049
+ const bpMaxMatch = part.match(BP_MAX_RE);
1050
+ if (bpMaxMatch) {
1051
+ const [, bp, innerAtom] = bpMaxMatch;
1052
+ const pseudoInner = innerAtom.match(/^(h|f|fv|a|fw):(.+)$/);
1053
+ if (pseudoInner) {
1054
+ const [, pseudoPrefix, atomName] = pseudoInner;
1055
+ const resolved3 = resolveAtom(`_${atomName}`);
1056
+ if (resolved3) {
1057
+ injectResponsiveMaxPseudo(part, resolved3.decl, bp, PSEUDO_NAMES[pseudoPrefix]);
1058
+ }
1059
+ result.push(part);
1060
+ continue;
1061
+ }
1062
+ const resolved2 = resolveAtom(`_${innerAtom}`);
1063
+ if (resolved2) {
1064
+ injectResponsiveMax(part, resolved2.decl, bp);
1065
+ }
1066
+ result.push(part);
1067
+ continue;
1068
+ }
944
1069
  const bpMatch = part.match(BP_RE);
945
1070
  if (bpMatch) {
946
1071
  const [, bp, innerAtom] = bpMatch;
@@ -996,8 +1121,12 @@ function css(...classes) {
996
1121
  }
997
1122
  const resolved = resolveAtom(part);
998
1123
  if (resolved) {
999
- const needsEscape = /[/\[\]#%(),+]/.test(resolved.className);
1000
- inject(resolved.className, resolved.decl, needsEscape ? escapeClass(resolved.className) : void 0);
1124
+ const needsEscape = /[/[\]#%(),+]/.test(resolved.className);
1125
+ inject(
1126
+ resolved.className,
1127
+ resolved.decl,
1128
+ needsEscape ? escapeClass(resolved.className) : void 0
1129
+ );
1001
1130
  result.push(part);
1002
1131
  } else {
1003
1132
  result.push(part);
@@ -1040,6 +1169,9 @@ export {
1040
1169
  injectMediaQuery,
1041
1170
  injectPseudo,
1042
1171
  injectResponsive,
1172
+ injectResponsiveMax,
1173
+ injectResponsiveMaxPseudo,
1174
+ injectResponsivePseudo,
1043
1175
  reset,
1044
1176
  resolveAtomDecl,
1045
1177
  setAnimations,
package/package.json CHANGED
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "name": "@decantr/css",
3
- "version": "1.0.2",
4
- "description": "Framework-agnostic CSS atoms runtime for Decantr projects",
5
- "author": "Decantr",
3
+ "version": "1.0.4",
4
+ "description": "Framework-agnostic CSS atom runtime for Decantr projects",
5
+ "author": "Decantr AI",
6
+ "bugs": {
7
+ "url": "https://github.com/decantr-ai/decantr/issues"
8
+ },
6
9
  "repository": {
7
10
  "type": "git",
8
- "url": "https://github.com/decantr/decantr-monorepo",
11
+ "url": "git+https://github.com/decantr-ai/decantr.git",
9
12
  "directory": "packages/css"
10
13
  },
11
- "homepage": "https://github.com/decantr/decantr-monorepo/tree/main/packages/css",
14
+ "homepage": "https://decantr.ai",
12
15
  "type": "module",
13
16
  "main": "./dist/index.js",
14
17
  "types": "./dist/index.d.ts",
@@ -21,17 +24,15 @@
21
24
  "files": [
22
25
  "dist"
23
26
  ],
24
- "scripts": {
25
- "build": "tsup src/index.ts --format esm --dts",
26
- "test": "vitest run",
27
- "test:watch": "vitest",
28
- "lint": "tsc --noEmit"
27
+ "publishConfig": {
28
+ "access": "public"
29
29
  },
30
30
  "keywords": [
31
31
  "decantr",
32
32
  "css",
33
33
  "atoms",
34
- "utility-css"
34
+ "utility-css",
35
+ "design-intelligence"
35
36
  ],
36
37
  "license": "MIT",
37
38
  "devDependencies": {
@@ -39,5 +40,12 @@
39
40
  "tsup": "^8.0.0",
40
41
  "typescript": "^5.0.0",
41
42
  "vitest": "^3.0.0"
43
+ },
44
+ "scripts": {
45
+ "build": "tsup src/index.ts --format esm --dts",
46
+ "test": "vitest run",
47
+ "test:watch": "vitest",
48
+ "lint": "tsc --noEmit",
49
+ "preversion": "pnpm build && pnpm test"
42
50
  }
43
- }
51
+ }