@pyreon/styler 0.24.5 → 0.24.6
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/package.json +5 -7
- package/src/ThemeProvider.ts +0 -65
- package/src/__tests__/ThemeProvider.test.ts +0 -67
- package/src/__tests__/benchmark.bench.ts +0 -200
- package/src/__tests__/composition-chain.test.ts +0 -537
- package/src/__tests__/css.test.ts +0 -70
- package/src/__tests__/dev-gate-treeshake.test.ts +0 -85
- package/src/__tests__/forward.test.ts +0 -282
- package/src/__tests__/globalStyle.test.ts +0 -72
- package/src/__tests__/hash.test.ts +0 -70
- package/src/__tests__/hybrid-injection.test.ts +0 -225
- package/src/__tests__/index.ts +0 -14
- package/src/__tests__/inject-rules.browser.test.ts +0 -40
- package/src/__tests__/insertion-effect.test.ts +0 -119
- package/src/__tests__/integration-dom.test.ts +0 -58
- package/src/__tests__/integration.test.ts +0 -179
- package/src/__tests__/keyframes.test.ts +0 -68
- package/src/__tests__/memory-growth.test.ts +0 -220
- package/src/__tests__/native-marker.test.ts +0 -9
- package/src/__tests__/p3-features.test.ts +0 -316
- package/src/__tests__/resolve-cache.test.ts +0 -94
- package/src/__tests__/resolve.test.ts +0 -308
- package/src/__tests__/shared.test.ts +0 -133
- package/src/__tests__/sheet-advanced.test.ts +0 -659
- package/src/__tests__/sheet-split-atrules.test.ts +0 -410
- package/src/__tests__/sheet.test.ts +0 -250
- package/src/__tests__/static-styler-resolve-cost.test.ts +0 -160
- package/src/__tests__/styled-reactive.test.ts +0 -74
- package/src/__tests__/styled-ssr.test.ts +0 -75
- package/src/__tests__/styled.test.ts +0 -511
- package/src/__tests__/styler.browser.test.tsx +0 -194
- package/src/__tests__/theme.test.ts +0 -33
- package/src/__tests__/useCSS.test.ts +0 -172
- package/src/css.ts +0 -13
- package/src/env.d.ts +0 -6
- package/src/forward.ts +0 -308
- package/src/globalStyle.ts +0 -53
- package/src/hash.ts +0 -28
- package/src/index.ts +0 -15
- package/src/keyframes.ts +0 -36
- package/src/manifest.ts +0 -332
- package/src/resolve.ts +0 -225
- package/src/shared.ts +0 -22
- package/src/sheet.ts +0 -635
- package/src/styled.tsx +0 -503
- package/src/tests/manifest-snapshot.test.ts +0 -51
- package/src/useCSS.ts +0 -20
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { ThemeContext, useTheme } from '../ThemeProvider'
|
|
3
|
-
|
|
4
|
-
describe('ThemeContext', () => {
|
|
5
|
-
it('is a Context object', () => {
|
|
6
|
-
expect(ThemeContext).toBeDefined()
|
|
7
|
-
expect(ThemeContext.id).toBeDefined()
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
it('has an id property for context identification', () => {
|
|
11
|
-
expect(typeof ThemeContext.id).toBe('symbol')
|
|
12
|
-
})
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
describe('useTheme', () => {
|
|
16
|
-
it('is a function', () => {
|
|
17
|
-
expect(typeof useTheme).toBe('function')
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('returns the default theme (empty object) when called outside a provider', () => {
|
|
21
|
-
const theme = useTheme()
|
|
22
|
-
expect(theme).toEqual({})
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('can be called with a type parameter', () => {
|
|
26
|
-
interface MyTheme {
|
|
27
|
-
primary: string
|
|
28
|
-
spacing: number
|
|
29
|
-
}
|
|
30
|
-
const theme = useTheme<MyTheme>()
|
|
31
|
-
expect(theme).toBeDefined()
|
|
32
|
-
})
|
|
33
|
-
})
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
-
import { css } from '../css'
|
|
3
|
-
import { sheet } from '../sheet'
|
|
4
|
-
import { useCSS } from '../useCSS'
|
|
5
|
-
|
|
6
|
-
describe('useCSS', () => {
|
|
7
|
-
afterEach(() => {
|
|
8
|
-
sheet.clearAll()
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
describe('basic usage', () => {
|
|
12
|
-
it('returns a className string for static CSS', () => {
|
|
13
|
-
const template = css`
|
|
14
|
-
display: flex;
|
|
15
|
-
`
|
|
16
|
-
const result = useCSS(template)
|
|
17
|
-
expect(typeof result).toBe('string')
|
|
18
|
-
expect(result).toMatch(/^pyr-[0-9a-z]+$/)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('returns different classNames for different CSS', () => {
|
|
22
|
-
const template1 = css`
|
|
23
|
-
display: flex;
|
|
24
|
-
`
|
|
25
|
-
const template2 = css`
|
|
26
|
-
display: block;
|
|
27
|
-
`
|
|
28
|
-
|
|
29
|
-
const r1 = useCSS(template1)
|
|
30
|
-
const r2 = useCSS(template2)
|
|
31
|
-
|
|
32
|
-
expect(r1).toMatch(/^pyr-/)
|
|
33
|
-
expect(r2).toMatch(/^pyr-/)
|
|
34
|
-
expect(r1).not.toBe(r2)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('returns empty string for empty CSS', () => {
|
|
38
|
-
const template = css``
|
|
39
|
-
const result = useCSS(template)
|
|
40
|
-
expect(result).toBe('')
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('returns empty string for whitespace-only CSS', () => {
|
|
44
|
-
const template = css``
|
|
45
|
-
const result = useCSS(template)
|
|
46
|
-
expect(result).toBe('')
|
|
47
|
-
})
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
describe('dynamic values', () => {
|
|
51
|
-
it('works with static interpolation values', () => {
|
|
52
|
-
const color = 'red'
|
|
53
|
-
const template = css`
|
|
54
|
-
color: ${color};
|
|
55
|
-
`
|
|
56
|
-
const result = useCSS(template)
|
|
57
|
-
expect(result).toMatch(/^pyr-[0-9a-z]+$/)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('works with function interpolations resolved via props', () => {
|
|
61
|
-
const template = css`
|
|
62
|
-
color: ${(p: any) => p.color};
|
|
63
|
-
`
|
|
64
|
-
const result = useCSS(template, { color: 'blue' })
|
|
65
|
-
expect(result).toMatch(/^pyr-[0-9a-z]+$/)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('different prop values produce different classNames', () => {
|
|
69
|
-
const template = css`
|
|
70
|
-
color: ${(p: any) => p.color};
|
|
71
|
-
`
|
|
72
|
-
|
|
73
|
-
const r1 = useCSS(template, { color: 'red' })
|
|
74
|
-
const r2 = useCSS(template, { color: 'green' })
|
|
75
|
-
|
|
76
|
-
expect(r1).not.toBe(r2)
|
|
77
|
-
})
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
describe('caching', () => {
|
|
81
|
-
it('same CSS returns same className on repeated calls', () => {
|
|
82
|
-
const template = css`
|
|
83
|
-
display: flex;
|
|
84
|
-
`
|
|
85
|
-
const cls1 = useCSS(template)
|
|
86
|
-
const cls2 = useCSS(template)
|
|
87
|
-
expect(cls1).toBe(cls2)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('same dynamic CSS with same props returns same className', () => {
|
|
91
|
-
const template = css`
|
|
92
|
-
color: ${(p: any) => p.color};
|
|
93
|
-
`
|
|
94
|
-
const cls1 = useCSS(template, { color: 'red' })
|
|
95
|
-
const cls2 = useCSS(template, { color: 'red' })
|
|
96
|
-
expect(cls1).toBe(cls2)
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
describe('cache hit path', () => {
|
|
101
|
-
it('reuses cached className with identical resolved CSS', () => {
|
|
102
|
-
const template = css`
|
|
103
|
-
color: ${(p: any) => p.color};
|
|
104
|
-
`
|
|
105
|
-
const cls1 = useCSS(template, { color: 'red' })
|
|
106
|
-
const cls2 = useCSS(template, { color: 'red' })
|
|
107
|
-
expect(cls1).toBe(cls2)
|
|
108
|
-
expect(cls1).toMatch(/^pyr-/)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('updates className when resolved CSS changes', () => {
|
|
112
|
-
const template = css`
|
|
113
|
-
color: ${(p: any) => p.color};
|
|
114
|
-
`
|
|
115
|
-
const cls1 = useCSS(template, { color: 'red' })
|
|
116
|
-
const cls2 = useCSS(template, { color: 'blue' })
|
|
117
|
-
expect(cls1).not.toBe(cls2)
|
|
118
|
-
})
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
describe('boost parameter', () => {
|
|
122
|
-
it('does not throw when boost is true', () => {
|
|
123
|
-
const template = css`
|
|
124
|
-
display: flex;
|
|
125
|
-
`
|
|
126
|
-
const result = useCSS(template, undefined, true)
|
|
127
|
-
expect(result).toMatch(/^pyr-[0-9a-z]+$/)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('does not throw when boost is false', () => {
|
|
131
|
-
const template = css`
|
|
132
|
-
display: flex;
|
|
133
|
-
`
|
|
134
|
-
const result = useCSS(template, undefined, false)
|
|
135
|
-
expect(result).toMatch(/^pyr-[0-9a-z]+$/)
|
|
136
|
-
})
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
describe('without theme and without props', () => {
|
|
140
|
-
it('uses empty object when no props and no theme', () => {
|
|
141
|
-
const template = css`
|
|
142
|
-
display: flex;
|
|
143
|
-
`
|
|
144
|
-
const result = useCSS(template)
|
|
145
|
-
expect(result).toMatch(/^pyr-[0-9a-z]+$/)
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
it('handles dynamic template without theme or props', () => {
|
|
149
|
-
const template = css`
|
|
150
|
-
color: ${(p: any) => p.color ?? 'red'};
|
|
151
|
-
`
|
|
152
|
-
const result = useCSS(template, undefined)
|
|
153
|
-
expect(result).toMatch(/^pyr-/)
|
|
154
|
-
})
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
describe('empty CSS from dynamic resolution', () => {
|
|
158
|
-
it('returns empty className when dynamic CSS resolves to empty', () => {
|
|
159
|
-
const template = css`
|
|
160
|
-
${(p: any) => (p.color ? `color: ${p.color};` : '')}
|
|
161
|
-
`
|
|
162
|
-
|
|
163
|
-
// First call: non-empty CSS
|
|
164
|
-
const cls1 = useCSS(template, { color: 'red' })
|
|
165
|
-
expect(cls1).toMatch(/^pyr-/)
|
|
166
|
-
|
|
167
|
-
// Second call: empty CSS
|
|
168
|
-
const cls2 = useCSS(template, { color: undefined })
|
|
169
|
-
expect(cls2).toBe('')
|
|
170
|
-
})
|
|
171
|
-
})
|
|
172
|
-
})
|
package/src/css.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { CSSResult, type Interpolation } from './resolve'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Tagged template function for CSS. Captures the template strings and
|
|
5
|
-
* interpolation values as a lazy CSSResult — resolution is deferred
|
|
6
|
-
* until a styled component renders.
|
|
7
|
-
*
|
|
8
|
-
* Works as both a tagged template (`css\`...\``) and a regular function
|
|
9
|
-
* call (`css(...args)`) since tagged templates are syntactic sugar for
|
|
10
|
-
* function calls with (TemplateStringsArray, ...values).
|
|
11
|
-
*/
|
|
12
|
-
export const css = (strings: TemplateStringsArray, ...values: Interpolation[]): CSSResult =>
|
|
13
|
-
new CSSResult(strings, values)
|
package/src/env.d.ts
DELETED
package/src/forward.ts
DELETED
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTML prop filtering. Prevents unknown props from being forwarded to DOM
|
|
3
|
-
* elements (which causes warnings). Props starting with `$` are
|
|
4
|
-
* transient (styling-only) and are always filtered out.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// Common HTML attributes, event handlers, and ARIA/data attributes.
|
|
8
|
-
//
|
|
9
|
-
// Using a plain object with `key in HTML_PROPS` instead of `Set.has(key)`:
|
|
10
|
-
// V8 inlines `in` checks via hidden-class lookups (the object has a fixed
|
|
11
|
-
// shape at module load and never changes), which is meaningfully faster
|
|
12
|
-
// than going through the Set protocol on hot prop-filter paths. Measured
|
|
13
|
-
// upstream (vitus-labs `be471b19`): +19% on the 5-lookup mix benchmark
|
|
14
|
-
// (4 hits + 1 miss).
|
|
15
|
-
const HTML_PROPS_LIST = [
|
|
16
|
-
// Core props
|
|
17
|
-
'className',
|
|
18
|
-
'class',
|
|
19
|
-
'dangerouslySetInnerHTML',
|
|
20
|
-
'htmlFor',
|
|
21
|
-
'id',
|
|
22
|
-
'key',
|
|
23
|
-
'ref',
|
|
24
|
-
'style',
|
|
25
|
-
'tabIndex',
|
|
26
|
-
'role',
|
|
27
|
-
// Event handlers
|
|
28
|
-
'onAbort',
|
|
29
|
-
'onAnimationEnd',
|
|
30
|
-
'onAnimationIteration',
|
|
31
|
-
'onAnimationStart',
|
|
32
|
-
'onBlur',
|
|
33
|
-
'onChange',
|
|
34
|
-
'onClick',
|
|
35
|
-
'onCompositionEnd',
|
|
36
|
-
'onCompositionStart',
|
|
37
|
-
'onCompositionUpdate',
|
|
38
|
-
'onContextMenu',
|
|
39
|
-
'onCopy',
|
|
40
|
-
'onCut',
|
|
41
|
-
'onDoubleClick',
|
|
42
|
-
'onDrag',
|
|
43
|
-
'onDragEnd',
|
|
44
|
-
'onDragEnter',
|
|
45
|
-
'onDragLeave',
|
|
46
|
-
'onDragOver',
|
|
47
|
-
'onDragStart',
|
|
48
|
-
'onDrop',
|
|
49
|
-
'onError',
|
|
50
|
-
'onFocus',
|
|
51
|
-
'onInput',
|
|
52
|
-
'onKeyDown',
|
|
53
|
-
'onKeyPress',
|
|
54
|
-
'onKeyUp',
|
|
55
|
-
'onLoad',
|
|
56
|
-
'onMouseDown',
|
|
57
|
-
'onMouseEnter',
|
|
58
|
-
'onMouseLeave',
|
|
59
|
-
'onMouseMove',
|
|
60
|
-
'onMouseOut',
|
|
61
|
-
'onMouseOver',
|
|
62
|
-
'onMouseUp',
|
|
63
|
-
'onPaste',
|
|
64
|
-
'onPointerCancel',
|
|
65
|
-
'onPointerDown',
|
|
66
|
-
'onPointerEnter',
|
|
67
|
-
'onPointerLeave',
|
|
68
|
-
'onPointerMove',
|
|
69
|
-
'onPointerOut',
|
|
70
|
-
'onPointerOver',
|
|
71
|
-
'onPointerUp',
|
|
72
|
-
'onScroll',
|
|
73
|
-
'onSelect',
|
|
74
|
-
'onSubmit',
|
|
75
|
-
'onTouchCancel',
|
|
76
|
-
'onTouchEnd',
|
|
77
|
-
'onTouchMove',
|
|
78
|
-
'onTouchStart',
|
|
79
|
-
'onTransitionEnd',
|
|
80
|
-
'onWheel',
|
|
81
|
-
// HTML attributes
|
|
82
|
-
'accept',
|
|
83
|
-
'acceptCharset',
|
|
84
|
-
'accessKey',
|
|
85
|
-
'action',
|
|
86
|
-
'allow',
|
|
87
|
-
'allowFullScreen',
|
|
88
|
-
'alt',
|
|
89
|
-
'as',
|
|
90
|
-
'async',
|
|
91
|
-
'autoCapitalize',
|
|
92
|
-
'autoComplete',
|
|
93
|
-
'autoCorrect',
|
|
94
|
-
'autoFocus',
|
|
95
|
-
'autoPlay',
|
|
96
|
-
'capture',
|
|
97
|
-
'cellPadding',
|
|
98
|
-
'cellSpacing',
|
|
99
|
-
'charSet',
|
|
100
|
-
'checked',
|
|
101
|
-
'cite',
|
|
102
|
-
'cols',
|
|
103
|
-
'colSpan',
|
|
104
|
-
'content',
|
|
105
|
-
'contentEditable',
|
|
106
|
-
'controls',
|
|
107
|
-
'controlsList',
|
|
108
|
-
'coords',
|
|
109
|
-
'crossOrigin',
|
|
110
|
-
'dateTime',
|
|
111
|
-
'decoding',
|
|
112
|
-
'default',
|
|
113
|
-
'defaultChecked',
|
|
114
|
-
'defaultValue',
|
|
115
|
-
'defer',
|
|
116
|
-
'dir',
|
|
117
|
-
'disabled',
|
|
118
|
-
'disablePictureInPicture',
|
|
119
|
-
'disableRemotePlayback',
|
|
120
|
-
'download',
|
|
121
|
-
'draggable',
|
|
122
|
-
'encType',
|
|
123
|
-
'enterKeyHint',
|
|
124
|
-
'fetchPriority',
|
|
125
|
-
'form',
|
|
126
|
-
'formAction',
|
|
127
|
-
'formEncType',
|
|
128
|
-
'formMethod',
|
|
129
|
-
'formNoValidate',
|
|
130
|
-
'formTarget',
|
|
131
|
-
'frameBorder',
|
|
132
|
-
'headers',
|
|
133
|
-
'height',
|
|
134
|
-
'hidden',
|
|
135
|
-
'high',
|
|
136
|
-
'href',
|
|
137
|
-
'hrefLang',
|
|
138
|
-
'httpEquiv',
|
|
139
|
-
'inputMode',
|
|
140
|
-
'integrity',
|
|
141
|
-
'is',
|
|
142
|
-
'label',
|
|
143
|
-
'lang',
|
|
144
|
-
'list',
|
|
145
|
-
'loading',
|
|
146
|
-
'loop',
|
|
147
|
-
'low',
|
|
148
|
-
'max',
|
|
149
|
-
'maxLength',
|
|
150
|
-
'media',
|
|
151
|
-
'method',
|
|
152
|
-
'min',
|
|
153
|
-
'minLength',
|
|
154
|
-
'multiple',
|
|
155
|
-
'muted',
|
|
156
|
-
'name',
|
|
157
|
-
'noModule',
|
|
158
|
-
'noValidate',
|
|
159
|
-
'nonce',
|
|
160
|
-
'open',
|
|
161
|
-
'optimum',
|
|
162
|
-
'pattern',
|
|
163
|
-
'placeholder',
|
|
164
|
-
'playsInline',
|
|
165
|
-
'poster',
|
|
166
|
-
'preload',
|
|
167
|
-
'readOnly',
|
|
168
|
-
'referrerPolicy',
|
|
169
|
-
'rel',
|
|
170
|
-
'required',
|
|
171
|
-
'reversed',
|
|
172
|
-
'rows',
|
|
173
|
-
'rowSpan',
|
|
174
|
-
'sandbox',
|
|
175
|
-
'scope',
|
|
176
|
-
'scoped',
|
|
177
|
-
'scrolling',
|
|
178
|
-
'selected',
|
|
179
|
-
'shape',
|
|
180
|
-
'size',
|
|
181
|
-
'sizes',
|
|
182
|
-
'slot',
|
|
183
|
-
'span',
|
|
184
|
-
'spellCheck',
|
|
185
|
-
'src',
|
|
186
|
-
'srcDoc',
|
|
187
|
-
'srcLang',
|
|
188
|
-
'srcSet',
|
|
189
|
-
'start',
|
|
190
|
-
'step',
|
|
191
|
-
'summary',
|
|
192
|
-
'target',
|
|
193
|
-
'title',
|
|
194
|
-
'translate',
|
|
195
|
-
'type',
|
|
196
|
-
'useMap',
|
|
197
|
-
'value',
|
|
198
|
-
'width',
|
|
199
|
-
'wrap',
|
|
200
|
-
] as const
|
|
201
|
-
|
|
202
|
-
// Build the lookup object once at module load. `null`-prototype keeps the
|
|
203
|
-
// object's hidden class lean and means `in` checks don't accidentally pick
|
|
204
|
-
// up `Object.prototype` keys.
|
|
205
|
-
const HTML_PROPS: Record<string, true> = Object.create(null)
|
|
206
|
-
for (const k of HTML_PROPS_LIST) HTML_PROPS[k] = true
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Filters props for HTML elements. Keeps valid HTML attrs, data-*, aria-*.
|
|
210
|
-
* Rejects unknown props and $-prefixed transient props.
|
|
211
|
-
*/
|
|
212
|
-
export const filterProps = (props: Record<string, unknown>): Record<string, unknown> => {
|
|
213
|
-
const filtered: Record<string, unknown> = {}
|
|
214
|
-
|
|
215
|
-
for (const key in props) {
|
|
216
|
-
// Skip transient props ($-prefixed) — used for styling-only props
|
|
217
|
-
if (key.charCodeAt(0) === 36) continue // '$'
|
|
218
|
-
|
|
219
|
-
// Skip `as` prop — handled separately by styled
|
|
220
|
-
if (key === 'as') continue
|
|
221
|
-
|
|
222
|
-
// Keep data-* and aria-* attributes
|
|
223
|
-
if (key.startsWith('data-') || key.startsWith('aria-')) {
|
|
224
|
-
filtered[key] = props[key]
|
|
225
|
-
continue
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Keep known HTML props — `in` against the null-prototype lookup
|
|
229
|
-
// object beats `Set.has` on the hot DOM-filter path.
|
|
230
|
-
if (key in HTML_PROPS) {
|
|
231
|
-
filtered[key] = props[key]
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return filtered
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Build final props for a styled component in a single pass.
|
|
240
|
-
* Combines className merging, ref injection, and prop filtering into one
|
|
241
|
-
* allocation and one iteration.
|
|
242
|
-
*
|
|
243
|
-
* Copies own property DESCRIPTORS rather than values for forwarded
|
|
244
|
-
* props — getter-shaped reactive props (compiler-emitted `_rp(() =>
|
|
245
|
-
* signal())` converted to getters by `makeReactiveProps`) survive the
|
|
246
|
-
* copy with their reactive subscription intact. A bare `result[key] =
|
|
247
|
-
* rawProps[key]` fires the getter at setup time and stores a static
|
|
248
|
-
* value, breaking signal-driven reactivity for any consumer that reads
|
|
249
|
-
* `props.x` in a reactive scope downstream.
|
|
250
|
-
*/
|
|
251
|
-
export const buildProps = (
|
|
252
|
-
rawProps: Record<string, any>,
|
|
253
|
-
generatedCls: string,
|
|
254
|
-
isDOM: boolean,
|
|
255
|
-
customFilter?: (prop: string) => boolean,
|
|
256
|
-
): Record<string, any> => {
|
|
257
|
-
const result: Record<string, any> = {}
|
|
258
|
-
|
|
259
|
-
// Merge generated + user className. Reading `rawProps.class` /
|
|
260
|
-
// `.className` synchronously is fine — `class` is consumed at this
|
|
261
|
-
// boundary (merged with the generated class), never forwarded
|
|
262
|
-
// reactively. The string we write is consumed by the DOM at apply
|
|
263
|
-
// time, not stored as a getter.
|
|
264
|
-
const userCls = rawProps.class || rawProps.className
|
|
265
|
-
if (generatedCls) {
|
|
266
|
-
result.class = userCls ? `${generatedCls} ${userCls}` : generatedCls
|
|
267
|
-
} else if (userCls) {
|
|
268
|
-
result.class = userCls
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Helper: copy a prop's OWN descriptor (preserves getters) into result.
|
|
272
|
-
// Falls back to a no-op if the source has no own descriptor for the key.
|
|
273
|
-
const copyDescriptor = (key: string): void => {
|
|
274
|
-
const d = Object.getOwnPropertyDescriptor(rawProps, key)
|
|
275
|
-
if (d) Object.defineProperty(result, key, d)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Component target — forward all props except as/className/class and $-prefixed
|
|
279
|
-
if (!isDOM) {
|
|
280
|
-
for (const key in rawProps) {
|
|
281
|
-
if (key === 'as' || key === 'className' || key === 'class') continue
|
|
282
|
-
if (key.charCodeAt(0) === 36) continue // $-prefixed transient
|
|
283
|
-
copyDescriptor(key)
|
|
284
|
-
}
|
|
285
|
-
return result
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// DOM element with custom shouldForwardProp
|
|
289
|
-
if (customFilter) {
|
|
290
|
-
for (const key in rawProps) {
|
|
291
|
-
if (key === 'as' || key === 'className' || key === 'class') continue
|
|
292
|
-
if (customFilter(key)) copyDescriptor(key)
|
|
293
|
-
}
|
|
294
|
-
return result
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// DOM element with default filtering
|
|
298
|
-
for (const key in rawProps) {
|
|
299
|
-
if (key === 'as' || key === 'className' || key === 'class') continue
|
|
300
|
-
if (key.charCodeAt(0) === 36) continue // $-prefixed transient
|
|
301
|
-
if (key.startsWith('data-') || key.startsWith('aria-')) {
|
|
302
|
-
copyDescriptor(key)
|
|
303
|
-
continue
|
|
304
|
-
}
|
|
305
|
-
if (key in HTML_PROPS) copyDescriptor(key)
|
|
306
|
-
}
|
|
307
|
-
return result
|
|
308
|
-
}
|
package/src/globalStyle.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* createGlobalStyle() — tagged template function that injects global CSS
|
|
3
|
-
* rules (not scoped to a class name). Returns a component function that
|
|
4
|
-
* injects styles when called and supports dynamic interpolations via
|
|
5
|
-
* props/theme.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* const GlobalStyle = createGlobalStyle`
|
|
9
|
-
* body { margin: 0; font-family: ${({ theme }) => theme.font}; }
|
|
10
|
-
* *, *::before, *::after { box-sizing: border-box; }
|
|
11
|
-
* `
|
|
12
|
-
*/
|
|
13
|
-
import type { ComponentFn } from '@pyreon/core'
|
|
14
|
-
import { type Interpolation, normalizeCSS, resolve } from './resolve'
|
|
15
|
-
import { isDynamic } from './shared'
|
|
16
|
-
import { sheet } from './sheet'
|
|
17
|
-
import { useTheme } from './ThemeProvider'
|
|
18
|
-
|
|
19
|
-
export const createGlobalStyle = (
|
|
20
|
-
strings: TemplateStringsArray,
|
|
21
|
-
...values: Interpolation[]
|
|
22
|
-
): ComponentFn => {
|
|
23
|
-
const hasDynamicValues = values.some(isDynamic)
|
|
24
|
-
|
|
25
|
-
// STATIC FAST PATH: compute once at creation time
|
|
26
|
-
if (!hasDynamicValues) {
|
|
27
|
-
const cssText = normalizeCSS(resolve(strings, values, {}))
|
|
28
|
-
|
|
29
|
-
// Inject into sheet immediately. `normalizeCSS` already strips
|
|
30
|
-
// leading/trailing whitespace, so a length check is equivalent to the
|
|
31
|
-
// prior `.trim()` (no O(n) whitespace scan, no string allocation).
|
|
32
|
-
// Ported from vitus-labs `be471b19`.
|
|
33
|
-
if (cssText.length > 0) sheet.insertGlobal(cssText)
|
|
34
|
-
|
|
35
|
-
const StaticGlobal: ComponentFn = () => null
|
|
36
|
-
return StaticGlobal
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// DYNAMIC PATH: resolve on every render with theme/props
|
|
40
|
-
const DynamicGlobal: ComponentFn = (props: Record<string, any>) => {
|
|
41
|
-
const theme = useTheme()
|
|
42
|
-
const allProps = { ...props, theme }
|
|
43
|
-
const cssText = normalizeCSS(resolve(strings, values, allProps))
|
|
44
|
-
|
|
45
|
-
// Length check — `normalizeCSS` already trims. Ported from
|
|
46
|
-
// vitus-labs `be471b19`.
|
|
47
|
-
if (cssText.length > 0) sheet.insertGlobal(cssText)
|
|
48
|
-
|
|
49
|
-
return null
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return DynamicGlobal
|
|
53
|
-
}
|
package/src/hash.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fast FNV-1a non-cryptographic hash. Returns base-36 string for compact class names.
|
|
3
|
-
*
|
|
4
|
-
* 32-bit hash space → ~4.3 billion unique values. Collision probability is
|
|
5
|
-
* negligible for typical applications (< 10,000 unique CSS rules).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/** FNV-1a offset basis — starting state for streaming hash. */
|
|
9
|
-
export const HASH_INIT = 2166136261
|
|
10
|
-
|
|
11
|
-
const FNV_PRIME = 16777619
|
|
12
|
-
|
|
13
|
-
/** Feed a string segment into the running hash state.
|
|
14
|
-
* Streaming: hashUpdate(hashUpdate(HASH_INIT, 'ab'), 'cd') === hash('abcd').
|
|
15
|
-
*/
|
|
16
|
-
export const hashUpdate = (init: number, str: string): number => {
|
|
17
|
-
let h = init
|
|
18
|
-
for (let i = 0; i < str.length; i++) {
|
|
19
|
-
h = Math.imul(h ^ str.charCodeAt(i), FNV_PRIME)
|
|
20
|
-
}
|
|
21
|
-
return h
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Finalize a hash state into a base-36 class name suffix. */
|
|
25
|
-
export const hashFinalize = (h: number): string => (h >>> 0).toString(36)
|
|
26
|
-
|
|
27
|
-
/** Hash a complete string in one shot. Returns base-36 string. */
|
|
28
|
-
export const hash = (str: string): string => hashFinalize(hashUpdate(HASH_INIT, str))
|
package/src/index.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export { css } from './css'
|
|
2
|
-
export { buildProps, filterProps } from './forward'
|
|
3
|
-
export { createGlobalStyle } from './globalStyle'
|
|
4
|
-
export { HASH_INIT, hash, hashFinalize, hashUpdate } from './hash'
|
|
5
|
-
export { keyframes } from './keyframes'
|
|
6
|
-
export type { CSSResult, Interpolation } from './resolve'
|
|
7
|
-
export { clearNormCache, normalizeCSS, resolve, resolveValue } from './resolve'
|
|
8
|
-
export { isDynamic } from './shared'
|
|
9
|
-
export type { StyleSheetOptions } from './sheet'
|
|
10
|
-
export { createSheet, StyleSheet, sheet } from './sheet'
|
|
11
|
-
export type { StyledFunction, StyledOptions } from './styled'
|
|
12
|
-
export { styled } from './styled'
|
|
13
|
-
export type { DefaultTheme } from './ThemeProvider'
|
|
14
|
-
export { ThemeContext, ThemeProvider, useTheme, useThemeAccessor } from './ThemeProvider'
|
|
15
|
-
export { useCSS } from './useCSS'
|
package/src/keyframes.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* keyframes() tagged template function. Creates a CSS @keyframes rule,
|
|
3
|
-
* injects it into the stylesheet, and returns the generated animation name.
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* const fadeIn = keyframes`
|
|
7
|
-
* from { opacity: 0; }
|
|
8
|
-
* to { opacity: 1; }
|
|
9
|
-
* `
|
|
10
|
-
* // fadeIn === "pyr-kf-abc123" (deterministic, hash-based)
|
|
11
|
-
*/
|
|
12
|
-
import { hash } from './hash'
|
|
13
|
-
import { type Interpolation, normalizeCSS, resolve } from './resolve'
|
|
14
|
-
import { sheet } from './sheet'
|
|
15
|
-
|
|
16
|
-
class KeyframesResult {
|
|
17
|
-
readonly name: string
|
|
18
|
-
|
|
19
|
-
constructor(strings: TemplateStringsArray, values: Interpolation[]) {
|
|
20
|
-
const body = normalizeCSS(resolve(strings, values, {}))
|
|
21
|
-
const h = hash(body)
|
|
22
|
-
this.name = `pyr-kf-${h}`
|
|
23
|
-
|
|
24
|
-
sheet.insertKeyframes(this.name, body)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Returns the animation name when used in string context. */
|
|
28
|
-
toString(): string {
|
|
29
|
-
return this.name
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const keyframes = (
|
|
34
|
-
strings: TemplateStringsArray,
|
|
35
|
-
...values: Interpolation[]
|
|
36
|
-
): KeyframesResult => new KeyframesResult(strings, values)
|