@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.
Files changed (2) hide show
  1. package/README.md +128 -132
  2. package/package.json +8 -8
package/README.md CHANGED
@@ -1,202 +1,198 @@
1
1
  # @pyreon/unistyle
2
2
 
3
- Responsive CSS engine for Pyreon.
3
+ Responsive CSS engine property-centric theme objects, automatic media queries, breakpoint dedup.
4
4
 
5
- Transforms property-centric theme objects into breakpoint-centric CSS with media queries. Automatic px-to-rem conversion, flex alignment helpers, and a data-driven style processor. Powers the responsive system behind `@pyreon/elements`, `@pyreon/coolgrid`, and `@pyreon/rocketstyle`.
5
+ `@pyreon/unistyle` transforms theme objects (`{ padding: { xs: 8, md: 16 }, fontSize: 14 }`) into breakpoint-centric CSS with mobile-first media queries. Automatic pxrem 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
- ## Features
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 Start
13
+ ## Quick start
23
14
 
24
- ```ts
25
- import { Provider } from '@pyreon/unistyle'
26
-
27
- const theme = {
28
- rootSize: 16,
29
- breakpoints: { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200 },
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
- ## API
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
- ### makeItResponsive
38
+ ## `makeItResponsive` — the core engine
43
39
 
44
- The core responsive engine. Takes a theme object and a styles processor, returns CSS with media query wrappers for each breakpoint.
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 { config } from '@pyreon/ui-core'
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
- // Single values
61
- Box({ $box: { padding: 16, fontSize: 14 } })
51
+ // Scalar
52
+ <Box $box={{ padding: 16, fontSize: 14 }} />
62
53
 
63
- // Responsive breakpoint object
64
- Box({ $box: { padding: { xs: 8, md: 16, lg: 24 }, fontSize: 14 } })
54
+ // Breakpoint object
55
+ <Box $box={{ padding: { xs: 8, md: 16, lg: 24 }, fontSize: 14 }} />
65
56
 
66
- // Responsive mobile-first array
67
- Box({ $box: { padding: [8, 16, 24], fontSize: 14 } })
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 → normalize (fill gaps) → transform (property → breakpoint pivot)
74
- optimize (deduplicate) media queries
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
- **Parameters:**
78
-
79
- | Param | Type | Description |
80
- | --------- | ---------- | -------------------------------------------------------------------- |
81
- | key | `string` | Theme prop name to read from styled-component props |
82
- | css | `function` | `css` tagged template function |
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
- ### styles
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
- Data-driven CSS property processor. Reads a theme object and outputs CSS for all recognized properties — layout, spacing, typography, borders, backgrounds, transforms, and more.
80
+ ## `styles` — data-driven CSS processor
89
81
 
90
- Used internally by `makeItResponsive` but can be called directly:
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
- // styles({ theme, css, rootSize }) => css``
87
+ const cssResult = styles({ theme, css, rootSize: 16 })
96
88
  ```
97
89
 
98
- Supports shorthand properties (`margin`, `padding`, `borderRadius`) with automatic expansion, and converts numeric values to rem units.
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
- ### Unit Conversion
92
+ ## Responsive value formats
93
+
94
+ Every property accepts three shapes:
101
95
 
102
96
  ```ts
103
- import { value, values, stripUnit } from '@pyreon/unistyle'
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
- // value(input, rootSize?, outputUnit?) => string | number | null
106
- value(16) // => '1rem' (16 / 16)
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
- ### Alignment Helpers
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
- ```ts
125
- import { alignContent, ALIGN_CONTENT_MAP_X, ALIGN_CONTENT_MAP_Y } from '@pyreon/unistyle'
126
- ```
109
+ ## Unit conversion
127
110
 
128
- Maps alignment keywords to CSS flex values:
111
+ ```ts
112
+ import { value, values, stripUnit } from '@pyreon/unistyle'
129
113
 
130
- | Keyword | X-axis CSS | Y-axis CSS |
131
- | ------------------ | --------------- | --------------- |
132
- | `left` / `top` | `flex-start` | `flex-start` |
133
- | `center` | `center` | `center` |
134
- | `right` / `bottom` | `flex-end` | `flex-end` |
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
- ### Default Breakpoints
120
+ stripUnit('24px') // 24
121
+ stripUnit('24px', true) // [24, 'px']
122
+ stripUnit(24) // 24
140
123
 
141
- ```ts
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
- Breakpoint values are converted to `em` units in media queries for correct cross-browser behavior.
133
+ Maps alignment keywords CSS flex values:
160
134
 
161
- ### Other Exports
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
- | Export | Description |
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
- ## Responsive Value Formats
146
+ ```ts
147
+ import { breakpoints, enrichTheme } from '@pyreon/unistyle'
173
148
 
174
- Every property in the theme object supports three formats:
149
+ breakpoints // { rootSize: 16, breakpoints: { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200, xxl: 1440 } }
150
+ ```
175
151
 
176
- ```ts
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
- // 2. Array — mobile-first, values map to breakpoints by position
181
- { padding: [8, 12, 16] } // xs: 8, sm: 12, md: 16
154
+ ## Other exports
182
155
 
183
- // 3. Object explicit breakpoint keys
184
- { padding: { xs: 8, md: 16, xl: 24 } }
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
- When using `normalize: true` (default), missing breakpoints inherit from the previous one.
179
+ ## Performance
188
180
 
189
- ## Peer Dependencies
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
- | Package | Version |
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
- ## Performance
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
- The `styles()` function reuses module-level `Set<number>` and `fragments[]` containers, cleared on each synchronous call instead of allocating per-call. Combined with the Tier 1 key-to-index lookup (iterates ~10-20 descriptors per component instead of ~257), this eliminates ~160 allocations per 80-component page.
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.21.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.8",
47
- "@pyreon/typescript": "^0.21.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.3.0"
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.21.0",
56
- "@pyreon/reactivity": "^0.21.0",
57
- "@pyreon/styler": "^0.21.0",
58
- "@pyreon/ui-core": "^0.21.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
  }