fashionable 0.0.0 → 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.md +21 -0
- package/README.md +276 -0
- package/dist/calc/index.d.mts +2 -0
- package/dist/calc/index.mjs +2 -0
- package/dist/data/index.d.mts +208 -0
- package/dist/data/index.mjs +696 -0
- package/dist/data/index.mjs.map +1 -0
- package/dist/declaration/index.d.mts +2 -0
- package/dist/declaration/index.mjs +74 -0
- package/dist/declaration/index.mjs.map +1 -0
- package/dist/fontFace/index.d.mts +2 -0
- package/dist/fontFace/index.mjs +102 -0
- package/dist/fontFace/index.mjs.map +1 -0
- package/dist/property/index.d.mts +2 -0
- package/dist/property/index.mjs +293 -0
- package/dist/property/index.mjs.map +1 -0
- package/dist/query/index.d.mts +2 -0
- package/dist/query/index.mjs +62 -0
- package/dist/query/index.mjs.map +1 -0
- package/dist/rule/index.d.mts +2 -0
- package/dist/rule/index.mjs +234 -0
- package/dist/rule/index.mjs.map +1 -0
- package/dist/selector/index.d.mts +2 -0
- package/dist/selector/index.mjs +178 -0
- package/dist/selector/index.mjs.map +1 -0
- package/dist/shared/calc-Cih4o2r7.mjs +1145 -0
- package/dist/shared/calc-Cih4o2r7.mjs.map +1 -0
- package/dist/shared/color-BvNJ2YqL.d.mts +590 -0
- package/dist/shared/color.internal-Dts5ycTm.mjs +450 -0
- package/dist/shared/color.internal-Dts5ycTm.mjs.map +1 -0
- package/dist/shared/declaration-1DlO_ltT.d.mts +166 -0
- package/dist/shared/declaration.internal-wLB4ssxC.mjs +73 -0
- package/dist/shared/declaration.internal-wLB4ssxC.mjs.map +1 -0
- package/dist/shared/equal-U9G5LhtL.mjs +71 -0
- package/dist/shared/equal-U9G5LhtL.mjs.map +1 -0
- package/dist/shared/fontFaceRule-C7nYgH6X.d.mts +294 -0
- package/dist/shared/fontFaceRule.internal-D5u48TUO.mjs +214 -0
- package/dist/shared/fontFaceRule.internal-D5u48TUO.mjs.map +1 -0
- package/dist/shared/format-B9jsmCER.mjs +53 -0
- package/dist/shared/format-B9jsmCER.mjs.map +1 -0
- package/dist/shared/index-BvtwY4FQ.d.mts +841 -0
- package/dist/shared/mediaQuery-BYR1z-iD.d.mts +145 -0
- package/dist/shared/mediaQuery.internal-B6iuMd75.mjs +100 -0
- package/dist/shared/mediaQuery.internal-B6iuMd75.mjs.map +1 -0
- package/dist/shared/mediaRule-BDB4WCYy.d.mts +558 -0
- package/dist/shared/precision.internal-0lv1nEpF.mjs +40 -0
- package/dist/shared/precision.internal-0lv1nEpF.mjs.map +1 -0
- package/dist/shared/propertyRule-BbkFh9b9.d.mts +466 -0
- package/dist/shared/propertyRule.internal-Bc_HrfcL.mjs +246 -0
- package/dist/shared/propertyRule.internal-Bc_HrfcL.mjs.map +1 -0
- package/dist/shared/render-BqcFH9Vr.mjs +9 -0
- package/dist/shared/render-BqcFH9Vr.mjs.map +1 -0
- package/dist/shared/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/shared/ruleSet.internal-DodzVMUU.mjs +218 -0
- package/dist/shared/ruleSet.internal-DodzVMUU.mjs.map +1 -0
- package/dist/shared/selector-HZY-W6l6.d.mts +346 -0
- package/dist/shared/selector.internal-ESe9s0IH.mjs +290 -0
- package/dist/shared/selector.internal-ESe9s0IH.mjs.map +1 -0
- package/dist/shared/utils-BKm298I-.d.mts +179 -0
- package/dist/stylesheet/index.d.mts +316 -0
- package/dist/stylesheet/index.mjs +296 -0
- package/dist/stylesheet/index.mjs.map +1 -0
- package/dist/utils.d.mts +2 -0
- package/dist/utils.mjs +131 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +96 -8
- package/index.js +0 -1
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tim Morris
|
|
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
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# fashionable
|
|
2
|
+
|
|
3
|
+
Structural stylesheet modeling and calc expression evaluation for TypeScript (npm: `fashionable`).
|
|
4
|
+
|
|
5
|
+
An Effect-style API for building CSS in code: a faithful structural model of the CSS language — rules, selectors, media queries, at-rules, nesting — unified with a `calc()` value-expression language that can be solved against bindings or serialized to CSS text. Immutable values, structural equality, deterministic output, zero runtime dependencies. No template literals anywhere.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install fashionable
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Typed calc
|
|
12
|
+
|
|
13
|
+
A `Calc` is a CSS math expression you can serialize to CSS or solve to a number — the same tree, two projections, so the thing you verified is the thing you shipped. The type tracks the expression's unbound references, its dimension (`<number>`, `<length>`, `<angle>`, or `<percentage>`), and the units it mentions.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Calc } from 'fashionable/calc'
|
|
17
|
+
import { Length } from 'fashionable/data'
|
|
18
|
+
|
|
19
|
+
const fluid = Calc.clamp(
|
|
20
|
+
Length.rem(1),
|
|
21
|
+
Calc.add(Length.rem(0.75), Length.vw(0.5)),
|
|
22
|
+
Length.rem(1.25),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
Calc.serialize(fluid) // 'clamp(1rem, 0.75rem + 0.5vw, 1.25rem)'
|
|
26
|
+
Calc.solve(fluid, {}, { rem: 16, vw: 1280 / 100 }) // 18.4 — at a 1280px viewport, 16px root
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`solve` lowers relative units through a context of px ratios, typed by the expression's units so the context can neither miss nor mismatch a key. Absolute lengths and angles solve with no context. `Length`, `Angle`, and `Percentage` in `fashionable/data` supply the unit constructors — `Length.px` through `Length.vmax`, `Angle.rad` and `Angle.deg`, `Percentage.of` — with `Unit` naming the unit types. A percentage serializes but does not solve.
|
|
30
|
+
|
|
31
|
+
References are CSS custom properties: `Calc.ref('space')` reads `var(--space)`, and `bind` substitutes and partially evaluates.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const gap = Calc.multiply(Calc.ref('space'), 2)
|
|
35
|
+
|
|
36
|
+
Calc.serialize(gap) // 'calc(var(--space) * 2)'
|
|
37
|
+
Calc.solve(gap.pipe(Calc.bind({ space: 4 }))) // 8
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The dimensional rules follow CSS and live in the types: `add`, `subtract`, `min`, `max`, and `clamp` require a shared kind, `multiply` scales a dimension by a number, and `divide` of two like dimensions is a number. Illegal combinations fail to typecheck rather than emitting invalid CSS.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// @ts-expect-error — a <length> plus a <number> has no CSS meaning
|
|
44
|
+
Calc.add(Length.px(10), 5)
|
|
45
|
+
// @ts-expect-error — two lengths do not multiply
|
|
46
|
+
Calc.multiply(Length.px(10), Length.px(10))
|
|
47
|
+
|
|
48
|
+
const ratio = Calc.divide(Calc.subtract(Length.vw(100), Length.px(320)), Length.px(160))
|
|
49
|
+
|
|
50
|
+
Calc.serialize(ratio) // 'calc((100vw - 320px) / 160px)' — a <length> over a <length> is a <number>
|
|
51
|
+
Calc.solve(ratio, {}, { vw: 1280 / 100 }) // 6
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Firefox does not yet support `<length> / <length>` division in `calc()`; `tan(atan2(y, x))` is the portable spelling of the same ratio.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const portable = Calc.tan(Calc.atan2(Calc.subtract(Length.vw(100), Length.px(320)), Length.px(160)))
|
|
58
|
+
|
|
59
|
+
Calc.serialize(portable) // 'tan(atan2(100vw - 320px, 160px))'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`mod`, `pow`/`signedPow`, `lerp`, `abs`, and `sin`/`cos`/`acos` round out the combinators. Trig follows CSS's typing: `acos` and `atan2` return an `<angle>`, and `sin`/`cos`/`tan` accept an `<angle>` or a plain number in radians.
|
|
63
|
+
|
|
64
|
+
Constants format under an explicit precision model: five decimals by default, overridable per serialize call, and per constant with an annotation that carries through constant folding.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { Precision } from 'fashionable/calc'
|
|
68
|
+
|
|
69
|
+
Calc.serialize(Calc.of(0.8377580409572781)) // '0.83776'
|
|
70
|
+
Calc.serialize(Calc.of(0.8377580409572781, Precision.significant(10))) // '0.837758041'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The full precision model lives in [docs/design.md](https://github.com/tkofh/fashionable/blob/main/docs/design.md).
|
|
74
|
+
|
|
75
|
+
## Color
|
|
76
|
+
|
|
77
|
+
A `Color` models a CSS color value structurally. Channels are `Calc` number expressions, so a channel can be a constant, a reference, arithmetic, or CSS's `none` keyword. A `Color` serializes and binds; it does not solve.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { Calc } from 'fashionable/calc'
|
|
81
|
+
import { Channel, Color, ColorSpace, HueInterpolation, Keyword } from 'fashionable/data'
|
|
82
|
+
|
|
83
|
+
Color.serialize(Color.oklch(0.7, 0.15, Calc.ref('hue'))) // 'oklch(0.7 0.15 var(--hue))'
|
|
84
|
+
Color.serialize(Color.srgb(0.18, 0.34, 0.78)) // 'color(srgb 0.18 0.34 0.78)'
|
|
85
|
+
Color.serialize(Color.oklch(0.2, 0, Keyword.none)) // 'oklch(0.2 0 none)'
|
|
86
|
+
|
|
87
|
+
Color.oklch(Calc.ref('l'), 0.15, 250).pipe(Color.bind({ l: 0.7 })) // serializes 'oklch(0.7 0.15 250)'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Relative color syntax.** `Color.from` derives a color from another — including one the browser resolves from the cascade. The destination space exposes the origin's channels as `Channel` keywords and scopes them: `Channel.L` belongs to `oklch`, `Channel.R` to `srgb`, and a keyword the space does not name is a compile error.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
const hover = Color.from(
|
|
94
|
+
Color.ref('accent'), // reads the whole color from var(--accent)
|
|
95
|
+
ColorSpace.oklch,
|
|
96
|
+
Calc.multiply(Channel.L, 0.8),
|
|
97
|
+
Channel.C,
|
|
98
|
+
Channel.H,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
Color.serialize(hover) // 'oklch(from var(--accent) calc(l * 0.8) c h)'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Mixing.** `Color.mix` models `color-mix()`. Arms are colors or `[color, weight]` tuples, weights read as percents. A polar space admits a `HueInterpolation` strategy after it; a strategy after a rectangular space is a compile error, matching the grammar.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const wash = Color.mix(ColorSpace.oklch, [Color.ref('brand'), 60], Color.named('white'))
|
|
108
|
+
|
|
109
|
+
Color.serialize(wash) // 'color-mix(in oklch, var(--brand) 60%, white)'
|
|
110
|
+
|
|
111
|
+
const arc = Color.mix(
|
|
112
|
+
ColorSpace.oklch,
|
|
113
|
+
HueInterpolation.longer,
|
|
114
|
+
Color.oklch(0.7, 0.15, 30),
|
|
115
|
+
Color.oklch(0.7, 0.15, 330),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
Color.serialize(arc) // 'color-mix(in oklch longer hue, oklch(0.7 0.15 30), oklch(0.7 0.15 330))'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Hue interpolation.** `HueInterpolation.interpolate` is the JS side of that hue arc: it builds the hue at `t` along the chosen strategy — the CSS Color 4 hue fixup written branchlessly with `mod` — folding to a constant when every argument is a number and staying symbolic otherwise. Your build and the browser compute the same fixup.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const hue = HueInterpolation.interpolate(
|
|
125
|
+
HueInterpolation.shorter,
|
|
126
|
+
30,
|
|
127
|
+
Calc.ref('to'),
|
|
128
|
+
Calc.ref('t'),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
Calc.serialize(hue) // 'calc(30 + (mod(var(--to) - 30 + 180, 360) - 180) * var(--t))'
|
|
132
|
+
Calc.serialize(HueInterpolation.interpolate(HueInterpolation.increasing, 20, 350, 0.5)) // '185'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Whole-value colors.** `Color.lightDark` models scheme-conditional `light-dark()` with whole colors in each arm. `Color.ref` reads an entire color from a custom property, `Color.named` renders a named color bare, and `Color.transparent` is the blessed no-color constant. All of them compose anywhere a `Color` goes.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const scheme = Color.lightDark(Color.oklch(0.98, 0.01, 250), Color.oklch(0.18, 0.02, 250))
|
|
139
|
+
|
|
140
|
+
Color.serialize(scheme) // 'light-dark(oklch(0.98 0.01 250), oklch(0.18 0.02 250))'
|
|
141
|
+
Color.serialize(Color.lightDark(Color.ref('surface'), Color.transparent))
|
|
142
|
+
// 'light-dark(var(--surface), transparent)'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Stylesheets
|
|
146
|
+
|
|
147
|
+
Build a stylesheet from typed parts — selectors, declarations, rules, nested media — then render it to CSS.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { Calc } from 'fashionable/calc'
|
|
151
|
+
import { Length } from 'fashionable/data'
|
|
152
|
+
import { Declaration } from 'fashionable/declaration'
|
|
153
|
+
import { MediaQuery } from 'fashionable/query'
|
|
154
|
+
import { MediaRule, RuleSet, StyleRule } from 'fashionable/rule'
|
|
155
|
+
import { Selector } from 'fashionable/selector'
|
|
156
|
+
import { Stylesheet } from 'fashionable/stylesheet'
|
|
157
|
+
|
|
158
|
+
const card = StyleRule.make(
|
|
159
|
+
Selector.class('card'),
|
|
160
|
+
RuleSet.make(
|
|
161
|
+
Declaration.make('padding', Length.rem(1)),
|
|
162
|
+
Declaration.make('gap', Calc.multiply(Calc.ref('space'), 2)),
|
|
163
|
+
MediaRule.make(
|
|
164
|
+
MediaQuery.minWidth(768),
|
|
165
|
+
RuleSet.make(Declaration.make('gap', Calc.multiply(Calc.ref('space'), 3))),
|
|
166
|
+
),
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
const sheet = Stylesheet.make(card) // Stylesheet<'space'>
|
|
171
|
+
|
|
172
|
+
Stylesheet.render(sheet, { indent: ' ' })
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Nesting is preserved as authored — `@media` stays inside its rule:
|
|
176
|
+
|
|
177
|
+
```css
|
|
178
|
+
.card {
|
|
179
|
+
padding: 1rem;
|
|
180
|
+
gap: calc(var(--space) * 2);
|
|
181
|
+
@media (min-width: 768px) {
|
|
182
|
+
gap: calc(var(--space) * 3);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Selectors are compound selectors built from typed parts — `type`, `id`, `class`, `attribute`, `not`, pseudo-classes and pseudo-elements — with computed `Specificity`. Media queries are and-sets of `minWidth` and `prefersColorScheme`. Both canonicalize at construction, so structural equality holds regardless of construction order and rendering is deterministic. Declaration and rule order is never touched: member order is cascade behavior, and the library preserves it.
|
|
188
|
+
|
|
189
|
+
A block built up through `pipe` caps off as a rule with `forSelector` or `forMediaQuery`:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
RuleSet.make(Declaration.make('--depth', Calc.ref('depth'))).pipe(
|
|
193
|
+
RuleSet.forSelector(Selector.root),
|
|
194
|
+
) // StyleRule<'depth'> — renders ':root { --depth: var(--depth); }'
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Refs
|
|
198
|
+
|
|
199
|
+
Every value and container is generic over `Refs`, the CSS custom properties it reads but has not bound. `Calc.ref('space')` is a `Calc<'space'>` and `Color.ref('accent')` is a `Color<'accent'>`. Combining values unions their `Refs`, and `bind` subtracts the names it binds. A closed expression — fully bound or constant — is a `Calc<never>`, which is what `solve` accepts with no bindings.
|
|
200
|
+
|
|
201
|
+
The parameter threads up through the model, so `Declaration<Refs>`, `RuleSet<Refs>`, `StyleRule<Refs>`, and `Stylesheet<Refs>` carry the union of the refs they contain. The `Stylesheet<'space'>` above is the compiler reporting that the sheet reads `var(--space)` and nothing else. `Stylesheet.refs` returns the same set at runtime. Unbound refs serialize as `var(--name)` — the reference channel is the custom-property channel.
|
|
202
|
+
|
|
203
|
+
## `@property` and `@font-face`
|
|
204
|
+
|
|
205
|
+
`PropertyRule` models `@property`. The `PropertySyntax` value in the `syntax` slot types the initial value, and the spec's computational-independence rule rides along: `Length.px(8)` registers under `PropertySyntax.length`, and `Length.vw(8)` is a compile error. Rules register with `inherits: false`; pipe through `PropertyRule.inheritable` to opt in.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { PropertyRule, PropertySyntax } from 'fashionable/property'
|
|
209
|
+
|
|
210
|
+
const depth = PropertyRule.make('--depth', PropertySyntax.number, 0)
|
|
211
|
+
|
|
212
|
+
PropertyRule.render(depth, { indent: ' ' })
|
|
213
|
+
// @property --depth {
|
|
214
|
+
// syntax: '<number>';
|
|
215
|
+
// inherits: false;
|
|
216
|
+
// initial-value: 0;
|
|
217
|
+
// }
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`FontFaceRule` models `@font-face`: multi-source `src`, weight ranges, metric overrides, and `unicode-range`.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { FontFaceRule } from 'fashionable/font-face'
|
|
224
|
+
|
|
225
|
+
const inter = FontFaceRule.make({
|
|
226
|
+
family: 'Inter',
|
|
227
|
+
src: [FontFaceRule.url('/fonts/inter.woff2', 'woff2'), FontFaceRule.local('Inter')],
|
|
228
|
+
weight: [400, 700],
|
|
229
|
+
display: 'swap',
|
|
230
|
+
unicodeRange: [[0x400, 0x4ff]],
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
FontFaceRule.render(inter, { indent: ' ' })
|
|
234
|
+
// @font-face {
|
|
235
|
+
// font-family: 'Inter';
|
|
236
|
+
// font-weight: 400 700;
|
|
237
|
+
// font-display: swap;
|
|
238
|
+
// src:
|
|
239
|
+
// url('/fonts/inter.woff2') format('woff2'),
|
|
240
|
+
// local('Inter');
|
|
241
|
+
// unicode-range: U+400-4FF;
|
|
242
|
+
// }
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Merging and coalescing
|
|
246
|
+
|
|
247
|
+
`Stylesheet.merge` is a lawful monoid — associative, idempotent, `empty` as identity — that keeps both sides' order and collapses structural duplicates to their first occurrence. That is the multi-emitter fold: emitters that each register the rules they share merge to a sheet carrying one copy.
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { Stylesheet } from 'fashionable/stylesheet'
|
|
251
|
+
|
|
252
|
+
const contract = Stylesheet.make(PropertyRule.make('--depth', PropertySyntax.number, 0))
|
|
253
|
+
|
|
254
|
+
Stylesheet.merge(contract, contract) === contract // true — idempotent, by reference
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
`coalesce` folds style rules that share a selector into the first occurrence:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
const sheet = Stylesheet.make(
|
|
261
|
+
StyleRule.make(Selector.root, RuleSet.make(Declaration.make('--a', 1))),
|
|
262
|
+
StyleRule.make(Selector.root, RuleSet.make(Declaration.make('--b', 2))),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
Stylesheet.render(Stylesheet.coalesce(sheet), { indent: ' ' })
|
|
266
|
+
// :root {
|
|
267
|
+
// --a: 1;
|
|
268
|
+
// --b: 2;
|
|
269
|
+
// }
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Coalescing is order-sensitive: pulling a block backward past a rule whose selector ties on specificity can change the cascade. So it stays an explicit opt-in, and `coalesce(sheet, { strict: true })` throws on any such pull — a build gate proving the normalization is cascade-preserving.
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MIT
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { C as Vmin, S as Vmax, T as units_d_exports, _ as Percentage$1, b as Rem, c as Precision, d as Angle$1, f as ChannelLeaf, g as Percent, h as Length$1, m as Em, p as Deg, r as Calc, v as Px, w as Vw, x as Vh, y as Rad } from "../shared/index-BvtwY4FQ.mjs";
|
|
2
|
+
import { a as colorSpace_d_exports, i as hueInterpolation_d_exports, n as color_d_exports, r as keywords_d_exports } from "../shared/color-BvNJ2YqL.mjs";
|
|
3
|
+
declare namespace angle_d_exports {
|
|
4
|
+
export { Angle, deg, rad };
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* An `<angle>` expression: a `Calc` of angle kind, in any angle unit. Names the
|
|
8
|
+
* dimension without spelling `Calc<Refs, 'angle', Unit.Angle>` — `Angle.rad(2)`
|
|
9
|
+
* produces one, and it composes with every `Calc` combinator (subtracting two
|
|
10
|
+
* angles is an angle, scaling by a number stays an angle). `Refs` unions the
|
|
11
|
+
* unbound reference names, as on `Calc`.
|
|
12
|
+
*
|
|
13
|
+
* The leaf is the widened `Unit.Angle`, so every specific angle expression is
|
|
14
|
+
* assignable to it; a constructor narrows it (`Angle.rad` carries `Unit.Rad`).
|
|
15
|
+
*
|
|
16
|
+
* @since 0.2.0
|
|
17
|
+
*/
|
|
18
|
+
type Angle<Refs extends string = string> = Calc<Refs, 'angle', Angle$1>;
|
|
19
|
+
/**
|
|
20
|
+
* An angle in `rad` (radians). Radians are the numeric measure of an angle, so
|
|
21
|
+
* a radian-only expression solves with no unit context.
|
|
22
|
+
*
|
|
23
|
+
* @param value - The angle in radians.
|
|
24
|
+
* @param precision - Optional serialization precision.
|
|
25
|
+
* @returns A `rad` angle expression.
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* Calc.serialize(Angle.rad(1.5708)) // '1.5708rad'
|
|
29
|
+
* ```
|
|
30
|
+
* @since 0.2.0
|
|
31
|
+
*/
|
|
32
|
+
declare const rad: (value: number, precision?: Precision) => Calc<never, 'angle', Rad>;
|
|
33
|
+
/**
|
|
34
|
+
* An angle in `deg` (degrees). Degrees lower to radians at solve (`180deg` is
|
|
35
|
+
* `pi`), a fixed ratio, so a degree-only expression solves with no unit context.
|
|
36
|
+
*
|
|
37
|
+
* @param value - The angle in degrees.
|
|
38
|
+
* @param precision - Optional serialization precision.
|
|
39
|
+
* @returns A `deg` angle expression.
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* Calc.serialize(Angle.deg(45)) // '45deg'
|
|
43
|
+
* ```
|
|
44
|
+
* @since 0.2.0
|
|
45
|
+
*/
|
|
46
|
+
declare const deg: (value: number, precision?: Precision) => Calc<never, 'angle', Deg>;
|
|
47
|
+
declare namespace channels_d_exports {
|
|
48
|
+
export { Alpha, B, C, G, H, L, R };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The `l` origin channel — lightness in `oklch`. Serializes bare as `l`.
|
|
52
|
+
*
|
|
53
|
+
* @since 0.2.0
|
|
54
|
+
*/
|
|
55
|
+
declare const L: Calc<never, 'number', ChannelLeaf<'l'>>;
|
|
56
|
+
/**
|
|
57
|
+
* The `c` origin channel — chroma in `oklch`. Serializes bare as `c`.
|
|
58
|
+
*
|
|
59
|
+
* @since 0.2.0
|
|
60
|
+
*/
|
|
61
|
+
declare const C: Calc<never, 'number', ChannelLeaf<'c'>>;
|
|
62
|
+
/**
|
|
63
|
+
* The `h` origin channel — hue in `oklch`, in degrees. Serializes bare as `h`.
|
|
64
|
+
*
|
|
65
|
+
* @since 0.2.0
|
|
66
|
+
*/
|
|
67
|
+
declare const H: Calc<never, 'number', ChannelLeaf<'h'>>;
|
|
68
|
+
/**
|
|
69
|
+
* The `r` origin channel — red in `color(srgb ...)`. Serializes bare as `r`.
|
|
70
|
+
*
|
|
71
|
+
* @since 0.2.0
|
|
72
|
+
*/
|
|
73
|
+
declare const R: Calc<never, 'number', ChannelLeaf<'r'>>;
|
|
74
|
+
/**
|
|
75
|
+
* The `g` origin channel — green in `color(srgb ...)`. Serializes bare as `g`.
|
|
76
|
+
*
|
|
77
|
+
* @since 0.2.0
|
|
78
|
+
*/
|
|
79
|
+
declare const G: Calc<never, 'number', ChannelLeaf<'g'>>;
|
|
80
|
+
/**
|
|
81
|
+
* The `b` origin channel — blue in `color(srgb ...)`. Serializes bare as `b`.
|
|
82
|
+
*
|
|
83
|
+
* @since 0.2.0
|
|
84
|
+
*/
|
|
85
|
+
declare const B: Calc<never, 'number', ChannelLeaf<'b'>>;
|
|
86
|
+
/**
|
|
87
|
+
* The `alpha` origin channel — the opacity of the `from` origin, in `oklch`
|
|
88
|
+
* and `color(srgb ...)` alike. Serializes bare as `alpha`.
|
|
89
|
+
*
|
|
90
|
+
* @since 0.2.0
|
|
91
|
+
*/
|
|
92
|
+
declare const Alpha: Calc<never, 'number', ChannelLeaf<'alpha'>>;
|
|
93
|
+
declare namespace length_d_exports {
|
|
94
|
+
export { Length, em, px, rem, vh, vmax, vmin, vw };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A `<length>` expression: a `Calc` of length kind, in any length unit. Names
|
|
98
|
+
* the dimension without spelling `Calc<Refs, 'length', Unit.Length>` —
|
|
99
|
+
* `Length.px(16)` produces one, and it composes with every `Calc` combinator
|
|
100
|
+
* (adding two lengths is a length, dividing one by another is a number). `Refs`
|
|
101
|
+
* unions the unbound reference names, as on `Calc`.
|
|
102
|
+
*
|
|
103
|
+
* The leaf is the widened `Unit.Length`, so a mixed-unit sum
|
|
104
|
+
* (`Calc.add(Length.px(16), Length.vw(2))`) and every single-unit length are
|
|
105
|
+
* alike assignable to it.
|
|
106
|
+
*
|
|
107
|
+
* @since 0.2.0
|
|
108
|
+
*/
|
|
109
|
+
type Length<Refs extends string = string> = Calc<Refs, 'length', Length$1>;
|
|
110
|
+
/**
|
|
111
|
+
* A length in `px` (absolute pixels).
|
|
112
|
+
*
|
|
113
|
+
* @param value - The pixel count.
|
|
114
|
+
* @param precision - Optional serialization precision.
|
|
115
|
+
* @returns A `px` length expression.
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* Calc.serialize(Length.px(16)) // '16px'
|
|
119
|
+
* ```
|
|
120
|
+
* @since 0.2.0
|
|
121
|
+
*/
|
|
122
|
+
declare const px: (value: number, precision?: Precision) => Calc<never, 'length', Px>;
|
|
123
|
+
/**
|
|
124
|
+
* A length in `rem` (relative to the root font size).
|
|
125
|
+
*
|
|
126
|
+
* @param value - The multiple of the root font size.
|
|
127
|
+
* @param precision - Optional serialization precision.
|
|
128
|
+
* @returns A `rem` length expression.
|
|
129
|
+
* @since 0.2.0
|
|
130
|
+
*/
|
|
131
|
+
declare const rem: (value: number, precision?: Precision) => Calc<never, 'length', Rem>;
|
|
132
|
+
/**
|
|
133
|
+
* A length in `em` (relative to the element font size).
|
|
134
|
+
*
|
|
135
|
+
* @param value - The multiple of the element font size.
|
|
136
|
+
* @param precision - Optional serialization precision.
|
|
137
|
+
* @returns An `em` length expression.
|
|
138
|
+
* @since 0.2.0
|
|
139
|
+
*/
|
|
140
|
+
declare const em: (value: number, precision?: Precision) => Calc<never, 'length', Em>;
|
|
141
|
+
/**
|
|
142
|
+
* A length in `vw` (1% of the viewport width).
|
|
143
|
+
*
|
|
144
|
+
* @param value - The percentage of viewport width.
|
|
145
|
+
* @param precision - Optional serialization precision.
|
|
146
|
+
* @returns A `vw` length expression.
|
|
147
|
+
* @since 0.2.0
|
|
148
|
+
*/
|
|
149
|
+
declare const vw: (value: number, precision?: Precision) => Calc<never, 'length', Vw>;
|
|
150
|
+
/**
|
|
151
|
+
* A length in `vh` (1% of the viewport height).
|
|
152
|
+
*
|
|
153
|
+
* @param value - The percentage of viewport height.
|
|
154
|
+
* @param precision - Optional serialization precision.
|
|
155
|
+
* @returns A `vh` length expression.
|
|
156
|
+
* @since 0.2.0
|
|
157
|
+
*/
|
|
158
|
+
declare const vh: (value: number, precision?: Precision) => Calc<never, 'length', Vh>;
|
|
159
|
+
/**
|
|
160
|
+
* A length in `vmin` (1% of the smaller viewport axis).
|
|
161
|
+
*
|
|
162
|
+
* @param value - The percentage of the smaller viewport axis.
|
|
163
|
+
* @param precision - Optional serialization precision.
|
|
164
|
+
* @returns A `vmin` length expression.
|
|
165
|
+
* @since 0.2.0
|
|
166
|
+
*/
|
|
167
|
+
declare const vmin: (value: number, precision?: Precision) => Calc<never, 'length', Vmin>;
|
|
168
|
+
/**
|
|
169
|
+
* A length in `vmax` (1% of the larger viewport axis).
|
|
170
|
+
*
|
|
171
|
+
* @param value - The percentage of the larger viewport axis.
|
|
172
|
+
* @param precision - Optional serialization precision.
|
|
173
|
+
* @returns A `vmax` length expression.
|
|
174
|
+
* @since 0.2.0
|
|
175
|
+
*/
|
|
176
|
+
declare const vmax: (value: number, precision?: Precision) => Calc<never, 'length', Vmax>;
|
|
177
|
+
declare namespace percentage_d_exports {
|
|
178
|
+
export { Percentage, of };
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* A `<percentage>` expression: a `Calc` of percentage kind. Names the dimension
|
|
182
|
+
* without spelling `Calc<Refs, 'percentage', Unit.Percentage>` —
|
|
183
|
+
* `Percentage.of(40)` produces one, and it composes with every `Calc`
|
|
184
|
+
* combinator (adding two percentages is a percentage, one over another is a
|
|
185
|
+
* number). `Refs` unions the unbound reference names, as on `Calc`.
|
|
186
|
+
*
|
|
187
|
+
* @since 0.2.0
|
|
188
|
+
*/
|
|
189
|
+
type Percentage<Refs extends string = string> = Calc<Refs, 'percentage', Percentage$1>;
|
|
190
|
+
/**
|
|
191
|
+
* A percentage — a number rendered with a trailing `%`. `Percentage.of(40)`
|
|
192
|
+
* serializes as `40%`. The value passes through unrounded; the optional
|
|
193
|
+
* `Precision` pins serialization exactly as `Calc.of` does.
|
|
194
|
+
*
|
|
195
|
+
* @param value - The percentage magnitude (`40` for `40%`, not `0.4`).
|
|
196
|
+
* @param precision - Optional serialization precision.
|
|
197
|
+
* @returns A `<percentage>` expression.
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* Calc.serialize(Percentage.of(40)) // '40%'
|
|
201
|
+
* Calc.serialize(Calc.add(Percentage.of(20), Percentage.of(5))) // '25%'
|
|
202
|
+
* ```
|
|
203
|
+
* @since 0.2.0
|
|
204
|
+
*/
|
|
205
|
+
declare const of: (value: number, precision?: Precision) => Calc<never, 'percentage', Percent>;
|
|
206
|
+
//#endregion
|
|
207
|
+
export { angle_d_exports as Angle, channels_d_exports as Channel, color_d_exports as Color, colorSpace_d_exports as ColorSpace, hueInterpolation_d_exports as HueInterpolation, keywords_d_exports as Keyword, length_d_exports as Length, percentage_d_exports as Percentage, units_d_exports as Unit };
|
|
208
|
+
//# sourceMappingURL=index.d.mts.map
|