@cssints/layout 0.0.0-stage → 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexei Accio
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,3 +1,130 @@
1
- # Temporary Holding Version
1
+ # @cssints/layout
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Layout primitives for [cssints](../../README.md), after [Every Layout](https://every-layout.dev) and
4
+ [smolcss](https://smolcss.dev): `stack()`, `cluster()`, `sidebar()`, `switcher()`, `cover()` and `autoGrid()`. Each one is a
5
+ chain member of `cssints/plugin` `scope()`: a call gives the primitive's kernel classes, and each method sets one custom
6
+ property. No runtime, no media queries: the layouts respond to the space they have.
7
+
8
+ ```ts
9
+ import { cn, p } from "cssints" with { type: "cssints" };
10
+ import {
11
+ autoGrid,
12
+ cluster,
13
+ sidebar,
14
+ stack,
15
+ switcher,
16
+ } from "@cssints/layout" with { type: "cssints" };
17
+
18
+ export const page = cn(stack().gap(6), p(4));
19
+ export const toolbar = cluster().gap(2).justify("space-between");
20
+ export const layout = sidebar().width("20rem").content("50%").gap(4);
21
+ export const buttons = switcher().threshold("30rem");
22
+ export const cards = autoGrid().min("16rem").gap(4);
23
+ ```
24
+
25
+ ```css
26
+ @layer _.k {
27
+ ._layout-stack-core {
28
+ gap: var(--layout-stack-gap);
29
+ --layout-stack-gap: 1rem;
30
+ display: flex;
31
+ flex-direction: column;
32
+ }
33
+ ._layout-sidebar-core {
34
+ gap: var(--layout-sidebar-gap);
35
+ --layout-sidebar-gap: 1rem;
36
+ --layout-sidebar-width: auto;
37
+ --layout-sidebar-content: 50%;
38
+ --layout-sidebar-ff: 1 1 var(--layout-sidebar-width);
39
+ --layout-sidebar-fm: auto;
40
+ --layout-sidebar-lf: 999 1 0;
41
+ --layout-sidebar-lm: var(--layout-sidebar-content);
42
+ display: flex;
43
+ flex-wrap: wrap;
44
+ }
45
+ ._layout-sidebar-core > :first-child {
46
+ flex: var(--layout-sidebar-ff);
47
+ min-inline-size: var(--layout-sidebar-fm);
48
+ }
49
+ ._layout-sidebar-core > :last-child {
50
+ flex: var(--layout-sidebar-lf);
51
+ min-inline-size: var(--layout-sidebar-lm);
52
+ }
53
+ ._layout-switcher-core > * {
54
+ flex-grow: 1;
55
+ flex-basis: calc((var(--layout-switcher-threshold) - 100%) * 999);
56
+ }
57
+ ._layout-grid-core {
58
+ gap: var(--layout-grid-gap);
59
+ --layout-grid-min: 16rem;
60
+ --layout-grid-gap: 1rem;
61
+ display: grid;
62
+ grid-template-columns: repeat(auto-fit, minmax(min(var(--layout-grid-min), 100%), 1fr));
63
+ }
64
+ /* ... the other kernels */
65
+ }
66
+ @layer _.a {
67
+ ._x {
68
+ --layout-stack-gap: 1.5rem;
69
+ }
70
+ ._y {
71
+ --layout-sidebar-width: 20rem;
72
+ }
73
+ /* ... one class per method and value */
74
+ }
75
+ ```
76
+
77
+ ## The primitives
78
+
79
+ | Primitive | Methods (each once, in any order) | What it is |
80
+ | ------------ | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
81
+ | `stack()` | `gap` (1rem) | a flex column with a gap |
82
+ | `cluster()` | `gap` (1rem), `justify` (`flex-start`), `align` (`center`) | a wrapping row: tags, buttons, a toolbar |
83
+ | `sidebar()` | `side` (`left`), `width` (`auto`, its content), `content` (50%), `gap` (1rem) | two children side by side; they stack when the content child would be narrower than `content` of the container |
84
+ | `switcher()` | `threshold` (30rem), `gap` (1rem) | its children in one row while the container is wider than `threshold`, else one column (Heydon Pickering's Flexbox Holy Albatross) |
85
+ | `cover()` | `minHeight` (100vh), `gap` (1rem) | a flex column of a min height with its content centred vertically |
86
+ | `autoGrid()` | `min` (16rem), `gap` (1rem) | as many columns of at least `min` as fit, `repeat(auto-fit, minmax(min(min, 100%), 1fr))`, which never overflows |
87
+
88
+ - **Values.** `gap` takes a length or a percentage, and a lone number is a step of the spacing scale (0.25rem), as in
89
+ `padding`. `width`, `minHeight` and `min` take a length or a percentage, `threshold` a length, `content` a percentage from
90
+ 0 to 100. `justify` is `start | end | center | space-between | space-around | space-evenly`, `align` is `start | end |
91
+ center | baseline | stretch`, `side` is `left | right`. Tokens go where a length does. The types check each method, and
92
+ the build checks it again (ranges included: `content("120%")` fails at the call).
93
+ - **Conditions.** A method in `media()`, `hover()` and the others sets the custom property under that condition:
94
+ `media("(width >= 40rem)")(stack().gap(8))`.
95
+
96
+ ## Rules
97
+
98
+ - **Kernels.** Each primitive is one kernel in layer `_.k`, once, written as styles: the defaults of its custom properties
99
+ on the element (`vars()`), the container's properties and, for `sidebar()` and `switcher()`, the rules of the children
100
+ (`nest("& > *")`). A call is its class (`_layout-stack-core`), and each method one class in `_.a` (or `_.b` under a
101
+ condition), which wins over the defaults.
102
+ - **Conflicts.** A `cn()` that holds a primitive and a property its core sets is a build error at the call, derived from
103
+ the kernel: `cn(stack(), display("grid"))` is
104
+ `cssints: layout: kernel stack-core sets display, flex-direction, gap (this style also sets display)`, and `cn(autoGrid(), rowGap("1rem"))` fails as well (a longhand of `gap`). Padding, sizes and
105
+ colours do not conflict, nor do the properties the kernel sets on the children (`cn(switcher(), flexGrow(0))` is fine). Conflicts with an opaque runtime string in `cn()` are not seen, as for any plugin.
106
+ - **No leak into nested primitives.** The defaults are set on every primitive, so a `stack()` inside a `stack().gap(8)`
107
+ has a gap of 1rem, not 2rem.
108
+ - **The children.** `sidebar()` sizes its first and last child (the first is the sidebar, or the last with
109
+ `side("right")`; the order in the DOM stays the reading order), `switcher()` every child. Those rules are one class
110
+ strong in `_.k`, so a class on a child wins over them.
111
+
112
+ ## Limits
113
+
114
+ - `sidebar()` and `switcher()` pass their settings to the children by inheritance. A child that is itself a sidebar
115
+ (or a switcher) reads its own defaults or settings in its role as a child, not its parent's: wrap it in a plain element
116
+ when the parent's values are not the defaults.
117
+ - `cover()` centres all its content; Every Layout's centred child between a header and a footer is `margin-block: auto`
118
+ on that child, which you write yourself.
119
+ - Not here: Every Layout's `center`, `box`, `frame`, `reel`, `imposter` and `icon` (`box` and `center` are a few plain
120
+ properties; `frame()` in cssints is the concentric radius), and the switcher's limit on the number of items.
121
+
122
+ ```bash
123
+ cd packages/layout && bun run test # fixtures through the engine (output, conflicts, build errors), and the type test via bun run typecheck
124
+ ```
125
+
126
+ ## Credits
127
+
128
+ The primitives are those of [Every Layout](https://every-layout.dev) (Heydon Pickering and Andy Bell) and
129
+ [smolcss](https://smolcss.dev) (Stephanie Eckles); the switcher is Heydon Pickering's Flexbox Holy Albatross. The CSS is
130
+ written here as styles of `scope()`; no code is copied.
@@ -0,0 +1,77 @@
1
+ //#region src/index.d.ts
2
+ /**
3
+ * Layout primitives, each a chain whose methods are used once: `stack().gap(4)`, `cluster().gap(2).justify("space-between")
4
+ * .align("center")`, `sidebar().side("left").width("20rem").content("50%").gap(4)`, `switcher().threshold("30rem").gap(4)`,
5
+ * `cover().minHeight("100vh").gap(4)`, `autoGrid().min("16rem").gap(4)`. A lone number in `gap` is a step of the spacing
6
+ * scale. A primitive sets `display` and the container's properties, so a `cn()` that sets one of them too is a build error.
7
+ */
8
+ export declare const stack: () => import("cssints/plugin").Chain<"stack", {
9
+ readonly gap: {
10
+ readonly emit: (v: string) => [string, string][];
11
+ readonly args: "<length-percentage [0,∞]>";
12
+ readonly spacing: true;
13
+ };
14
+ }, {}>, cluster: () => import("cssints/plugin").Chain<"cluster", {
15
+ readonly gap: {
16
+ readonly emit: (v: string) => [string, string][];
17
+ readonly args: "<length-percentage [0,∞]>";
18
+ readonly spacing: true;
19
+ };
20
+ readonly justify: {
21
+ readonly args: "start | end | center | space-between | space-around | space-evenly";
22
+ readonly emit: (v: string) => [string, string][];
23
+ };
24
+ readonly align: {
25
+ readonly args: "start | end | center | baseline | stretch";
26
+ readonly emit: (v: string) => [string, string][];
27
+ };
28
+ }, {}>, sidebar: () => import("cssints/plugin").Chain<"sidebar", {
29
+ readonly side: {
30
+ readonly args: "left | right";
31
+ readonly emit: (v: string) => [string, string][];
32
+ };
33
+ readonly width: {
34
+ readonly args: "<length-percentage [0,∞]>";
35
+ readonly emit: (v: string) => [string, string][];
36
+ };
37
+ readonly content: {
38
+ readonly args: "<percentage [0,100]>";
39
+ readonly emit: (v: string) => [string, string][];
40
+ };
41
+ readonly gap: {
42
+ readonly emit: (v: string) => [string, string][];
43
+ readonly args: "<length-percentage [0,∞]>";
44
+ readonly spacing: true;
45
+ };
46
+ }, {}>, switcher: () => import("cssints/plugin").Chain<"switcher", {
47
+ readonly threshold: {
48
+ readonly args: "<length [0,∞]>";
49
+ readonly emit: (v: string) => [string, string][];
50
+ };
51
+ readonly gap: {
52
+ readonly emit: (v: string) => [string, string][];
53
+ readonly args: "<length-percentage [0,∞]>";
54
+ readonly spacing: true;
55
+ };
56
+ }, {}>, cover: () => import("cssints/plugin").Chain<"cover", {
57
+ readonly minHeight: {
58
+ readonly args: "<length-percentage [0,∞]>";
59
+ readonly emit: (v: string) => [string, string][];
60
+ };
61
+ readonly gap: {
62
+ readonly emit: (v: string) => [string, string][];
63
+ readonly args: "<length-percentage [0,∞]>";
64
+ readonly spacing: true;
65
+ };
66
+ }, {}>, autoGrid: () => import("cssints/plugin").Chain<"autoGrid", {
67
+ readonly min: {
68
+ readonly args: "<length-percentage [0,∞]>";
69
+ readonly emit: (v: string) => [string, string][];
70
+ };
71
+ readonly gap: {
72
+ readonly emit: (v: string) => [string, string][];
73
+ readonly args: "<length-percentage [0,∞]>";
74
+ readonly spacing: true;
75
+ };
76
+ }, {}>;
77
+ //#endregion
package/dist/index.mjs ADDED
@@ -0,0 +1,176 @@
1
+ import { scope } from "cssints/plugin";
2
+ //#region src/index.ts
3
+ const space = "<length-percentage [0,∞]>";
4
+ const gap = {
5
+ args: space,
6
+ spacing: true
7
+ };
8
+ const narrow = {
9
+ flex: "1 1 var(--_sidebar-width)",
10
+ min: "auto"
11
+ };
12
+ const wide = {
13
+ flex: "999 1 0",
14
+ min: "var(--_sidebar-content)"
15
+ };
16
+ const sides = (first, last) => Object.entries({
17
+ "--_sidebar-ff": first.flex,
18
+ "--_sidebar-fm": first.min,
19
+ "--_sidebar-lf": last.flex,
20
+ "--_sidebar-lm": last.min
21
+ });
22
+ /**
23
+ * Layout primitives, each a chain whose methods are used once: `stack().gap(4)`, `cluster().gap(2).justify("space-between")
24
+ * .align("center")`, `sidebar().side("left").width("20rem").content("50%").gap(4)`, `switcher().threshold("30rem").gap(4)`,
25
+ * `cover().minHeight("100vh").gap(4)`, `autoGrid().min("16rem").gap(4)`. A lone number in `gap` is a step of the spacing
26
+ * scale. A primitive sets `display` and the container's properties, so a `cn()` that sets one of them too is a build error.
27
+ */
28
+ const { stack, cluster, sidebar, switcher, cover, autoGrid } = scope("layout", {
29
+ "#stack-core": { css: (css) => [
30
+ css.vars({ "--_stack-gap": "1rem" }),
31
+ css.display("flex"),
32
+ css.flexDirection("column"),
33
+ css.gap("var(--_stack-gap)")
34
+ ] },
35
+ stack: {
36
+ uses: ["stack-core"],
37
+ chain: { gap: {
38
+ ...gap,
39
+ emit: (v) => [["--_stack-gap", v]]
40
+ } }
41
+ },
42
+ "#cluster-core": { css: (css) => [
43
+ css.vars({
44
+ "--_cluster-gap": "1rem",
45
+ "--_cluster-justify": "flex-start",
46
+ "--_cluster-align": "center"
47
+ }),
48
+ css.display("flex"),
49
+ css.flexWrap("wrap"),
50
+ css.gap("var(--_cluster-gap)"),
51
+ css.justifyContent("var(--_cluster-justify)"),
52
+ css.alignItems("var(--_cluster-align)")
53
+ ] },
54
+ cluster: {
55
+ uses: ["cluster-core"],
56
+ chain: {
57
+ gap: {
58
+ ...gap,
59
+ emit: (v) => [["--_cluster-gap", v]]
60
+ },
61
+ justify: {
62
+ args: "start | end | center | space-between | space-around | space-evenly",
63
+ emit: (v) => [["--_cluster-justify", v]]
64
+ },
65
+ align: {
66
+ args: "start | end | center | baseline | stretch",
67
+ emit: (v) => [["--_cluster-align", v]]
68
+ }
69
+ }
70
+ },
71
+ "#sidebar-core": { css: (css) => [
72
+ css.vars({
73
+ "--_sidebar-gap": "1rem",
74
+ "--_sidebar-width": "auto",
75
+ "--_sidebar-content": "50%",
76
+ ...Object.fromEntries(sides(narrow, wide))
77
+ }),
78
+ css.display("flex"),
79
+ css.flexWrap("wrap"),
80
+ css.gap("var(--_sidebar-gap)"),
81
+ css.nest("& > :first-child")(css.flex("var(--_sidebar-ff)"), css.minInlineSize("var(--_sidebar-fm)")),
82
+ css.nest("& > :last-child")(css.flex("var(--_sidebar-lf)"), css.minInlineSize("var(--_sidebar-lm)"))
83
+ ] },
84
+ sidebar: {
85
+ uses: ["sidebar-core"],
86
+ chain: {
87
+ side: {
88
+ args: "left | right",
89
+ emit: (v) => v === "right" ? sides(wide, narrow) : sides(narrow, wide)
90
+ },
91
+ width: {
92
+ args: space,
93
+ emit: (v) => [["--_sidebar-width", v]]
94
+ },
95
+ content: {
96
+ args: "<percentage [0,100]>",
97
+ emit: (v) => [["--_sidebar-content", v]]
98
+ },
99
+ gap: {
100
+ ...gap,
101
+ emit: (v) => [["--_sidebar-gap", v]]
102
+ }
103
+ }
104
+ },
105
+ "#switcher-core": { css: (css) => [
106
+ css.vars({
107
+ "--_switcher-gap": "1rem",
108
+ "--_switcher-threshold": "30rem"
109
+ }),
110
+ css.display("flex"),
111
+ css.flexWrap("wrap"),
112
+ css.gap("var(--_switcher-gap)"),
113
+ css.nest("& > *")(css.flexGrow(1), css.flexBasis("calc((var(--_switcher-threshold) - 100%) * 999)"))
114
+ ] },
115
+ switcher: {
116
+ uses: ["switcher-core"],
117
+ chain: {
118
+ threshold: {
119
+ args: "<length [0,∞]>",
120
+ emit: (v) => [["--_switcher-threshold", v]]
121
+ },
122
+ gap: {
123
+ ...gap,
124
+ emit: (v) => [["--_switcher-gap", v]]
125
+ }
126
+ }
127
+ },
128
+ "#cover-core": { css: (css) => [
129
+ css.vars({
130
+ "--_cover-min": "100vh",
131
+ "--_cover-gap": "1rem"
132
+ }),
133
+ css.display("flex"),
134
+ css.flexDirection("column"),
135
+ css.justifyContent("center"),
136
+ css.minBlockSize("var(--_cover-min)"),
137
+ css.gap("var(--_cover-gap)")
138
+ ] },
139
+ cover: {
140
+ uses: ["cover-core"],
141
+ chain: {
142
+ minHeight: {
143
+ args: space,
144
+ emit: (v) => [["--_cover-min", v]]
145
+ },
146
+ gap: {
147
+ ...gap,
148
+ emit: (v) => [["--_cover-gap", v]]
149
+ }
150
+ }
151
+ },
152
+ "#grid-core": { css: (css) => [
153
+ css.vars({
154
+ "--_grid-min": "16rem",
155
+ "--_grid-gap": "1rem"
156
+ }),
157
+ css.display("grid"),
158
+ css.gridTemplateColumns("repeat(auto-fit, minmax(min(var(--_grid-min), 100%), 1fr))"),
159
+ css.gap("var(--_grid-gap)")
160
+ ] },
161
+ autoGrid: {
162
+ uses: ["grid-core"],
163
+ chain: {
164
+ min: {
165
+ args: space,
166
+ emit: (v) => [["--_grid-min", v]]
167
+ },
168
+ gap: {
169
+ ...gap,
170
+ emit: (v) => [["--_grid-gap", v]]
171
+ }
172
+ }
173
+ }
174
+ }).export();
175
+ //#endregion
176
+ export { autoGrid, cluster, cover, sidebar, stack, switcher };
package/package.json CHANGED
@@ -1,6 +1,32 @@
1
1
  {
2
2
  "name": "@cssints/layout",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Layout primitives for cssints: stack, cluster, sidebar, switcher, cover and autoGrid as chain members",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/alexeiaccio/css-in-ts-compiler.git",
9
+ "directory": "packages/layout"
10
+ },
11
+ "homepage": "https://cssints.dev",
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.mts",
20
+ "default": "./dist/index.mjs"
21
+ }
22
+ },
23
+ "peerDependencies": {
24
+ "cssints": "^0.2.0"
25
+ },
26
+ "engines": {
27
+ "node": ">=22"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ }
32
+ }