@pyreon/styler 0.0.2
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 +21 -0
- package/README.md +283 -0
- package/lib/index.d.ts +212 -0
- package/lib/index.js +850 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present Vit Bokisch
|
|
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,283 @@
|
|
|
1
|
+
# @pyreon/styler
|
|
2
|
+
|
|
3
|
+
Lightweight CSS-in-JS engine for Pyreon.
|
|
4
|
+
|
|
5
|
+
**3.81 KB** gzipped | **SSR & static export ready** | **TypeScript strict**
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add @pyreon/styler
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { styled, css, ThemeContext } from '@pyreon/styler'
|
|
17
|
+
import { useContext, pushContext, popContext, onUnmount } from '@pyreon/core'
|
|
18
|
+
|
|
19
|
+
const Button = styled('button')`
|
|
20
|
+
display: inline-flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
padding: 8px 16px;
|
|
23
|
+
border-radius: 4px;
|
|
24
|
+
background: ${({ theme }) => theme.colors.primary};
|
|
25
|
+
color: white;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
|
|
28
|
+
&:hover {
|
|
29
|
+
opacity: 0.9;
|
|
30
|
+
}
|
|
31
|
+
`
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## API
|
|
35
|
+
|
|
36
|
+
### `styled(tag)`
|
|
37
|
+
|
|
38
|
+
Creates a styled Pyreon component from an HTML tag or another component.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// HTML tag
|
|
42
|
+
const Box = styled('div')`
|
|
43
|
+
display: flex;
|
|
44
|
+
`
|
|
45
|
+
|
|
46
|
+
// Shorthand (via Proxy)
|
|
47
|
+
const Box = styled.div`
|
|
48
|
+
display: flex;
|
|
49
|
+
`
|
|
50
|
+
|
|
51
|
+
// Wrapping a component
|
|
52
|
+
const StyledLink = styled(Link)`
|
|
53
|
+
color: blue;
|
|
54
|
+
text-decoration: none;
|
|
55
|
+
`
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Dynamic interpolations
|
|
59
|
+
|
|
60
|
+
Function interpolations receive all props plus the current `theme`:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const Text = styled('p')`
|
|
64
|
+
color: ${({ theme }) => theme.colors.text};
|
|
65
|
+
font-size: ${(props) => props.$size || '16px'};
|
|
66
|
+
`
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Polymorphic `as` prop
|
|
70
|
+
|
|
71
|
+
Render as a different element at runtime:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const Box = styled('div')`padding: 16px;`
|
|
75
|
+
|
|
76
|
+
// Renders as a <section>
|
|
77
|
+
Box({ as: 'section', children: 'Content' })
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### Transient props
|
|
81
|
+
|
|
82
|
+
Props prefixed with `$` are not forwarded to the DOM:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const Box = styled('div')`
|
|
86
|
+
color: ${(p) => p.$active ? 'blue' : 'gray'};
|
|
87
|
+
`
|
|
88
|
+
|
|
89
|
+
// $active is used for styling but won't appear on the <div>
|
|
90
|
+
Box({ $active: true })
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Custom prop filtering
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const Box = styled('div', {
|
|
97
|
+
shouldForwardProp: (prop) => prop !== 'size',
|
|
98
|
+
})`
|
|
99
|
+
font-size: ${(p) => p.size}px;
|
|
100
|
+
`
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `css`
|
|
104
|
+
|
|
105
|
+
Tagged template for composable CSS fragments:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const flexCenter = css`
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
justify-content: center;
|
|
112
|
+
`
|
|
113
|
+
|
|
114
|
+
const Card = styled('div')`
|
|
115
|
+
${flexCenter};
|
|
116
|
+
padding: 16px;
|
|
117
|
+
`
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Supports conditional patterns:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
const Box = styled('div')`
|
|
124
|
+
display: flex;
|
|
125
|
+
${(props) => props.$bordered && css`
|
|
126
|
+
border: 1px solid #e0e0e0;
|
|
127
|
+
border-radius: 4px;
|
|
128
|
+
`};
|
|
129
|
+
`
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `keyframes`
|
|
133
|
+
|
|
134
|
+
Creates `@keyframes` animations:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const fadeIn = keyframes`
|
|
138
|
+
from { opacity: 0; }
|
|
139
|
+
to { opacity: 1; }
|
|
140
|
+
`
|
|
141
|
+
|
|
142
|
+
const FadeBox = styled('div')`
|
|
143
|
+
animation: ${fadeIn} 300ms ease-in;
|
|
144
|
+
`
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `createGlobalStyle`
|
|
148
|
+
|
|
149
|
+
Injects global CSS rules (not scoped to a class):
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const GlobalStyle = createGlobalStyle`
|
|
153
|
+
*, *::before, *::after {
|
|
154
|
+
box-sizing: border-box;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
body {
|
|
158
|
+
margin: 0;
|
|
159
|
+
font-family: ${({ theme }) => theme.font};
|
|
160
|
+
}
|
|
161
|
+
`
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### `ThemeContext` & `useTheme`
|
|
165
|
+
|
|
166
|
+
Provides a theme object to all nested styled components via Pyreon's context system:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { ThemeContext, useTheme } from '@pyreon/styler'
|
|
170
|
+
import { pushContext, onUnmount, popContext } from '@pyreon/core'
|
|
171
|
+
|
|
172
|
+
// Provide theme
|
|
173
|
+
pushContext(new Map([[ThemeContext.id, myTheme]]))
|
|
174
|
+
onUnmount(() => popContext())
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Access the theme from any component:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
const MyComponent = () => {
|
|
181
|
+
const theme = useTheme()
|
|
182
|
+
// use theme values
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### TypeScript theme augmentation
|
|
187
|
+
|
|
188
|
+
Extend `DefaultTheme` for strict typing across your app:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
declare module '@pyreon/styler' {
|
|
192
|
+
interface DefaultTheme {
|
|
193
|
+
colors: { primary: string; text: string }
|
|
194
|
+
spacing: (n: number) => string
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### `sheet` & `createSheet`
|
|
200
|
+
|
|
201
|
+
The singleton `sheet` manages CSS rule injection. For SSR, use `createSheet` for per-request isolation:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { createSheet } from '@pyreon/styler'
|
|
205
|
+
|
|
206
|
+
const sheet = createSheet()
|
|
207
|
+
const html = renderToString(App({}))
|
|
208
|
+
const styleTags = sheet.getStyleTag()
|
|
209
|
+
sheet.reset()
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### `@layer` support
|
|
213
|
+
|
|
214
|
+
Wrap all scoped rules in a CSS Cascade Layer:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
const sheet = createSheet({ layer: 'components' })
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## How It Works
|
|
221
|
+
|
|
222
|
+
### Static path (zero runtime cost)
|
|
223
|
+
|
|
224
|
+
Templates with no function interpolations are resolved **once at component creation time**. The CSS class, rules, and `<style>` element are pre-computed and cached.
|
|
225
|
+
|
|
226
|
+
### Dynamic path
|
|
227
|
+
|
|
228
|
+
Templates with function interpolations resolve on every render. A cache skips `sheet.prepare()` and `<style>` element creation when the resolved CSS text hasn't changed.
|
|
229
|
+
|
|
230
|
+
### CSS Nesting
|
|
231
|
+
|
|
232
|
+
Native CSS nesting is supported out of the box. The engine passes CSS through without transformation, so `&:hover`, `&::before`, nested selectors, and `@media` queries work as-is in all modern browsers.
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
const Card = styled('div')`
|
|
236
|
+
padding: 16px;
|
|
237
|
+
|
|
238
|
+
&:hover {
|
|
239
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
& > h2 {
|
|
243
|
+
margin: 0 0 8px;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@media (min-width: 768px) {
|
|
247
|
+
padding: 24px;
|
|
248
|
+
}
|
|
249
|
+
`
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Benchmarks
|
|
253
|
+
|
|
254
|
+
### Bundle Size
|
|
255
|
+
|
|
256
|
+
| Library | Minified | Gzipped |
|
|
257
|
+
|---------|--------:|--------:|
|
|
258
|
+
| goober | 2.32 KB | 1.31 KB |
|
|
259
|
+
| **@pyreon/styler** | **10.13 KB** | **3.81 KB** |
|
|
260
|
+
| styled-components | 44.93 KB | 17.89 KB |
|
|
261
|
+
| @emotion/react + styled | 48.26 KB | 16.59 KB |
|
|
262
|
+
|
|
263
|
+
### Performance (ops/sec, higher is better)
|
|
264
|
+
|
|
265
|
+
| Benchmark | styler | styled-components | @emotion | goober |
|
|
266
|
+
|-----------|-------:|-------------------:|---------:|-------:|
|
|
267
|
+
| css() creation | **25.2M** | 9.0M | 2.2M | 26K |
|
|
268
|
+
| css() with interpolations | **24.9M** | 5.6M | 2.3M | 28K |
|
|
269
|
+
| Template resolution | **21.4M** | 3.9M | — | — |
|
|
270
|
+
| Nested composition | **8.3M** | 2.2M | 1.4M | 8K |
|
|
271
|
+
| SSR renderToString | **307K** | 69K | 192K | 18K |
|
|
272
|
+
| styled() factory | **17.3M** | 109K | 933K | 18.2M |
|
|
273
|
+
|
|
274
|
+
## Peer Dependencies
|
|
275
|
+
|
|
276
|
+
| Package | Version |
|
|
277
|
+
| ------- | ------- |
|
|
278
|
+
| @pyreon/core | >= 0.0.1 |
|
|
279
|
+
| @pyreon/reactivity | >= 0.0.1 |
|
|
280
|
+
|
|
281
|
+
## License
|
|
282
|
+
|
|
283
|
+
MIT
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import * as _pyreon_core0 from "@pyreon/core";
|
|
2
|
+
import { ComponentFn, VNode, VNodeChild } from "@pyreon/core";
|
|
3
|
+
|
|
4
|
+
//#region src/ThemeProvider.d.ts
|
|
5
|
+
interface DefaultTheme {}
|
|
6
|
+
type Theme = DefaultTheme & Record<string, unknown>;
|
|
7
|
+
declare const ThemeContext: _pyreon_core0.Context<Theme>;
|
|
8
|
+
/** Hook to read the current theme from the nearest ThemeProvider. */
|
|
9
|
+
declare const useTheme: <T extends object = Theme>() => T;
|
|
10
|
+
/** Provides a theme object to all nested styled components via Pyreon context. */
|
|
11
|
+
declare function ThemeProvider({
|
|
12
|
+
theme,
|
|
13
|
+
children
|
|
14
|
+
}: {
|
|
15
|
+
theme: Theme;
|
|
16
|
+
children?: VNodeChild;
|
|
17
|
+
}): VNode | null;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/resolve.d.ts
|
|
20
|
+
type Interpolation = string | number | boolean | null | undefined | CSSResult | Interpolation[] | ((props: {
|
|
21
|
+
theme?: DefaultTheme & Record<string, any>;
|
|
22
|
+
[key: string]: any;
|
|
23
|
+
}) => Interpolation);
|
|
24
|
+
/**
|
|
25
|
+
* Lazy representation of a `css` tagged template. Stores the raw template
|
|
26
|
+
* strings and interpolation values without resolving them. Resolution is
|
|
27
|
+
* deferred until a styled component renders (or until explicitly resolved).
|
|
28
|
+
*/
|
|
29
|
+
declare class CSSResult {
|
|
30
|
+
readonly strings: TemplateStringsArray;
|
|
31
|
+
readonly values: Interpolation[];
|
|
32
|
+
constructor(strings: TemplateStringsArray, values: Interpolation[]);
|
|
33
|
+
/** Resolve with empty props — useful for static templates, testing, and debugging. */
|
|
34
|
+
toString(): string;
|
|
35
|
+
}
|
|
36
|
+
/** Resolve a tagged template's strings + values into a final CSS string. */
|
|
37
|
+
declare const resolve: (strings: TemplateStringsArray, values: Interpolation[], props: Record<string, any>) => string;
|
|
38
|
+
/** Clear the normalizeCSS cache (called during HMR cleanup). */
|
|
39
|
+
declare const clearNormCache: () => void;
|
|
40
|
+
declare const normalizeCSS: (css: string) => string;
|
|
41
|
+
declare const resolveValue: (value: Interpolation, props: Record<string, any>) => string;
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/css.d.ts
|
|
44
|
+
/**
|
|
45
|
+
* Tagged template function for CSS. Captures the template strings and
|
|
46
|
+
* interpolation values as a lazy CSSResult — resolution is deferred
|
|
47
|
+
* until a styled component renders.
|
|
48
|
+
*
|
|
49
|
+
* Works as both a tagged template (`css\`...\``) and a regular function
|
|
50
|
+
* call (`css(...args)`) since tagged templates are syntactic sugar for
|
|
51
|
+
* function calls with (TemplateStringsArray, ...values).
|
|
52
|
+
*/
|
|
53
|
+
declare const css: (strings: TemplateStringsArray, ...values: Interpolation[]) => CSSResult;
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/forward.d.ts
|
|
56
|
+
/**
|
|
57
|
+
* HTML prop filtering. Prevents unknown props from being forwarded to DOM
|
|
58
|
+
* elements (which causes warnings). Props starting with `$` are
|
|
59
|
+
* transient (styling-only) and are always filtered out.
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Filters props for HTML elements. Keeps valid HTML attrs, data-*, aria-*.
|
|
63
|
+
* Rejects unknown props and $-prefixed transient props.
|
|
64
|
+
*/
|
|
65
|
+
declare const filterProps: (props: Record<string, unknown>) => Record<string, unknown>;
|
|
66
|
+
/**
|
|
67
|
+
* Build final props for a styled component in a single pass.
|
|
68
|
+
* Combines className merging, ref injection, and prop filtering into one
|
|
69
|
+
* allocation and one iteration.
|
|
70
|
+
*/
|
|
71
|
+
declare const buildProps: (rawProps: Record<string, any>, generatedCls: string, isDOM: boolean, customFilter?: (prop: string) => boolean) => Record<string, any>;
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/globalStyle.d.ts
|
|
74
|
+
declare const createGlobalStyle: (strings: TemplateStringsArray, ...values: Interpolation[]) => ComponentFn;
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/hash.d.ts
|
|
77
|
+
/**
|
|
78
|
+
* Fast FNV-1a non-cryptographic hash. Returns base-36 string for compact class names.
|
|
79
|
+
*
|
|
80
|
+
* 32-bit hash space → ~4.3 billion unique values. Collision probability is
|
|
81
|
+
* negligible for typical applications (< 10,000 unique CSS rules).
|
|
82
|
+
*/
|
|
83
|
+
/** FNV-1a offset basis — starting state for streaming hash. */
|
|
84
|
+
declare const HASH_INIT = 2166136261;
|
|
85
|
+
/**
|
|
86
|
+
* Feed a string segment into the running hash state.
|
|
87
|
+
* Streaming: hashUpdate(hashUpdate(HASH_INIT, 'ab'), 'cd') === hash('abcd').
|
|
88
|
+
*/
|
|
89
|
+
declare const hashUpdate: (init: number, str: string) => number;
|
|
90
|
+
/** Finalize a hash state into a base-36 class name suffix. */
|
|
91
|
+
declare const hashFinalize: (h: number) => string;
|
|
92
|
+
/** Hash a complete string in one shot. Returns base-36 string. */
|
|
93
|
+
declare const hash: (str: string) => string;
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/keyframes.d.ts
|
|
96
|
+
declare class KeyframesResult {
|
|
97
|
+
readonly name: string;
|
|
98
|
+
constructor(strings: TemplateStringsArray, values: Interpolation[]);
|
|
99
|
+
/** Returns the animation name when used in string context. */
|
|
100
|
+
toString(): string;
|
|
101
|
+
}
|
|
102
|
+
declare const keyframes: (strings: TemplateStringsArray, ...values: Interpolation[]) => KeyframesResult;
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/shared.d.ts
|
|
105
|
+
/** Check if an interpolation value is dynamic (contains functions or nested dynamic CSSResults). */
|
|
106
|
+
declare const isDynamic: (v: Interpolation) => boolean;
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/sheet.d.ts
|
|
109
|
+
interface StyleSheetOptions {
|
|
110
|
+
/** Maximum number of cached rules before eviction (default: 10000). */
|
|
111
|
+
maxCacheSize?: number;
|
|
112
|
+
/** CSS @layer name to wrap scoped rules in. */
|
|
113
|
+
layer?: string;
|
|
114
|
+
}
|
|
115
|
+
declare class StyleSheet {
|
|
116
|
+
private cache;
|
|
117
|
+
private insertCache;
|
|
118
|
+
private sheet;
|
|
119
|
+
private ssrBuffer;
|
|
120
|
+
private isSSR;
|
|
121
|
+
private maxCacheSize;
|
|
122
|
+
private layer;
|
|
123
|
+
constructor(options?: StyleSheetOptions);
|
|
124
|
+
private mount;
|
|
125
|
+
/** Extract className from a selector like ".pyr-abc" or ".pyr-abc.pyr-abc" → "pyr-abc" */
|
|
126
|
+
private extractClassName;
|
|
127
|
+
/** Parse existing rules from SSR-rendered <style> tag into cache. */
|
|
128
|
+
private hydrateFromTag;
|
|
129
|
+
/** Evict oldest entries when cache exceeds max size. */
|
|
130
|
+
private evictIfNeeded;
|
|
131
|
+
/**
|
|
132
|
+
* Extract nested at-rules (@media, @supports, @container) from CSS text
|
|
133
|
+
* and wrap their content in the given selector as separate top-level rules.
|
|
134
|
+
*/
|
|
135
|
+
private splitAtRules;
|
|
136
|
+
/**
|
|
137
|
+
* Compute a className from CSS text without injecting (pure function).
|
|
138
|
+
*/
|
|
139
|
+
getClassName(cssText: string): string;
|
|
140
|
+
/**
|
|
141
|
+
* Insert CSS rules for a component. Returns the class name (deterministic, hash-based).
|
|
142
|
+
* Deduplicates: same CSS text always produces the same class name and
|
|
143
|
+
* the rules are only injected once.
|
|
144
|
+
*
|
|
145
|
+
* When `boost` is true, the selector is doubled (`.pyr-abc.pyr-abc`)
|
|
146
|
+
* to raise specificity from (0,1,0) to (0,2,0).
|
|
147
|
+
*/
|
|
148
|
+
insert(cssText: string, boost?: boolean): string;
|
|
149
|
+
/** Insert a @keyframes rule. Deduplicates by animation name. */
|
|
150
|
+
insertKeyframes(name: string, body: string): void;
|
|
151
|
+
/**
|
|
152
|
+
* Split CSS text into individual top-level rules.
|
|
153
|
+
* CSSStyleSheet.insertRule() only accepts one rule at a time.
|
|
154
|
+
*/
|
|
155
|
+
private splitRules;
|
|
156
|
+
/** Insert global CSS rules (no wrapper selector). Deduplicates by hash. */
|
|
157
|
+
insertGlobal(cssText: string): void;
|
|
158
|
+
/** Returns collected CSS for SSR as a complete `<style>` tag string. */
|
|
159
|
+
getStyleTag(): string;
|
|
160
|
+
/** Returns collected CSS rules as a raw string (useful for streaming SSR). */
|
|
161
|
+
getStyles(): string;
|
|
162
|
+
/** Reset SSR buffer and cache (call between server requests). */
|
|
163
|
+
reset(): void;
|
|
164
|
+
/** Clear the dedup cache. Useful for HMR / dev-time reloads. */
|
|
165
|
+
clearCache(): void;
|
|
166
|
+
/**
|
|
167
|
+
* Full cleanup: clear cache and remove all CSS rules from the DOM.
|
|
168
|
+
* Intended for HMR / dev-time reloads where stale styles must be purged.
|
|
169
|
+
*/
|
|
170
|
+
clearAll(): void;
|
|
171
|
+
/**
|
|
172
|
+
* Compute className and full CSS rule text without injecting.
|
|
173
|
+
*/
|
|
174
|
+
prepare(cssText: string, boost?: boolean): {
|
|
175
|
+
className: string;
|
|
176
|
+
rules: string;
|
|
177
|
+
};
|
|
178
|
+
/** Check if a className is already in the cache. O(1) Map lookup. */
|
|
179
|
+
has(className: string): boolean;
|
|
180
|
+
/** Current number of cached rules. */
|
|
181
|
+
get cacheSize(): number;
|
|
182
|
+
}
|
|
183
|
+
/** Default singleton sheet for client-side use. */
|
|
184
|
+
declare const sheet: StyleSheet;
|
|
185
|
+
/**
|
|
186
|
+
* Factory for creating isolated StyleSheet instances.
|
|
187
|
+
* Use in SSR to get per-request isolation.
|
|
188
|
+
*/
|
|
189
|
+
declare const createSheet: (options?: StyleSheetOptions) => StyleSheet;
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/styled.d.ts
|
|
192
|
+
type Tag = string | ComponentFn<any>;
|
|
193
|
+
interface StyledOptions {
|
|
194
|
+
/** Custom prop filter. Return true to forward the prop to the DOM element. */
|
|
195
|
+
shouldForwardProp?: (prop: string) => boolean;
|
|
196
|
+
/**
|
|
197
|
+
* Double the class selector to raise specificity from (0,1,0) to (0,2,0).
|
|
198
|
+
* Ensures this component's styles override inner library components
|
|
199
|
+
* regardless of CSS source order.
|
|
200
|
+
*/
|
|
201
|
+
boost?: boolean;
|
|
202
|
+
}
|
|
203
|
+
type TagTemplateFn = (strings: TemplateStringsArray, ...values: Interpolation[]) => ComponentFn;
|
|
204
|
+
type HtmlTags = "a" | "abbr" | "address" | "article" | "aside" | "audio" | "b" | "blockquote" | "body" | "br" | "button" | "canvas" | "caption" | "code" | "col" | "colgroup" | "dd" | "details" | "div" | "dl" | "dt" | "em" | "fieldset" | "figcaption" | "figure" | "footer" | "form" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "head" | "header" | "hr" | "html" | "i" | "iframe" | "img" | "input" | "label" | "legend" | "li" | "link" | "main" | "mark" | "menu" | "meta" | "nav" | "ol" | "optgroup" | "option" | "output" | "p" | "picture" | "pre" | "progress" | "q" | "section" | "select" | "small" | "source" | "span" | "strong" | "style" | "sub" | "summary" | "sup" | "svg" | "table" | "tbody" | "td" | "template" | "textarea" | "tfoot" | "th" | "thead" | "time" | "tr" | "u" | "ul" | "video";
|
|
205
|
+
type StyledFunction = ((tag: Tag, options?: StyledOptions) => TagTemplateFn) & { [K in HtmlTags]: TagTemplateFn };
|
|
206
|
+
declare const styled: StyledFunction;
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region src/useCSS.d.ts
|
|
209
|
+
declare function useCSS(template: CSSResult, props?: Record<string, any>, boost?: boolean): string;
|
|
210
|
+
//#endregion
|
|
211
|
+
export { type CSSResult, type DefaultTheme, HASH_INIT, type Interpolation, StyleSheet, type StyleSheetOptions, type StyledFunction, type StyledOptions, ThemeContext, ThemeProvider, buildProps, clearNormCache, createGlobalStyle, createSheet, css, filterProps, hash, hashFinalize, hashUpdate, isDynamic, keyframes, normalizeCSS, resolve, resolveValue, sheet, styled, useCSS, useTheme };
|
|
212
|
+
//# sourceMappingURL=index2.d.ts.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
import { createContext, h, onUnmount, popContext, pushContext, useContext } from "@pyreon/core";
|
|
2
|
+
|
|
3
|
+
//#region src/resolve.ts
|
|
4
|
+
/**
|
|
5
|
+
* Lazy representation of a `css` tagged template. Stores the raw template
|
|
6
|
+
* strings and interpolation values without resolving them. Resolution is
|
|
7
|
+
* deferred until a styled component renders (or until explicitly resolved).
|
|
8
|
+
*/
|
|
9
|
+
var CSSResult = class {
|
|
10
|
+
constructor(strings, values) {
|
|
11
|
+
this.strings = strings;
|
|
12
|
+
this.values = values;
|
|
13
|
+
}
|
|
14
|
+
/** Resolve with empty props — useful for static templates, testing, and debugging. */
|
|
15
|
+
toString() {
|
|
16
|
+
return resolve(this.strings, this.values, {});
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/** Resolve a tagged template's strings + values into a final CSS string. */
|
|
20
|
+
const resolve = (strings, values, props) => {
|
|
21
|
+
let result = strings[0];
|
|
22
|
+
for (let i = 0; i < values.length; i++) {
|
|
23
|
+
const v = values[i];
|
|
24
|
+
const s = strings[i + 1];
|
|
25
|
+
if (typeof v === "function") {
|
|
26
|
+
const r = v(props);
|
|
27
|
+
result += (typeof r === "string" ? r : r == null || r === false || r === true ? "" : resolveValue(r, props)) + s;
|
|
28
|
+
} else if (v == null || v === false || v === true) result += s;
|
|
29
|
+
else if (typeof v === "string") result += v + s;
|
|
30
|
+
else if (typeof v === "number") result += v + s;
|
|
31
|
+
else result += resolveValue(v, props) + s;
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Normalize resolved CSS text for strict `insertRule` compatibility.
|
|
37
|
+
*
|
|
38
|
+
* Single-pass scanner that handles all cleanup in one traversal:
|
|
39
|
+
* - Strips block comments and line comments (preserves :// in URLs)
|
|
40
|
+
* - Collapses whitespace to single spaces
|
|
41
|
+
* - Removes redundant semicolons
|
|
42
|
+
* - Trims leading/trailing whitespace
|
|
43
|
+
*/
|
|
44
|
+
const normCache = /* @__PURE__ */ new Map();
|
|
45
|
+
/** Clear the normalizeCSS cache (called during HMR cleanup). */
|
|
46
|
+
const clearNormCache = () => normCache.clear();
|
|
47
|
+
const normalizeCSS = (css) => {
|
|
48
|
+
const cached = normCache.get(css);
|
|
49
|
+
if (cached !== void 0) return cached;
|
|
50
|
+
const len = css.length;
|
|
51
|
+
let out = "";
|
|
52
|
+
let space = false;
|
|
53
|
+
let last = 0;
|
|
54
|
+
for (let i = 0; i < len; i++) {
|
|
55
|
+
const c = css.charCodeAt(i);
|
|
56
|
+
if (c === 47 && css.charCodeAt(i + 1) === 42) {
|
|
57
|
+
const end = css.indexOf("*/", i + 2);
|
|
58
|
+
i = end === -1 ? len : end + 1;
|
|
59
|
+
space = true;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (c === 47 && css.charCodeAt(i + 1) === 47 && last !== 58) {
|
|
63
|
+
const nl = css.indexOf("\n", i + 2);
|
|
64
|
+
i = nl === -1 ? len : nl;
|
|
65
|
+
space = true;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12) {
|
|
69
|
+
space = true;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (c === 59) {
|
|
73
|
+
if (last === 0 || last === 123 || last === 125 || last === 59) continue;
|
|
74
|
+
space = false;
|
|
75
|
+
out += ";";
|
|
76
|
+
last = 59;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (space && last !== 0) out += " ";
|
|
80
|
+
space = false;
|
|
81
|
+
out += css[i];
|
|
82
|
+
last = c;
|
|
83
|
+
}
|
|
84
|
+
if (normCache.size > 2e3) {
|
|
85
|
+
let count = 0;
|
|
86
|
+
for (const key of normCache.keys()) {
|
|
87
|
+
if (count >= 200) break;
|
|
88
|
+
normCache.delete(key);
|
|
89
|
+
count++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
normCache.set(css, out);
|
|
93
|
+
return out;
|
|
94
|
+
};
|
|
95
|
+
const resolveValue = (value, props) => {
|
|
96
|
+
if (value == null || value === false || value === true) return "";
|
|
97
|
+
if (typeof value === "function") return resolveValue(value(props), props);
|
|
98
|
+
if (value instanceof CSSResult) return resolve(value.strings, value.values, props);
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
let arrayResult = "";
|
|
101
|
+
for (let i = 0; i < value.length; i++) arrayResult += resolveValue(value[i], props);
|
|
102
|
+
return arrayResult;
|
|
103
|
+
}
|
|
104
|
+
return String(value);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/css.ts
|
|
109
|
+
/**
|
|
110
|
+
* Tagged template function for CSS. Captures the template strings and
|
|
111
|
+
* interpolation values as a lazy CSSResult — resolution is deferred
|
|
112
|
+
* until a styled component renders.
|
|
113
|
+
*
|
|
114
|
+
* Works as both a tagged template (`css\`...\``) and a regular function
|
|
115
|
+
* call (`css(...args)`) since tagged templates are syntactic sugar for
|
|
116
|
+
* function calls with (TemplateStringsArray, ...values).
|
|
117
|
+
*/
|
|
118
|
+
const css = (strings, ...values) => new CSSResult(strings, values);
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/forward.ts
|
|
122
|
+
/**
|
|
123
|
+
* HTML prop filtering. Prevents unknown props from being forwarded to DOM
|
|
124
|
+
* elements (which causes warnings). Props starting with `$` are
|
|
125
|
+
* transient (styling-only) and are always filtered out.
|
|
126
|
+
*/
|
|
127
|
+
const HTML_PROPS = new Set([
|
|
128
|
+
"children",
|
|
129
|
+
"className",
|
|
130
|
+
"class",
|
|
131
|
+
"dangerouslySetInnerHTML",
|
|
132
|
+
"htmlFor",
|
|
133
|
+
"id",
|
|
134
|
+
"key",
|
|
135
|
+
"ref",
|
|
136
|
+
"style",
|
|
137
|
+
"tabIndex",
|
|
138
|
+
"role",
|
|
139
|
+
"onAbort",
|
|
140
|
+
"onAnimationEnd",
|
|
141
|
+
"onAnimationIteration",
|
|
142
|
+
"onAnimationStart",
|
|
143
|
+
"onBlur",
|
|
144
|
+
"onChange",
|
|
145
|
+
"onClick",
|
|
146
|
+
"onCompositionEnd",
|
|
147
|
+
"onCompositionStart",
|
|
148
|
+
"onCompositionUpdate",
|
|
149
|
+
"onContextMenu",
|
|
150
|
+
"onCopy",
|
|
151
|
+
"onCut",
|
|
152
|
+
"onDoubleClick",
|
|
153
|
+
"onDrag",
|
|
154
|
+
"onDragEnd",
|
|
155
|
+
"onDragEnter",
|
|
156
|
+
"onDragLeave",
|
|
157
|
+
"onDragOver",
|
|
158
|
+
"onDragStart",
|
|
159
|
+
"onDrop",
|
|
160
|
+
"onError",
|
|
161
|
+
"onFocus",
|
|
162
|
+
"onInput",
|
|
163
|
+
"onKeyDown",
|
|
164
|
+
"onKeyPress",
|
|
165
|
+
"onKeyUp",
|
|
166
|
+
"onLoad",
|
|
167
|
+
"onMouseDown",
|
|
168
|
+
"onMouseEnter",
|
|
169
|
+
"onMouseLeave",
|
|
170
|
+
"onMouseMove",
|
|
171
|
+
"onMouseOut",
|
|
172
|
+
"onMouseOver",
|
|
173
|
+
"onMouseUp",
|
|
174
|
+
"onPaste",
|
|
175
|
+
"onPointerCancel",
|
|
176
|
+
"onPointerDown",
|
|
177
|
+
"onPointerEnter",
|
|
178
|
+
"onPointerLeave",
|
|
179
|
+
"onPointerMove",
|
|
180
|
+
"onPointerOut",
|
|
181
|
+
"onPointerOver",
|
|
182
|
+
"onPointerUp",
|
|
183
|
+
"onScroll",
|
|
184
|
+
"onSelect",
|
|
185
|
+
"onSubmit",
|
|
186
|
+
"onTouchCancel",
|
|
187
|
+
"onTouchEnd",
|
|
188
|
+
"onTouchMove",
|
|
189
|
+
"onTouchStart",
|
|
190
|
+
"onTransitionEnd",
|
|
191
|
+
"onWheel",
|
|
192
|
+
"accept",
|
|
193
|
+
"acceptCharset",
|
|
194
|
+
"accessKey",
|
|
195
|
+
"action",
|
|
196
|
+
"allow",
|
|
197
|
+
"allowFullScreen",
|
|
198
|
+
"alt",
|
|
199
|
+
"as",
|
|
200
|
+
"async",
|
|
201
|
+
"autoCapitalize",
|
|
202
|
+
"autoComplete",
|
|
203
|
+
"autoCorrect",
|
|
204
|
+
"autoFocus",
|
|
205
|
+
"autoPlay",
|
|
206
|
+
"capture",
|
|
207
|
+
"cellPadding",
|
|
208
|
+
"cellSpacing",
|
|
209
|
+
"charSet",
|
|
210
|
+
"checked",
|
|
211
|
+
"cite",
|
|
212
|
+
"cols",
|
|
213
|
+
"colSpan",
|
|
214
|
+
"content",
|
|
215
|
+
"contentEditable",
|
|
216
|
+
"controls",
|
|
217
|
+
"controlsList",
|
|
218
|
+
"coords",
|
|
219
|
+
"crossOrigin",
|
|
220
|
+
"dateTime",
|
|
221
|
+
"decoding",
|
|
222
|
+
"default",
|
|
223
|
+
"defaultChecked",
|
|
224
|
+
"defaultValue",
|
|
225
|
+
"defer",
|
|
226
|
+
"dir",
|
|
227
|
+
"disabled",
|
|
228
|
+
"disablePictureInPicture",
|
|
229
|
+
"disableRemotePlayback",
|
|
230
|
+
"download",
|
|
231
|
+
"draggable",
|
|
232
|
+
"encType",
|
|
233
|
+
"enterKeyHint",
|
|
234
|
+
"fetchPriority",
|
|
235
|
+
"form",
|
|
236
|
+
"formAction",
|
|
237
|
+
"formEncType",
|
|
238
|
+
"formMethod",
|
|
239
|
+
"formNoValidate",
|
|
240
|
+
"formTarget",
|
|
241
|
+
"frameBorder",
|
|
242
|
+
"headers",
|
|
243
|
+
"height",
|
|
244
|
+
"hidden",
|
|
245
|
+
"high",
|
|
246
|
+
"href",
|
|
247
|
+
"hrefLang",
|
|
248
|
+
"httpEquiv",
|
|
249
|
+
"inputMode",
|
|
250
|
+
"integrity",
|
|
251
|
+
"is",
|
|
252
|
+
"label",
|
|
253
|
+
"lang",
|
|
254
|
+
"list",
|
|
255
|
+
"loading",
|
|
256
|
+
"loop",
|
|
257
|
+
"low",
|
|
258
|
+
"max",
|
|
259
|
+
"maxLength",
|
|
260
|
+
"media",
|
|
261
|
+
"method",
|
|
262
|
+
"min",
|
|
263
|
+
"minLength",
|
|
264
|
+
"multiple",
|
|
265
|
+
"muted",
|
|
266
|
+
"name",
|
|
267
|
+
"noModule",
|
|
268
|
+
"noValidate",
|
|
269
|
+
"nonce",
|
|
270
|
+
"open",
|
|
271
|
+
"optimum",
|
|
272
|
+
"pattern",
|
|
273
|
+
"placeholder",
|
|
274
|
+
"playsInline",
|
|
275
|
+
"poster",
|
|
276
|
+
"preload",
|
|
277
|
+
"readOnly",
|
|
278
|
+
"referrerPolicy",
|
|
279
|
+
"rel",
|
|
280
|
+
"required",
|
|
281
|
+
"reversed",
|
|
282
|
+
"rows",
|
|
283
|
+
"rowSpan",
|
|
284
|
+
"sandbox",
|
|
285
|
+
"scope",
|
|
286
|
+
"scoped",
|
|
287
|
+
"scrolling",
|
|
288
|
+
"selected",
|
|
289
|
+
"shape",
|
|
290
|
+
"size",
|
|
291
|
+
"sizes",
|
|
292
|
+
"slot",
|
|
293
|
+
"span",
|
|
294
|
+
"spellCheck",
|
|
295
|
+
"src",
|
|
296
|
+
"srcDoc",
|
|
297
|
+
"srcLang",
|
|
298
|
+
"srcSet",
|
|
299
|
+
"start",
|
|
300
|
+
"step",
|
|
301
|
+
"summary",
|
|
302
|
+
"target",
|
|
303
|
+
"title",
|
|
304
|
+
"translate",
|
|
305
|
+
"type",
|
|
306
|
+
"useMap",
|
|
307
|
+
"value",
|
|
308
|
+
"width",
|
|
309
|
+
"wrap"
|
|
310
|
+
]);
|
|
311
|
+
/**
|
|
312
|
+
* Filters props for HTML elements. Keeps valid HTML attrs, data-*, aria-*.
|
|
313
|
+
* Rejects unknown props and $-prefixed transient props.
|
|
314
|
+
*/
|
|
315
|
+
const filterProps = (props) => {
|
|
316
|
+
const filtered = {};
|
|
317
|
+
for (const key in props) {
|
|
318
|
+
if (key.charCodeAt(0) === 36) continue;
|
|
319
|
+
if (key === "as") continue;
|
|
320
|
+
if (key.startsWith("data-") || key.startsWith("aria-")) {
|
|
321
|
+
filtered[key] = props[key];
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (HTML_PROPS.has(key)) filtered[key] = props[key];
|
|
325
|
+
}
|
|
326
|
+
return filtered;
|
|
327
|
+
};
|
|
328
|
+
/**
|
|
329
|
+
* Build final props for a styled component in a single pass.
|
|
330
|
+
* Combines className merging, ref injection, and prop filtering into one
|
|
331
|
+
* allocation and one iteration.
|
|
332
|
+
*/
|
|
333
|
+
const buildProps = (rawProps, generatedCls, isDOM, customFilter) => {
|
|
334
|
+
const result = {};
|
|
335
|
+
const userCls = rawProps.class || rawProps.className;
|
|
336
|
+
if (generatedCls) result.class = userCls ? `${generatedCls} ${userCls}` : generatedCls;
|
|
337
|
+
else if (userCls) result.class = userCls;
|
|
338
|
+
if (!isDOM) {
|
|
339
|
+
for (const key in rawProps) {
|
|
340
|
+
if (key === "as" || key === "className" || key === "class") continue;
|
|
341
|
+
if (key.charCodeAt(0) === 36) continue;
|
|
342
|
+
result[key] = rawProps[key];
|
|
343
|
+
}
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
if (customFilter) {
|
|
347
|
+
for (const key in rawProps) {
|
|
348
|
+
if (key === "as" || key === "className" || key === "class") continue;
|
|
349
|
+
if (customFilter(key)) result[key] = rawProps[key];
|
|
350
|
+
}
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
for (const key in rawProps) {
|
|
354
|
+
if (key === "as" || key === "className" || key === "class") continue;
|
|
355
|
+
if (key.charCodeAt(0) === 36) continue;
|
|
356
|
+
if (key.startsWith("data-") || key.startsWith("aria-")) {
|
|
357
|
+
result[key] = rawProps[key];
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (HTML_PROPS.has(key)) result[key] = rawProps[key];
|
|
361
|
+
}
|
|
362
|
+
return result;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
//#endregion
|
|
366
|
+
//#region src/shared.ts
|
|
367
|
+
/**
|
|
368
|
+
* Shared utilities used across multiple modules.
|
|
369
|
+
*/
|
|
370
|
+
/** Check if an interpolation value is dynamic (contains functions or nested dynamic CSSResults). */
|
|
371
|
+
const isDynamic = (v) => {
|
|
372
|
+
if (typeof v === "function") return true;
|
|
373
|
+
if (Array.isArray(v)) return v.some(isDynamic);
|
|
374
|
+
if (v instanceof CSSResult) return v.values.some(isDynamic);
|
|
375
|
+
return false;
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/hash.ts
|
|
380
|
+
/**
|
|
381
|
+
* Fast FNV-1a non-cryptographic hash. Returns base-36 string for compact class names.
|
|
382
|
+
*
|
|
383
|
+
* 32-bit hash space → ~4.3 billion unique values. Collision probability is
|
|
384
|
+
* negligible for typical applications (< 10,000 unique CSS rules).
|
|
385
|
+
*/
|
|
386
|
+
/** FNV-1a offset basis — starting state for streaming hash. */
|
|
387
|
+
const HASH_INIT = 2166136261;
|
|
388
|
+
const FNV_PRIME = 16777619;
|
|
389
|
+
/**
|
|
390
|
+
* Feed a string segment into the running hash state.
|
|
391
|
+
* Streaming: hashUpdate(hashUpdate(HASH_INIT, 'ab'), 'cd') === hash('abcd').
|
|
392
|
+
*/
|
|
393
|
+
const hashUpdate = (init, str) => {
|
|
394
|
+
let h = init;
|
|
395
|
+
for (let i = 0; i < str.length; i++) {
|
|
396
|
+
h ^= str.charCodeAt(i);
|
|
397
|
+
h = Math.imul(h, FNV_PRIME);
|
|
398
|
+
}
|
|
399
|
+
return h;
|
|
400
|
+
};
|
|
401
|
+
/** Finalize a hash state into a base-36 class name suffix. */
|
|
402
|
+
const hashFinalize = (h) => (h >>> 0).toString(36);
|
|
403
|
+
/** Hash a complete string in one shot. Returns base-36 string. */
|
|
404
|
+
const hash = (str) => hashFinalize(hashUpdate(HASH_INIT, str));
|
|
405
|
+
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region src/sheet.ts
|
|
408
|
+
/**
|
|
409
|
+
* StyleSheet manager. Handles CSS rule injection, hash-based deduplication,
|
|
410
|
+
* SSR buffering, client-side hydration, bounded cache, and @layer support.
|
|
411
|
+
*
|
|
412
|
+
* Media queries (@media), @supports, and @container blocks nested inside
|
|
413
|
+
* component CSS are automatically extracted into separate top-level rules.
|
|
414
|
+
*/
|
|
415
|
+
const PREFIX = "pyr";
|
|
416
|
+
const ATTR = "data-pyreon-styler";
|
|
417
|
+
const DEFAULT_MAX_CACHE_SIZE = 1e4;
|
|
418
|
+
var StyleSheet = class {
|
|
419
|
+
cache = /* @__PURE__ */ new Map();
|
|
420
|
+
insertCache = /* @__PURE__ */ new Map();
|
|
421
|
+
sheet = null;
|
|
422
|
+
ssrBuffer = [];
|
|
423
|
+
isSSR;
|
|
424
|
+
maxCacheSize;
|
|
425
|
+
layer;
|
|
426
|
+
constructor(options = {}) {
|
|
427
|
+
this.maxCacheSize = options.maxCacheSize ?? DEFAULT_MAX_CACHE_SIZE;
|
|
428
|
+
this.layer = options.layer;
|
|
429
|
+
this.isSSR = typeof document === "undefined";
|
|
430
|
+
if (!this.isSSR) this.mount();
|
|
431
|
+
}
|
|
432
|
+
mount() {
|
|
433
|
+
const existing = document.querySelector(`style[${ATTR}]`);
|
|
434
|
+
if (existing) {
|
|
435
|
+
this.sheet = existing.sheet ?? null;
|
|
436
|
+
this.hydrateFromTag(existing);
|
|
437
|
+
} else {
|
|
438
|
+
const el = document.createElement("style");
|
|
439
|
+
el.setAttribute(ATTR, "");
|
|
440
|
+
document.head.appendChild(el);
|
|
441
|
+
this.sheet = el.sheet ?? null;
|
|
442
|
+
}
|
|
443
|
+
if (this.layer && this.sheet) try {
|
|
444
|
+
this.sheet.insertRule(`@layer ${this.layer};`, 0);
|
|
445
|
+
} catch {}
|
|
446
|
+
}
|
|
447
|
+
/** Extract className from a selector like ".pyr-abc" or ".pyr-abc.pyr-abc" → "pyr-abc" */
|
|
448
|
+
extractClassName(selectorText) {
|
|
449
|
+
if (selectorText[0] !== ".") return null;
|
|
450
|
+
const dotIdx = selectorText.indexOf(".", 1);
|
|
451
|
+
return dotIdx > 0 ? selectorText.slice(1, dotIdx) : selectorText.slice(1);
|
|
452
|
+
}
|
|
453
|
+
/** Parse existing rules from SSR-rendered <style> tag into cache. */
|
|
454
|
+
hydrateFromTag(el) {
|
|
455
|
+
const sheet = el.sheet;
|
|
456
|
+
if (!sheet) return;
|
|
457
|
+
for (let i = 0; i < sheet.cssRules.length; i++) {
|
|
458
|
+
const rule = sheet.cssRules[i];
|
|
459
|
+
if (rule instanceof CSSStyleRule) {
|
|
460
|
+
const className = this.extractClassName(rule.selectorText);
|
|
461
|
+
if (className) this.cache.set(className, className);
|
|
462
|
+
}
|
|
463
|
+
if (typeof CSSMediaRule !== "undefined" && rule instanceof CSSMediaRule) for (let j = 0; j < rule.cssRules.length; j++) {
|
|
464
|
+
const inner = rule.cssRules[j];
|
|
465
|
+
if (inner instanceof CSSStyleRule) {
|
|
466
|
+
const className = this.extractClassName(inner.selectorText);
|
|
467
|
+
if (className) this.cache.set(className, className);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/** Evict oldest entries when cache exceeds max size. */
|
|
473
|
+
evictIfNeeded() {
|
|
474
|
+
if (this.cache.size <= this.maxCacheSize) return;
|
|
475
|
+
const toDelete = Math.floor(this.maxCacheSize * .1);
|
|
476
|
+
let count = 0;
|
|
477
|
+
for (const key of this.cache.keys()) {
|
|
478
|
+
if (count >= toDelete) break;
|
|
479
|
+
this.cache.delete(key);
|
|
480
|
+
count++;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Extract nested at-rules (@media, @supports, @container) from CSS text
|
|
485
|
+
* and wrap their content in the given selector as separate top-level rules.
|
|
486
|
+
*/
|
|
487
|
+
splitAtRules(cssText, selector) {
|
|
488
|
+
if (cssText.indexOf("@") === -1) return {
|
|
489
|
+
base: cssText,
|
|
490
|
+
atRules: []
|
|
491
|
+
};
|
|
492
|
+
const atRules = [];
|
|
493
|
+
const baseParts = [];
|
|
494
|
+
let depth = 0;
|
|
495
|
+
let atStart = -1;
|
|
496
|
+
let lastBase = 0;
|
|
497
|
+
for (let i = 0; i < cssText.length; i++) {
|
|
498
|
+
const ch = cssText[i];
|
|
499
|
+
if (ch === "{") depth++;
|
|
500
|
+
else if (ch === "}") {
|
|
501
|
+
depth--;
|
|
502
|
+
if (depth === 0 && atStart >= 0) {
|
|
503
|
+
const openBrace = cssText.indexOf("{", atStart);
|
|
504
|
+
const atPrefix = cssText.slice(atStart, openBrace).trim();
|
|
505
|
+
const innerCSS = cssText.slice(openBrace + 1, i).trim();
|
|
506
|
+
if (innerCSS) atRules.push(`${atPrefix}{${selector}{${innerCSS}}}`);
|
|
507
|
+
atStart = -1;
|
|
508
|
+
lastBase = i + 1;
|
|
509
|
+
}
|
|
510
|
+
} else if (depth === 0 && ch === "@" && atStart < 0) {
|
|
511
|
+
const remaining = cssText.slice(i, i + 20);
|
|
512
|
+
if (/^@(?:media|supports|container)\b/.test(remaining)) {
|
|
513
|
+
const baseBefore = cssText.slice(lastBase, i).trim();
|
|
514
|
+
if (baseBefore) baseParts.push(baseBefore);
|
|
515
|
+
atStart = i;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (lastBase < cssText.length && atStart < 0) {
|
|
520
|
+
const remaining = cssText.slice(lastBase).trim();
|
|
521
|
+
if (remaining) baseParts.push(remaining);
|
|
522
|
+
}
|
|
523
|
+
if (atRules.length === 0) return {
|
|
524
|
+
base: cssText,
|
|
525
|
+
atRules: []
|
|
526
|
+
};
|
|
527
|
+
return {
|
|
528
|
+
base: baseParts.join(" "),
|
|
529
|
+
atRules
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Compute a className from CSS text without injecting (pure function).
|
|
534
|
+
*/
|
|
535
|
+
getClassName(cssText) {
|
|
536
|
+
const cached = this.insertCache.get(cssText);
|
|
537
|
+
if (cached) return cached;
|
|
538
|
+
return `${PREFIX}-${hash(cssText)}`;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Insert CSS rules for a component. Returns the class name (deterministic, hash-based).
|
|
542
|
+
* Deduplicates: same CSS text always produces the same class name and
|
|
543
|
+
* the rules are only injected once.
|
|
544
|
+
*
|
|
545
|
+
* When `boost` is true, the selector is doubled (`.pyr-abc.pyr-abc`)
|
|
546
|
+
* to raise specificity from (0,1,0) to (0,2,0).
|
|
547
|
+
*/
|
|
548
|
+
insert(cssText, boost = false) {
|
|
549
|
+
const icKey = boost ? `${cssText}\0` : cssText;
|
|
550
|
+
const icHit = this.insertCache.get(icKey);
|
|
551
|
+
if (icHit) return icHit;
|
|
552
|
+
const className = `${PREFIX}-${hash(cssText)}`;
|
|
553
|
+
if (this.cache.has(className)) {
|
|
554
|
+
this.insertCache.set(icKey, className);
|
|
555
|
+
return className;
|
|
556
|
+
}
|
|
557
|
+
this.evictIfNeeded();
|
|
558
|
+
this.cache.set(className, className);
|
|
559
|
+
const selector = boost ? `.${className}.${className}` : `.${className}`;
|
|
560
|
+
const { base, atRules } = this.splitAtRules(cssText, selector);
|
|
561
|
+
const rules = [];
|
|
562
|
+
if (base) rules.push(`${selector}{${base}}`);
|
|
563
|
+
rules.push(...atRules);
|
|
564
|
+
const finalRules = this.layer ? rules.map((r) => `@layer ${this.layer}{${r}}`) : rules;
|
|
565
|
+
if (this.isSSR) for (const rule of finalRules) this.ssrBuffer.push(rule);
|
|
566
|
+
else if (this.sheet) for (const rule of finalRules) try {
|
|
567
|
+
this.sheet.insertRule(rule, this.sheet.cssRules.length);
|
|
568
|
+
} catch (_e) {
|
|
569
|
+
if (process.env.NODE_ENV !== "production") console.warn("[styler] Failed to insert CSS rule:", rule, _e);
|
|
570
|
+
}
|
|
571
|
+
this.insertCache.set(icKey, className);
|
|
572
|
+
return className;
|
|
573
|
+
}
|
|
574
|
+
/** Insert a @keyframes rule. Deduplicates by animation name. */
|
|
575
|
+
insertKeyframes(name, body) {
|
|
576
|
+
if (this.cache.has(name)) return;
|
|
577
|
+
this.evictIfNeeded();
|
|
578
|
+
this.cache.set(name, name);
|
|
579
|
+
const rule = `@keyframes ${name}{${body}}`;
|
|
580
|
+
if (this.isSSR) this.ssrBuffer.push(rule);
|
|
581
|
+
else if (this.sheet) try {
|
|
582
|
+
this.sheet.insertRule(rule, this.sheet.cssRules.length);
|
|
583
|
+
} catch (_e) {
|
|
584
|
+
if (process.env.NODE_ENV !== "production") {}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Split CSS text into individual top-level rules.
|
|
589
|
+
* CSSStyleSheet.insertRule() only accepts one rule at a time.
|
|
590
|
+
*/
|
|
591
|
+
splitRules(cssText) {
|
|
592
|
+
const rules = [];
|
|
593
|
+
let depth = 0;
|
|
594
|
+
let start = 0;
|
|
595
|
+
for (let i = 0; i < cssText.length; i++) {
|
|
596
|
+
const ch = cssText[i];
|
|
597
|
+
if (ch === "{") depth++;
|
|
598
|
+
else if (ch === "}") {
|
|
599
|
+
depth--;
|
|
600
|
+
if (depth === 0) {
|
|
601
|
+
const rule = cssText.slice(start, i + 1).trim();
|
|
602
|
+
if (rule) rules.push(rule);
|
|
603
|
+
start = i + 1;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return rules;
|
|
608
|
+
}
|
|
609
|
+
/** Insert global CSS rules (no wrapper selector). Deduplicates by hash. */
|
|
610
|
+
insertGlobal(cssText) {
|
|
611
|
+
const key = `global-${hash(cssText)}`;
|
|
612
|
+
if (this.cache.has(key)) return;
|
|
613
|
+
this.evictIfNeeded();
|
|
614
|
+
this.cache.set(key, key);
|
|
615
|
+
if (this.isSSR) this.ssrBuffer.push(cssText);
|
|
616
|
+
else if (this.sheet) {
|
|
617
|
+
const rules = this.splitRules(cssText);
|
|
618
|
+
for (const rule of rules) try {
|
|
619
|
+
this.sheet.insertRule(rule, this.sheet.cssRules.length);
|
|
620
|
+
} catch (_e) {
|
|
621
|
+
if (process.env.NODE_ENV !== "production") console.warn("[styler] Failed to insert global CSS rule:", rule, _e);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
/** Returns collected CSS for SSR as a complete `<style>` tag string. */
|
|
626
|
+
getStyleTag() {
|
|
627
|
+
return `<style ${ATTR}="">${this.ssrBuffer.join("").replace(/<\/style/gi, "<\\/style")}</style>`;
|
|
628
|
+
}
|
|
629
|
+
/** Returns collected CSS rules as a raw string (useful for streaming SSR). */
|
|
630
|
+
getStyles() {
|
|
631
|
+
return this.ssrBuffer.join("");
|
|
632
|
+
}
|
|
633
|
+
/** Reset SSR buffer and cache (call between server requests). */
|
|
634
|
+
reset() {
|
|
635
|
+
this.ssrBuffer = [];
|
|
636
|
+
this.cache.clear();
|
|
637
|
+
this.insertCache.clear();
|
|
638
|
+
}
|
|
639
|
+
/** Clear the dedup cache. Useful for HMR / dev-time reloads. */
|
|
640
|
+
clearCache() {
|
|
641
|
+
this.cache.clear();
|
|
642
|
+
this.insertCache.clear();
|
|
643
|
+
clearNormCache();
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Full cleanup: clear cache and remove all CSS rules from the DOM.
|
|
647
|
+
* Intended for HMR / dev-time reloads where stale styles must be purged.
|
|
648
|
+
*/
|
|
649
|
+
clearAll() {
|
|
650
|
+
this.cache.clear();
|
|
651
|
+
this.insertCache.clear();
|
|
652
|
+
clearNormCache();
|
|
653
|
+
this.ssrBuffer = [];
|
|
654
|
+
if (this.sheet) while (this.sheet.cssRules.length > 0) this.sheet.deleteRule(0);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Compute className and full CSS rule text without injecting.
|
|
658
|
+
*/
|
|
659
|
+
prepare(cssText, boost = false) {
|
|
660
|
+
const className = `${PREFIX}-${hash(cssText)}`;
|
|
661
|
+
const selector = boost ? `.${className}.${className}` : `.${className}`;
|
|
662
|
+
const { base, atRules } = this.splitAtRules(cssText, selector);
|
|
663
|
+
const allRules = [];
|
|
664
|
+
if (base) allRules.push(`${selector}{${base}}`);
|
|
665
|
+
allRules.push(...atRules);
|
|
666
|
+
return {
|
|
667
|
+
className,
|
|
668
|
+
rules: (this.layer ? allRules.map((r) => `@layer ${this.layer}{${r}}`) : allRules).join("")
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
/** Check if a className is already in the cache. O(1) Map lookup. */
|
|
672
|
+
has(className) {
|
|
673
|
+
return this.cache.has(className);
|
|
674
|
+
}
|
|
675
|
+
/** Current number of cached rules. */
|
|
676
|
+
get cacheSize() {
|
|
677
|
+
return this.cache.size;
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
/** Default singleton sheet for client-side use. */
|
|
681
|
+
const sheet = new StyleSheet();
|
|
682
|
+
/**
|
|
683
|
+
* Factory for creating isolated StyleSheet instances.
|
|
684
|
+
* Use in SSR to get per-request isolation.
|
|
685
|
+
*/
|
|
686
|
+
const createSheet = (options) => new StyleSheet(options);
|
|
687
|
+
|
|
688
|
+
//#endregion
|
|
689
|
+
//#region src/ThemeProvider.ts
|
|
690
|
+
const ThemeContext = createContext({});
|
|
691
|
+
/** Hook to read the current theme from the nearest ThemeProvider. */
|
|
692
|
+
const useTheme = () => useContext(ThemeContext);
|
|
693
|
+
/** Provides a theme object to all nested styled components via Pyreon context. */
|
|
694
|
+
function ThemeProvider({ theme, children }) {
|
|
695
|
+
pushContext(new Map([[ThemeContext.id, theme]]));
|
|
696
|
+
onUnmount(() => popContext());
|
|
697
|
+
return children ?? null;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/globalStyle.ts
|
|
702
|
+
const createGlobalStyle = (strings, ...values) => {
|
|
703
|
+
if (!values.some(isDynamic)) {
|
|
704
|
+
const cssText = normalizeCSS(resolve(strings, values, {}));
|
|
705
|
+
if (cssText.trim()) sheet.insertGlobal(cssText);
|
|
706
|
+
const StaticGlobal = () => null;
|
|
707
|
+
return StaticGlobal;
|
|
708
|
+
}
|
|
709
|
+
const DynamicGlobal = (props) => {
|
|
710
|
+
const theme = useTheme();
|
|
711
|
+
const cssText = normalizeCSS(resolve(strings, values, {
|
|
712
|
+
...props,
|
|
713
|
+
theme
|
|
714
|
+
}));
|
|
715
|
+
if (cssText.trim()) sheet.insertGlobal(cssText);
|
|
716
|
+
return null;
|
|
717
|
+
};
|
|
718
|
+
return DynamicGlobal;
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
//#endregion
|
|
722
|
+
//#region src/keyframes.ts
|
|
723
|
+
/**
|
|
724
|
+
* keyframes() tagged template function. Creates a CSS @keyframes rule,
|
|
725
|
+
* injects it into the stylesheet, and returns the generated animation name.
|
|
726
|
+
*
|
|
727
|
+
* Usage:
|
|
728
|
+
* const fadeIn = keyframes`
|
|
729
|
+
* from { opacity: 0; }
|
|
730
|
+
* to { opacity: 1; }
|
|
731
|
+
* `
|
|
732
|
+
* // fadeIn === "pyr-kf-abc123" (deterministic, hash-based)
|
|
733
|
+
*/
|
|
734
|
+
var KeyframesResult = class {
|
|
735
|
+
name;
|
|
736
|
+
constructor(strings, values) {
|
|
737
|
+
const body = normalizeCSS(resolve(strings, values, {}));
|
|
738
|
+
this.name = `pyr-kf-${hash(body)}`;
|
|
739
|
+
sheet.insertKeyframes(this.name, body);
|
|
740
|
+
}
|
|
741
|
+
/** Returns the animation name when used in string context. */
|
|
742
|
+
toString() {
|
|
743
|
+
return this.name;
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
const keyframes = (strings, ...values) => new KeyframesResult(strings, values);
|
|
747
|
+
|
|
748
|
+
//#endregion
|
|
749
|
+
//#region src/styled.tsx
|
|
750
|
+
const getDisplayName = (tag) => typeof tag === "string" ? tag : tag.displayName || tag.name || "Component";
|
|
751
|
+
const staticComponentCache = /* @__PURE__ */ new WeakMap();
|
|
752
|
+
let _hotStrings = null;
|
|
753
|
+
let _hotTag = null;
|
|
754
|
+
let _hotComponent = null;
|
|
755
|
+
const createStyledComponent = (tag, strings, values, options) => {
|
|
756
|
+
if (values.length === 0 && !options) {
|
|
757
|
+
if (strings === _hotStrings && tag === _hotTag) return _hotComponent;
|
|
758
|
+
const tagMap = staticComponentCache.get(strings);
|
|
759
|
+
if (tagMap) {
|
|
760
|
+
const cached = tagMap.get(tag);
|
|
761
|
+
if (cached) {
|
|
762
|
+
_hotStrings = strings;
|
|
763
|
+
_hotTag = tag;
|
|
764
|
+
_hotComponent = cached;
|
|
765
|
+
return cached;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
const hasDynamicValues = values.length > 0 && values.some(isDynamic);
|
|
770
|
+
const customFilter = options ? options.shouldForwardProp : void 0;
|
|
771
|
+
const boost = options ? options.boost ?? false : false;
|
|
772
|
+
if (!hasDynamicValues) {
|
|
773
|
+
const cssText = normalizeCSS(values.length === 0 ? strings[0] : resolve(strings, values, {}));
|
|
774
|
+
const staticClassName = cssText.length > 0 ? sheet.insert(cssText, boost) : "";
|
|
775
|
+
const StaticStyled = (rawProps) => {
|
|
776
|
+
const finalTag = rawProps.as || tag;
|
|
777
|
+
return h(finalTag, buildProps(rawProps, staticClassName, typeof finalTag === "string", customFilter), ...Array.isArray(rawProps.children) ? rawProps.children : rawProps.children != null ? [rawProps.children] : []);
|
|
778
|
+
};
|
|
779
|
+
StaticStyled.displayName = `styled(${getDisplayName(tag)})`;
|
|
780
|
+
if (!options && values.length === 0) {
|
|
781
|
+
let tagMap = staticComponentCache.get(strings);
|
|
782
|
+
if (!tagMap) {
|
|
783
|
+
tagMap = /* @__PURE__ */ new Map();
|
|
784
|
+
staticComponentCache.set(strings, tagMap);
|
|
785
|
+
}
|
|
786
|
+
tagMap.set(tag, StaticStyled);
|
|
787
|
+
_hotStrings = strings;
|
|
788
|
+
_hotTag = tag;
|
|
789
|
+
_hotComponent = StaticStyled;
|
|
790
|
+
}
|
|
791
|
+
return StaticStyled;
|
|
792
|
+
}
|
|
793
|
+
const DynamicStyled = (rawProps) => {
|
|
794
|
+
const theme = useTheme();
|
|
795
|
+
const cssText = normalizeCSS(resolve(strings, values, {
|
|
796
|
+
...rawProps,
|
|
797
|
+
theme
|
|
798
|
+
}));
|
|
799
|
+
const className = cssText.length > 0 ? sheet.insert(cssText, boost) : "";
|
|
800
|
+
const finalTag = rawProps.as || tag;
|
|
801
|
+
return h(finalTag, buildProps(rawProps, className, typeof finalTag === "string", customFilter), ...Array.isArray(rawProps.children) ? rawProps.children : rawProps.children != null ? [rawProps.children] : []);
|
|
802
|
+
};
|
|
803
|
+
DynamicStyled.displayName = `styled(${getDisplayName(tag)})`;
|
|
804
|
+
return DynamicStyled;
|
|
805
|
+
};
|
|
806
|
+
/** Factory function: styled(tag) returns a tagged template function. */
|
|
807
|
+
const styledFactory = (tag, options) => {
|
|
808
|
+
const templateFn = (strings, ...values) => createStyledComponent(tag, strings, values, options);
|
|
809
|
+
return templateFn;
|
|
810
|
+
};
|
|
811
|
+
/**
|
|
812
|
+
* Main styled export. Supports both calling conventions:
|
|
813
|
+
* - `styled('div')` or `styled(Component)` → returns tagged template function
|
|
814
|
+
* - `styled('div', { shouldForwardProp })` → with custom prop filtering
|
|
815
|
+
* - `styled.div` → shorthand via Proxy (no options)
|
|
816
|
+
*/
|
|
817
|
+
const proxyCache = /* @__PURE__ */ new Map();
|
|
818
|
+
const styled = new Proxy(styledFactory, { get(_target, prop) {
|
|
819
|
+
if (prop === "prototype" || prop === "$$typeof") return void 0;
|
|
820
|
+
let fn = proxyCache.get(prop);
|
|
821
|
+
if (!fn) {
|
|
822
|
+
fn = (strings, ...values) => createStyledComponent(prop, strings, values);
|
|
823
|
+
proxyCache.set(prop, fn);
|
|
824
|
+
}
|
|
825
|
+
return fn;
|
|
826
|
+
} });
|
|
827
|
+
|
|
828
|
+
//#endregion
|
|
829
|
+
//#region src/useCSS.ts
|
|
830
|
+
/**
|
|
831
|
+
* Hook that resolves a CSSResult template with props, injects CSS
|
|
832
|
+
* into the shared stylesheet, and returns the className.
|
|
833
|
+
*
|
|
834
|
+
* Use this when you need computed CSS class names on plain elements
|
|
835
|
+
* without the overhead of a styled component layer.
|
|
836
|
+
*/
|
|
837
|
+
function useCSS(template, props, boost) {
|
|
838
|
+
const theme = useTheme();
|
|
839
|
+
const allProps = theme ? {
|
|
840
|
+
...props,
|
|
841
|
+
theme
|
|
842
|
+
} : props ?? {};
|
|
843
|
+
const cssText = normalizeCSS(resolve(template.strings, template.values, allProps));
|
|
844
|
+
if (!cssText.trim()) return "";
|
|
845
|
+
return sheet.insert(cssText, boost);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
//#endregion
|
|
849
|
+
export { HASH_INIT, StyleSheet, ThemeContext, ThemeProvider, buildProps, clearNormCache, createGlobalStyle, createSheet, css, filterProps, hash, hashFinalize, hashUpdate, isDynamic, keyframes, normalizeCSS, resolve, resolveValue, sheet, styled, useCSS, useTheme };
|
|
850
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pyreon/styler",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Lightweight CSS-in-JS engine for Pyreon",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
"source": "./src/index.ts",
|
|
10
|
+
"import": "./lib/index.js",
|
|
11
|
+
"types": "./lib/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"types": "./lib/index.d.ts",
|
|
14
|
+
"main": "./lib/index.js",
|
|
15
|
+
"files": [
|
|
16
|
+
"lib",
|
|
17
|
+
"!lib/**/*.map",
|
|
18
|
+
"!lib/analysis",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">= 18"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"prepublish": "bun run build",
|
|
30
|
+
"build": "bun run vl_rolldown_build",
|
|
31
|
+
"build:watch": "bun run vl_rolldown_build-watch",
|
|
32
|
+
"lint": "biome check src/",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:coverage": "vitest run --coverage",
|
|
35
|
+
"test:watch": "vitest",
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@pyreon/core": ">=0.3.0",
|
|
40
|
+
"@pyreon/reactivity": ">=0.3.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@vitus-labs/tools-rolldown": "^1.15.0",
|
|
44
|
+
"@vitus-labs/tools-typescript": "^1.15.0"
|
|
45
|
+
}
|
|
46
|
+
}
|