@pyreon/unistyle 0.21.0 → 0.23.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/README.md +128 -132
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -1,202 +1,198 @@
|
|
|
1
1
|
# @pyreon/unistyle
|
|
2
2
|
|
|
3
|
-
Responsive CSS engine
|
|
3
|
+
Responsive CSS engine — property-centric theme objects, automatic media queries, breakpoint dedup.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`@pyreon/unistyle` transforms theme objects (`{ padding: { xs: 8, md: 16 }, fontSize: 14 }`) into breakpoint-centric CSS with mobile-first media queries. Automatic px→rem conversion, 170+ CSS property mappings, three input shapes per property (scalar / mobile-first array / breakpoint object), and an optimizer that emits only the per-breakpoint DELTAS — so a property set at `xs` doesn't re-emit at every larger breakpoint. Powers the responsive system behind `@pyreon/elements`, `@pyreon/coolgrid`, and `@pyreon/rocketstyle`. Per-theme cached so re-renders against the same theme reference return the previous output verbatim.
|
|
6
6
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
- **Responsive pipeline** — write theme objects, get media queries automatically
|
|
10
|
-
- **px-to-rem conversion** — configurable rootSize, zero-effort unit handling
|
|
11
|
-
- **Breakpoint deduplication** — identical breakpoints are collapsed, no redundant CSS
|
|
12
|
-
- **Three input formats** — scalar, mobile-first array, or breakpoint object per property
|
|
13
|
-
- **Data-driven styles** — 100+ CSS properties processed from a theme object
|
|
14
|
-
- **Alignment helpers** — flex alignment constants for X and Y axes
|
|
15
|
-
|
|
16
|
-
## Installation
|
|
7
|
+
## Install
|
|
17
8
|
|
|
18
9
|
```bash
|
|
19
|
-
bun add @pyreon/unistyle
|
|
10
|
+
bun add @pyreon/unistyle @pyreon/core @pyreon/reactivity @pyreon/ui-core
|
|
20
11
|
```
|
|
21
12
|
|
|
22
|
-
## Quick
|
|
13
|
+
## Quick start
|
|
23
14
|
|
|
24
|
-
```
|
|
25
|
-
import { Provider } from '@pyreon/unistyle'
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
Provider({
|
|
33
|
-
theme,
|
|
34
|
-
children: [
|
|
35
|
-
/* your app */
|
|
36
|
-
],
|
|
37
|
-
})
|
|
15
|
+
```tsx
|
|
16
|
+
import { Provider, breakpoints } from '@pyreon/unistyle'
|
|
17
|
+
|
|
18
|
+
<Provider theme={breakpoints}>
|
|
19
|
+
<App />
|
|
20
|
+
</Provider>
|
|
38
21
|
```
|
|
39
22
|
|
|
40
|
-
|
|
23
|
+
Or with a custom theme:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
<Provider
|
|
27
|
+
theme={{
|
|
28
|
+
rootSize: 16,
|
|
29
|
+
breakpoints: { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200, xxl: 1440 },
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<App />
|
|
33
|
+
</Provider>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`Provider` is also re-exported from `@pyreon/coolgrid` and `@pyreon/elements` — set it once near the app root (typically `<PyreonUI>` wraps it automatically).
|
|
41
37
|
|
|
42
|
-
|
|
38
|
+
## `makeItResponsive` — the core engine
|
|
43
39
|
|
|
44
|
-
|
|
40
|
+
Reads a theme prop off styled-component props, runs the responsive pipeline, and returns CSS with media-query wrappers.
|
|
45
41
|
|
|
46
42
|
```ts
|
|
47
43
|
import { makeItResponsive, styles } from '@pyreon/unistyle'
|
|
48
|
-
import {
|
|
49
|
-
|
|
50
|
-
const { styled, css } = config
|
|
44
|
+
import { css } from '@pyreon/styler'
|
|
45
|
+
import { styled } from '@pyreon/styler'
|
|
51
46
|
|
|
52
47
|
const Box = styled('div')`
|
|
53
|
-
${makeItResponsive({
|
|
54
|
-
key: '$box',
|
|
55
|
-
css,
|
|
56
|
-
styles,
|
|
57
|
-
})}
|
|
48
|
+
${makeItResponsive({ key: '$box', css, styles })}
|
|
58
49
|
`
|
|
59
50
|
|
|
60
|
-
//
|
|
61
|
-
Box
|
|
51
|
+
// Scalar
|
|
52
|
+
<Box $box={{ padding: 16, fontSize: 14 }} />
|
|
62
53
|
|
|
63
|
-
//
|
|
64
|
-
Box
|
|
54
|
+
// Breakpoint object
|
|
55
|
+
<Box $box={{ padding: { xs: 8, md: 16, lg: 24 }, fontSize: 14 }} />
|
|
65
56
|
|
|
66
|
-
//
|
|
67
|
-
Box
|
|
57
|
+
// Mobile-first array
|
|
58
|
+
<Box $box={{ padding: [8, 16, 24], fontSize: 14 }} />
|
|
68
59
|
```
|
|
69
60
|
|
|
70
61
|
**Pipeline:**
|
|
71
62
|
|
|
72
63
|
```text
|
|
73
|
-
theme object
|
|
74
|
-
|
|
64
|
+
theme object
|
|
65
|
+
↓ normalize (fill gaps so every breakpoint has a complete set)
|
|
66
|
+
↓ transform (property-centric → breakpoint-centric pivot)
|
|
67
|
+
↓ optimize (drop declarations that don't change vs previous breakpoint)
|
|
68
|
+
↓ emit @media rules
|
|
75
69
|
```
|
|
76
70
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
| styles | `function` | Style processor (use the exported `styles`) |
|
|
84
|
-
| normalize | `boolean` | Fill missing breakpoints by inheriting from previous (default: true) |
|
|
71
|
+
| Param | Type | Notes |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `key` | `string` | Theme prop name to read from props |
|
|
74
|
+
| `css` | `function` | `css` tagged template from `@pyreon/styler` |
|
|
75
|
+
| `styles` | `function` | Style processor (use the exported `styles`) |
|
|
76
|
+
| `normalize` | `boolean` | Fill missing breakpoints by inheriting from previous (default `true`) |
|
|
85
77
|
|
|
86
|
-
|
|
78
|
+
`makeItResponsive` carries a **render-output cache** keyed by theme reference — the same `(internalTheme, outerTheme)` pair returns the previous CSSResult array verbatim. Re-renders against a stable provider cost ~0.
|
|
87
79
|
|
|
88
|
-
|
|
80
|
+
## `styles` — data-driven CSS processor
|
|
89
81
|
|
|
90
|
-
Used internally by `makeItResponsive
|
|
82
|
+
Reads a theme object and outputs CSS for 170+ recognized properties — layout, spacing, typography, borders, backgrounds, transforms, special keys (`fullScreen`, `clearFix`, `extendCss`, `backgroundImage`, `animation`). Used internally by `makeItResponsive`; callable directly when you have a non-responsive theme.
|
|
91
83
|
|
|
92
84
|
```ts
|
|
93
85
|
import { styles } from '@pyreon/unistyle'
|
|
94
86
|
|
|
95
|
-
|
|
87
|
+
const cssResult = styles({ theme, css, rootSize: 16 })
|
|
96
88
|
```
|
|
97
89
|
|
|
98
|
-
Supports shorthand
|
|
90
|
+
Supports shorthand expansion (`margin: 16` → `margin: 1rem`; `padding: '12 16'` → top/bottom/left/right), property-first naming (`borderWidthTop`, not CSS-spec `borderTopWidth`), and numeric→rem auto-conversion.
|
|
99
91
|
|
|
100
|
-
|
|
92
|
+
## Responsive value formats
|
|
93
|
+
|
|
94
|
+
Every property accepts three shapes:
|
|
101
95
|
|
|
102
96
|
```ts
|
|
103
|
-
|
|
97
|
+
// Scalar — applies at every breakpoint
|
|
98
|
+
{ padding: 16 }
|
|
99
|
+
|
|
100
|
+
// Mobile-first array — positional [xs, sm, md, lg, xl, xxl]
|
|
101
|
+
{ padding: [8, 12, 16] } // xs: 8, sm: 12, md: 16, fills above
|
|
104
102
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
value(24) // => '1.5rem' (24 / 16)
|
|
108
|
-
value(0) // => '0' (always unitless)
|
|
109
|
-
value('2em') // => '2em' (string passthrough)
|
|
110
|
-
value(16, 16, 'px') // => '16px'
|
|
111
|
-
|
|
112
|
-
// stripUnit(input, unitReturn?)
|
|
113
|
-
stripUnit('24px') // => 24
|
|
114
|
-
stripUnit('24px', true) // => [24, 'px']
|
|
115
|
-
stripUnit(24) // => 24
|
|
116
|
-
|
|
117
|
-
// values(array, rootSize?, outputUnit?) => string
|
|
118
|
-
// Picks first non-null and converts
|
|
119
|
-
values([null, 16, 24], 16) // => '1rem'
|
|
103
|
+
// Object — explicit breakpoint keys
|
|
104
|
+
{ padding: { xs: 8, md: 16, xl: 24 } }
|
|
120
105
|
```
|
|
121
106
|
|
|
122
|
-
|
|
107
|
+
With `normalize: true` (default), missing breakpoints inherit from the previous one. The optimizer then DROPS declarations that don't actually change vs the previous breakpoint — so `{ color: { xs: 'red', sm: 'red' }, padding: { xs: 0, sm: '1rem' } }` emits only `padding: 1rem` at `@media (min-width: sm)`, not `color: red; padding: 1rem;`.
|
|
123
108
|
|
|
124
|
-
|
|
125
|
-
import { alignContent, ALIGN_CONTENT_MAP_X, ALIGN_CONTENT_MAP_Y } from '@pyreon/unistyle'
|
|
126
|
-
```
|
|
109
|
+
## Unit conversion
|
|
127
110
|
|
|
128
|
-
|
|
111
|
+
```ts
|
|
112
|
+
import { value, values, stripUnit } from '@pyreon/unistyle'
|
|
129
113
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
| `spaceBetween` | `space-between` | `space-between` |
|
|
136
|
-
| `spaceAround` | `space-around` | `space-around` |
|
|
137
|
-
| `block` | `stretch` | `stretch` |
|
|
114
|
+
value(16) // '1rem' (16 / 16)
|
|
115
|
+
value(24) // '1.5rem'
|
|
116
|
+
value(0) // '0' (always unitless)
|
|
117
|
+
value('2em') // '2em' (string passthrough)
|
|
118
|
+
value(16, 16, 'px') // '16px' (output-unit override)
|
|
138
119
|
|
|
139
|
-
|
|
120
|
+
stripUnit('24px') // 24
|
|
121
|
+
stripUnit('24px', true) // [24, 'px']
|
|
122
|
+
stripUnit(24) // 24
|
|
140
123
|
|
|
141
|
-
|
|
142
|
-
import { breakpoints } from '@pyreon/unistyle'
|
|
124
|
+
values([null, 16, 24], 16) // '1rem' (picks first non-null and converts)
|
|
143
125
|
```
|
|
144
126
|
|
|
127
|
+
## Alignment helpers
|
|
128
|
+
|
|
145
129
|
```ts
|
|
146
|
-
{
|
|
147
|
-
rootSize: 16,
|
|
148
|
-
breakpoints: {
|
|
149
|
-
xs: 0, // mobile
|
|
150
|
-
sm: 576, // small
|
|
151
|
-
md: 768, // tablet
|
|
152
|
-
lg: 992, // desktop
|
|
153
|
-
xl: 1200, // large desktop
|
|
154
|
-
xxl: 1440, // extra large
|
|
155
|
-
},
|
|
156
|
-
}
|
|
130
|
+
import { alignContent, ALIGN_CONTENT_MAP_X, ALIGN_CONTENT_MAP_Y, ALIGN_CONTENT_DIRECTION } from '@pyreon/unistyle'
|
|
157
131
|
```
|
|
158
132
|
|
|
159
|
-
|
|
133
|
+
Maps alignment keywords → CSS flex values:
|
|
160
134
|
|
|
161
|
-
|
|
135
|
+
| Keyword | X-axis | Y-axis |
|
|
136
|
+
| ------------------ | ----------------- | ----------------- |
|
|
137
|
+
| `left` / `top` | `flex-start` | `flex-start` |
|
|
138
|
+
| `center` | `center` | `center` |
|
|
139
|
+
| `right` / `bottom` | `flex-end` | `flex-end` |
|
|
140
|
+
| `spaceBetween` | `space-between` | `space-between` |
|
|
141
|
+
| `spaceAround` | `space-around` | `space-around` |
|
|
142
|
+
| `block` | `stretch` | `stretch` |
|
|
162
143
|
|
|
163
|
-
|
|
164
|
-
| ---------------------- | ------------------------------------------------------------------ |
|
|
165
|
-
| `createMediaQueries` | Builds breakpoint-name → tagged-template-function map |
|
|
166
|
-
| `transformTheme` | Pivots property-centric theme to breakpoint-centric |
|
|
167
|
-
| `normalizeTheme` | Fills gaps so every breakpoint has a complete set of values |
|
|
168
|
-
| `sortBreakpoints` | Sorts breakpoint definitions by value (ascending) |
|
|
169
|
-
| `extendCss` | Helper for processing ExtendCss props (string, function, callback) |
|
|
170
|
-
| `Provider` / `context` | Theme context provider and consumer |
|
|
144
|
+
## Default breakpoints
|
|
171
145
|
|
|
172
|
-
|
|
146
|
+
```ts
|
|
147
|
+
import { breakpoints, enrichTheme } from '@pyreon/unistyle'
|
|
173
148
|
|
|
174
|
-
|
|
149
|
+
breakpoints // { rootSize: 16, breakpoints: { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200, xxl: 1440 } }
|
|
150
|
+
```
|
|
175
151
|
|
|
176
|
-
|
|
177
|
-
// 1. Scalar — applied to all breakpoints
|
|
178
|
-
{ padding: 16 }
|
|
152
|
+
Values are converted to `em` units in media queries for correct cross-browser behaviour. `enrichTheme(userTheme)` merges your theme with the defaults — used by `<PyreonUI>` internally.
|
|
179
153
|
|
|
180
|
-
|
|
181
|
-
{ padding: [8, 12, 16] } // xs: 8, sm: 12, md: 16
|
|
154
|
+
## Other exports
|
|
182
155
|
|
|
183
|
-
|
|
184
|
-
|
|
156
|
+
| Export | Notes |
|
|
157
|
+
|---|---|
|
|
158
|
+
| `createMediaQueries` | Builds breakpoint-name → tagged-template-function map |
|
|
159
|
+
| `transformTheme` | Property-centric → breakpoint-centric pivot |
|
|
160
|
+
| `normalizeTheme` | Fills gaps so every breakpoint has a complete set |
|
|
161
|
+
| `sortBreakpoints` | Sorts breakpoint definitions by value (ascending) |
|
|
162
|
+
| `extendCss` | Helper for processing `ExtendCss` props (string, fn, callback) |
|
|
163
|
+
| `Provider` / `context` | Theme context provider + consumer |
|
|
164
|
+
| `enrichTheme` | Merge user theme with default breakpoints/spacing |
|
|
165
|
+
|
|
166
|
+
## Types
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import type {
|
|
170
|
+
PyreonTheme, Breakpoints,
|
|
171
|
+
ITheme, Styles, StylesTheme, ExtendCss,
|
|
172
|
+
AlignContent, AlignContentAlignXKeys, AlignContentAlignYKeys, AlignContentDirectionKeys,
|
|
173
|
+
BrowserColors, Color, PropertyValue, UnitValue, Value, Values,
|
|
174
|
+
MakeItResponsive, MakeItResponsiveStyles, TransformTheme, NormalizeTheme, SortBreakpoints, CreateMediaQueries,
|
|
175
|
+
StripUnit, Defaults, TProvider,
|
|
176
|
+
} from '@pyreon/unistyle'
|
|
185
177
|
```
|
|
186
178
|
|
|
187
|
-
|
|
179
|
+
## Performance
|
|
188
180
|
|
|
189
|
-
|
|
181
|
+
- **Per-theme render cache** — `makeItResponsive` carries a `WeakMap<innerTheme, WeakMap<outerTheme, CSSResult[]>>`. Same theme reference → previous output returned verbatim.
|
|
182
|
+
- **Key-to-index lookup** — `styles()` iterates ~10-20 descriptors per component (matched against the user's theme keys) instead of ~257 (full descriptor table). The index builder walks every branch's identifying field (`d.key` / `d.keys` / `d.id` for the `'special'` branch — `fullScreen`, `clearFix`, `extendCss`, `backgroundImage`, `animation`).
|
|
183
|
+
- **Module-level reusable containers** — `styles()` reuses module-scoped `Set<number>` and `fragments[]` containers cleared on each synchronous call, eliminating ~160 allocations per 80-component page.
|
|
184
|
+
- **Optimizer drops re-emitted declarations** — `optimizeBreakpointDeltas()` removes properties that don't change vs the previous breakpoint, cutting bytes in mobile-first responsive cascades.
|
|
190
185
|
|
|
191
|
-
|
|
192
|
-
| ------------------ | -------- |
|
|
193
|
-
| @pyreon/core | >= 0.0.1 |
|
|
194
|
-
| @pyreon/reactivity | >= 0.0.1 |
|
|
195
|
-
| @pyreon/ui-core | >= 0.0.1 |
|
|
186
|
+
## Gotchas
|
|
196
187
|
|
|
197
|
-
|
|
188
|
+
- **Conditional CSS in responsive callbacks needs an explicit else.** When `t.block` is responsive (`[true, false, true]`), a callback like `${t.block && 'align-self: stretch'}` emits `stretch` at the truthy breakpoints AND emits NOTHING at the falsy ones — the optimizer is subtractive and can't synthesize a reset. The cascade leaves `stretch` in place. Fix: always emit a value with an explicit else: `align-self: ${t.block ? 'stretch' : 'auto'}`. The optimizer drops both halves when nothing changes, so the always-emit pattern is free in the steady state.
|
|
189
|
+
- **CSS property naming follows unistyle convention** (`borderWidthTop` / `borderColorLeft`), NOT CSS-spec naming (`borderTopWidth`). Property-first.
|
|
190
|
+
- **`extendCss`** accepts string, function, or callback. The function form receives the resolved theme so you can derive from it.
|
|
191
|
+
- **`enrichTheme` merges; it does not replace.** Pass only the keys you want to override — defaults fill in the rest.
|
|
192
|
+
|
|
193
|
+
## Documentation
|
|
198
194
|
|
|
199
|
-
|
|
195
|
+
Full docs: [docs.pyreon.dev/docs/unistyle](https://docs.pyreon.dev/docs/unistyle) (or `docs/docs/unistyle.md` in this repo).
|
|
200
196
|
|
|
201
197
|
## License
|
|
202
198
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/unistyle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Responsive theming and breakpoint utilities for Pyreon",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -43,18 +43,18 @@
|
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@pyreon/manifest": "0.13.1",
|
|
46
|
-
"@pyreon/test-utils": "^0.13.
|
|
47
|
-
"@pyreon/typescript": "^0.
|
|
46
|
+
"@pyreon/test-utils": "^0.13.10",
|
|
47
|
+
"@pyreon/typescript": "^0.23.0",
|
|
48
48
|
"@vitest/browser-playwright": "^4.1.4",
|
|
49
|
-
"@vitus-labs/tools-rolldown": "^2.
|
|
49
|
+
"@vitus-labs/tools-rolldown": "^2.4.0"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
52
52
|
"node": ">= 22"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@pyreon/core": "^0.
|
|
56
|
-
"@pyreon/reactivity": "^0.
|
|
57
|
-
"@pyreon/styler": "^0.
|
|
58
|
-
"@pyreon/ui-core": "^0.
|
|
55
|
+
"@pyreon/core": "^0.23.0",
|
|
56
|
+
"@pyreon/reactivity": "^0.23.0",
|
|
57
|
+
"@pyreon/styler": "^0.23.0",
|
|
58
|
+
"@pyreon/ui-core": "^0.23.0"
|
|
59
59
|
}
|
|
60
60
|
}
|