@pyreon/rocketstyle 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 +351 -0
- package/lib/index.d.ts +294 -0
- package/lib/index.js +657 -0
- package/package.json +47 -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,351 @@
|
|
|
1
|
+
# @pyreon/rocketstyle
|
|
2
|
+
|
|
3
|
+
Multi-dimensional styling system for Pyreon.
|
|
4
|
+
|
|
5
|
+
Organize component styles by dimensions — states, sizes, variants — instead of flat props. Chain theme values, attach CSS via `@pyreon/styler`, and get full TypeScript inference. Built-in pseudo-state handling, light/dark mode, and provider/consumer patterns for parent-child state propagation.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Dimension-based theming** — define style variations as named dimensions (states, sizes, variants)
|
|
10
|
+
- **Immutable chaining** — `.attrs()`, `.theme()`, `.states()`, `.sizes()`, `.styles()` and more
|
|
11
|
+
- **Boolean shorthand** — `Button({ primary: true, lg: true })` instead of `Button({ state: 'primary', size: 'lg' })`
|
|
12
|
+
- **Pseudo-state detection** — hover, focus, pressed tracked via signals and context
|
|
13
|
+
- **Light/dark mode** — theme callbacks receive a mode parameter
|
|
14
|
+
- **Provider/Consumer** — propagate parent state to children through context
|
|
15
|
+
- **WeakMap caching** — computed themes cached per component instance
|
|
16
|
+
- **TypeScript inference** — dimension values and prop types inferred through the chain
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bun add @pyreon/rocketstyle
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import rocketstyle from '@pyreon/rocketstyle'
|
|
28
|
+
import { Element } from '@pyreon/elements'
|
|
29
|
+
|
|
30
|
+
const Button = rocketstyle()({
|
|
31
|
+
name: 'Button',
|
|
32
|
+
component: Element,
|
|
33
|
+
})
|
|
34
|
+
.attrs({ tag: 'button' })
|
|
35
|
+
.theme({
|
|
36
|
+
fontSize: 16,
|
|
37
|
+
paddingX: 16,
|
|
38
|
+
paddingY: 8,
|
|
39
|
+
borderRadius: 4,
|
|
40
|
+
color: '#fff',
|
|
41
|
+
backgroundColor: '#0d6efd',
|
|
42
|
+
hover: {
|
|
43
|
+
backgroundColor: '#0b5ed7',
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
.states({
|
|
47
|
+
primary: {
|
|
48
|
+
backgroundColor: '#0d6efd',
|
|
49
|
+
hover: { backgroundColor: '#0b5ed7' },
|
|
50
|
+
},
|
|
51
|
+
danger: {
|
|
52
|
+
backgroundColor: '#dc3545',
|
|
53
|
+
hover: { backgroundColor: '#bb2d3b' },
|
|
54
|
+
},
|
|
55
|
+
success: {
|
|
56
|
+
backgroundColor: '#198754',
|
|
57
|
+
hover: { backgroundColor: '#157347' },
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
.sizes({
|
|
61
|
+
sm: { fontSize: 14, paddingX: 12, paddingY: 6 },
|
|
62
|
+
md: { fontSize: 16, paddingX: 16, paddingY: 8 },
|
|
63
|
+
lg: { fontSize: 18, paddingX: 20, paddingY: 10 },
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// Named props
|
|
69
|
+
Button({ state: 'danger', size: 'lg', label: 'Delete' })
|
|
70
|
+
|
|
71
|
+
// Boolean shorthand (when useBooleans is enabled)
|
|
72
|
+
Button({ danger: true, lg: true, label: 'Delete' })
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Core Concepts
|
|
76
|
+
|
|
77
|
+
### Dimensions
|
|
78
|
+
|
|
79
|
+
A dimension is a named axis of style variation. The factory ships with four defaults:
|
|
80
|
+
|
|
81
|
+
| Dimension | Prop name | Multi | Example |
|
|
82
|
+
| --------- | --------- | ----- | ------- |
|
|
83
|
+
| `states` | `state` | no | `primary`, `danger`, `success` |
|
|
84
|
+
| `sizes` | `size` | no | `sm`, `md`, `lg` |
|
|
85
|
+
| `variants` | `variant` | no | `outlined`, `filled` |
|
|
86
|
+
| `multiple` | — | yes | `rounded`, `shadow` |
|
|
87
|
+
|
|
88
|
+
Each dimension creates a chain method (`.states()`, `.sizes()`, etc.) and a corresponding prop on the component.
|
|
89
|
+
|
|
90
|
+
**Multi dimensions** allow multiple values at once: `Button({ rounded: true, shadow: true })`.
|
|
91
|
+
|
|
92
|
+
### Theme Object
|
|
93
|
+
|
|
94
|
+
The `.theme()` method defines base CSS property values. Values are processed by `@pyreon/unistyle` — numbers convert to rem, shorthand properties expand automatically.
|
|
95
|
+
|
|
96
|
+
Pseudo-state keys nest directly in the theme object:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
.theme({
|
|
100
|
+
color: '#333',
|
|
101
|
+
fontSize: 16,
|
|
102
|
+
hover: { color: '#000' },
|
|
103
|
+
focus: { outline: '2px solid blue' },
|
|
104
|
+
active: { transform: 'scale(0.98)' },
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Styles Function
|
|
109
|
+
|
|
110
|
+
The `.styles()` method defines the CSS template that receives the computed theme:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
.styles((css) => css`
|
|
114
|
+
${({ $rocketstyle, $rocketstate }) => {
|
|
115
|
+
// $rocketstyle — computed theme values (base + active dimension values merged)
|
|
116
|
+
// $rocketstate — { hover, focus, pressed, active, disabled, pseudo }
|
|
117
|
+
return css`...`
|
|
118
|
+
}}
|
|
119
|
+
`)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## API
|
|
123
|
+
|
|
124
|
+
### rocketstyle(options?)
|
|
125
|
+
|
|
126
|
+
Factory initializer. Returns a function that accepts component configuration.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const factory = rocketstyle({
|
|
130
|
+
dimensions: { /* custom dimensions */ },
|
|
131
|
+
useBooleans: true,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const Component = factory({
|
|
135
|
+
name: 'ComponentName',
|
|
136
|
+
component: BaseComponent,
|
|
137
|
+
})
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### .attrs(props | callback, options?)
|
|
141
|
+
|
|
142
|
+
Same API as `@pyreon/attrs`. Define default props with optional priority and filter.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
Button.attrs({ tag: 'button', role: 'button' })
|
|
146
|
+
Button.attrs((props) => ({ 'aria-label': props.label }))
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### .theme(values | callback)
|
|
150
|
+
|
|
151
|
+
Base theme values applied to every instance.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
// Object form
|
|
155
|
+
Button.theme({
|
|
156
|
+
fontSize: 16,
|
|
157
|
+
color: '#fff',
|
|
158
|
+
hover: { opacity: 0.9 },
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// Callback form — receives the theme context and mode
|
|
162
|
+
Button.theme((theme, mode, css) => ({
|
|
163
|
+
fontSize: 16,
|
|
164
|
+
color: mode === 'dark' ? '#fff' : '#333',
|
|
165
|
+
}))
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### .states() / .sizes() / .variants() / .multiple()
|
|
169
|
+
|
|
170
|
+
Define values for each dimension. Each key becomes a selectable option.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
Button.states({
|
|
174
|
+
primary: { backgroundColor: '#0d6efd' },
|
|
175
|
+
danger: { backgroundColor: '#dc3545' },
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
Button.sizes({
|
|
179
|
+
sm: { fontSize: 14, paddingX: 8 },
|
|
180
|
+
lg: { fontSize: 18, paddingX: 20 },
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
// Multi dimension — multiple can be active at once
|
|
184
|
+
Button.multiple({
|
|
185
|
+
rounded: { borderRadius: 999 },
|
|
186
|
+
shadow: { boxShadow: '0 2px 8px rgba(0,0,0,0.15)' },
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Dimension methods also accept callbacks:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
Button.states((theme, mode, css) => ({
|
|
194
|
+
primary: { backgroundColor: theme.colors?.primary ?? '#0d6efd' },
|
|
195
|
+
}))
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### .styles(callback)
|
|
199
|
+
|
|
200
|
+
Define the CSS template using `@pyreon/styler`'s `css` tagged template.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
Button.styles((css) => css`
|
|
204
|
+
cursor: pointer;
|
|
205
|
+
border: none;
|
|
206
|
+
transition: all 0.2s;
|
|
207
|
+
|
|
208
|
+
${({ $rocketstyle }) =>
|
|
209
|
+
makeItResponsive({ theme: $rocketstyle, styles, css })
|
|
210
|
+
}
|
|
211
|
+
`)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### .config(options)
|
|
215
|
+
|
|
216
|
+
Reconfigure the component.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
Button.config({
|
|
220
|
+
name: 'PrimaryButton', // change displayName
|
|
221
|
+
component: NewBase, // swap base component
|
|
222
|
+
provider: true, // make this component a context provider
|
|
223
|
+
consumer: (ctx) => ..., // consume parent component context
|
|
224
|
+
inversed: true, // invert theme mode
|
|
225
|
+
DEBUG: true, // enable debug logging
|
|
226
|
+
})
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### .compose(hocs) / .statics(metadata)
|
|
230
|
+
|
|
231
|
+
Same API as `@pyreon/attrs`:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
Button.compose({ withTracking: trackingHoc })
|
|
235
|
+
Button.statics({ category: 'action' })
|
|
236
|
+
|
|
237
|
+
Button.meta.category // => 'action'
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### isRocketComponent(value)
|
|
241
|
+
|
|
242
|
+
Runtime type guard.
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import { isRocketComponent } from '@pyreon/rocketstyle'
|
|
246
|
+
|
|
247
|
+
isRocketComponent(Button) // => true
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Custom Dimensions
|
|
251
|
+
|
|
252
|
+
Define your own dimensions by passing them to the factory:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
const rocketButton = rocketstyle({
|
|
256
|
+
dimensions: {
|
|
257
|
+
intent: 'intent', // prop: intent="primary"
|
|
258
|
+
size: 'size', // prop: size="lg"
|
|
259
|
+
appearance: {
|
|
260
|
+
propName: 'appearance',
|
|
261
|
+
multi: true, // allows multiple values
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
})
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
This creates `.intent()`, `.size()`, and `.appearance()` chain methods.
|
|
268
|
+
|
|
269
|
+
## Transform Dimensions
|
|
270
|
+
|
|
271
|
+
Mark a dimension as `transform: true` to make its values receive the accumulated theme from all prior dimensions. This is ideal for modifiers like `outlined` that derive styles from the active state:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
const rocketButton = rocketstyle({
|
|
275
|
+
dimensions: {
|
|
276
|
+
states: 'state',
|
|
277
|
+
modifiers: { propName: 'modifier', multi: true, transform: true },
|
|
278
|
+
},
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
const Button = rocketButton({ name: 'Button', component: Element })
|
|
282
|
+
.theme({ backgroundColor: '#0d6efd', color: '#fff' })
|
|
283
|
+
.states({
|
|
284
|
+
danger: { backgroundColor: '#dc3545', color: '#fff' },
|
|
285
|
+
})
|
|
286
|
+
.modifiers({
|
|
287
|
+
outlined: (theme) => ({
|
|
288
|
+
color: theme.backgroundColor,
|
|
289
|
+
backgroundColor: 'transparent',
|
|
290
|
+
}),
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
// outlined receives { backgroundColor: '#dc3545', color: '#fff' } from the danger state
|
|
294
|
+
Button({ state: 'danger', modifier: 'outlined' })
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Provider / Consumer
|
|
298
|
+
|
|
299
|
+
Propagate parent component state to children through Pyreon's context system.
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
// Parent provides its state
|
|
303
|
+
const ButtonGroup = Button.config({ provider: true })
|
|
304
|
+
|
|
305
|
+
// Child consumes parent state
|
|
306
|
+
const ButtonIcon = rocketstyle()({
|
|
307
|
+
name: 'ButtonIcon',
|
|
308
|
+
component: Element,
|
|
309
|
+
})
|
|
310
|
+
.config({
|
|
311
|
+
consumer: (ctx) => ctx(({ pseudo }) => ({
|
|
312
|
+
state: pseudo.hover ? 'active' : 'default',
|
|
313
|
+
})),
|
|
314
|
+
})
|
|
315
|
+
.states({
|
|
316
|
+
default: { color: '#666' },
|
|
317
|
+
active: { color: '#fff' },
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
// Icon reacts to parent's hover state
|
|
321
|
+
ButtonGroup({ state: 'primary', children: [
|
|
322
|
+
ButtonIcon({}),
|
|
323
|
+
'Label',
|
|
324
|
+
]})
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Light / Dark Mode
|
|
328
|
+
|
|
329
|
+
Theme callbacks receive a `mode` parameter:
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
Button.theme((theme, mode) => ({
|
|
333
|
+
color: mode === 'dark' ? '#fff' : '#1a1a1a',
|
|
334
|
+
backgroundColor: mode === 'dark' ? '#333' : '#fff',
|
|
335
|
+
}))
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Use `inversed: true` in `.config()` to flip the mode for a component subtree.
|
|
339
|
+
|
|
340
|
+
## Peer Dependencies
|
|
341
|
+
|
|
342
|
+
| Package | Version |
|
|
343
|
+
| ------- | ------- |
|
|
344
|
+
| @pyreon/core | * |
|
|
345
|
+
| @pyreon/reactivity | * |
|
|
346
|
+
| @pyreon/ui-core | * |
|
|
347
|
+
| @pyreon/styler | * |
|
|
348
|
+
|
|
349
|
+
## License
|
|
350
|
+
|
|
351
|
+
MIT
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { VNode, VNodeChild } from "@pyreon/core";
|
|
2
|
+
import { config, context, render } from "@pyreon/ui-core";
|
|
3
|
+
|
|
4
|
+
//#region src/context/context.d.ts
|
|
5
|
+
type Theme$1 = {
|
|
6
|
+
rootSize: number;
|
|
7
|
+
breakpoints?: Record<string, number>;
|
|
8
|
+
} & Record<string, unknown>;
|
|
9
|
+
type TProvider = {
|
|
10
|
+
children: VNode | VNode[] | null;
|
|
11
|
+
theme?: Theme$1 | undefined;
|
|
12
|
+
mode?: "light" | "dark" | undefined;
|
|
13
|
+
inversed?: boolean | undefined;
|
|
14
|
+
provider?: ((props: Record<string, unknown>) => VNodeChild) | undefined;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Top-level theme and mode provider for rocketstyle components.
|
|
18
|
+
* Reads the parent context, merges incoming props, and resolves
|
|
19
|
+
* the active mode (with optional inversion for nested dark/light switching).
|
|
20
|
+
*
|
|
21
|
+
* In Pyreon, context is provided via pushContext/popContext instead of React.Provider.
|
|
22
|
+
*/
|
|
23
|
+
declare const Provider: ({
|
|
24
|
+
provider,
|
|
25
|
+
inversed,
|
|
26
|
+
...props
|
|
27
|
+
}: TProvider) => VNode | null;
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/constants/defaultDimensions.d.ts
|
|
30
|
+
/**
|
|
31
|
+
* Default dimension configuration for rocketstyle components.
|
|
32
|
+
* Defines four built-in dimensions: `states`, `sizes`, `variants`,
|
|
33
|
+
* and `multiple` (a multi-select dimension).
|
|
34
|
+
*/
|
|
35
|
+
declare const DEFAULT_DIMENSIONS: {
|
|
36
|
+
readonly states: "state";
|
|
37
|
+
readonly sizes: "size";
|
|
38
|
+
readonly variants: "variant";
|
|
39
|
+
readonly multiple: {
|
|
40
|
+
readonly propName: "multiple";
|
|
41
|
+
readonly multi: true;
|
|
42
|
+
};
|
|
43
|
+
readonly modifiers: {
|
|
44
|
+
readonly propName: "modifier";
|
|
45
|
+
readonly multi: true;
|
|
46
|
+
readonly transform: true;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
type DefaultDimensions = typeof DEFAULT_DIMENSIONS;
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/types/pseudo.d.ts
|
|
52
|
+
type PseudoActions = {
|
|
53
|
+
onMouseEnter: (e: MouseEvent) => void;
|
|
54
|
+
onMouseLeave: (e: MouseEvent) => void;
|
|
55
|
+
onMouseDown: (e: MouseEvent) => void;
|
|
56
|
+
onMouseUp: (e: MouseEvent) => void;
|
|
57
|
+
onFocus: (e: FocusEvent) => void;
|
|
58
|
+
onBlur: (e: FocusEvent) => void;
|
|
59
|
+
};
|
|
60
|
+
type PseudoState = {
|
|
61
|
+
active: boolean;
|
|
62
|
+
hover: boolean;
|
|
63
|
+
focus: boolean;
|
|
64
|
+
pressed: boolean;
|
|
65
|
+
disabled: boolean;
|
|
66
|
+
readOnly: boolean;
|
|
67
|
+
};
|
|
68
|
+
type PseudoProps = Partial<PseudoState & PseudoActions>;
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/types/utils.d.ts
|
|
71
|
+
type TObj = Record<string, unknown>;
|
|
72
|
+
type TFn = (...args: any) => any;
|
|
73
|
+
type CallBackParam = TObj | TFn;
|
|
74
|
+
/** In Pyreon, components are plain functions — no forwardRef needed. */
|
|
75
|
+
type ComponentFn<P = any> = ((props: P) => VNode | null) & Partial<Record<string, any>>;
|
|
76
|
+
type ElementType<T extends TObj | unknown = any> = ComponentFn<T>;
|
|
77
|
+
type ValueOf<T> = T[keyof T];
|
|
78
|
+
type ArrayOfValues<T> = T[keyof T][];
|
|
79
|
+
type ArrayOfKeys<T> = Array<keyof T>;
|
|
80
|
+
type IsFalseOrNullable<T> = T extends null | undefined | false ? never : true;
|
|
81
|
+
type NullableKeys<T> = { [K in keyof T]: IsFalseOrNullable<T[K]> };
|
|
82
|
+
type ReturnCbParam<P extends TFn | TObj> = P extends TFn ? ReturnType<P> : P;
|
|
83
|
+
type Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
|
|
84
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
85
|
+
type ExtractNullableKeys<T> = { [P in keyof T as IsAny<T[P]> extends true ? P : [T[P]] extends [never] ? never : [T[P]] extends [null | undefined] ? never : P]: T[P] };
|
|
86
|
+
type SpreadTwo<L, R> = Id<Pick<L, Exclude<keyof L, keyof R>> & R>;
|
|
87
|
+
type Spread<A extends readonly [...any]> = A extends [infer L, ...infer R] ? SpreadTwo<L, Spread<R>> : unknown;
|
|
88
|
+
type MergeTypes<A extends readonly [...any]> = ExtractNullableKeys<Spread<A>>;
|
|
89
|
+
type ExtractProps<TComponentOrTProps> = TComponentOrTProps extends ComponentFn<infer TProps> ? TProps : TComponentOrTProps;
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/types/styles.d.ts
|
|
92
|
+
interface StylesDefault {}
|
|
93
|
+
type Styles<S = unknown> = StylesDefault;
|
|
94
|
+
type Css = typeof config.css;
|
|
95
|
+
/**
|
|
96
|
+
* Props available inside `.styles()` interpolation functions.
|
|
97
|
+
*
|
|
98
|
+
* - `$rocketstyle` — computed theme (inferred from `.theme()` chain)
|
|
99
|
+
* - `$rocketstate` — active dimension values + pseudo state
|
|
100
|
+
*/
|
|
101
|
+
type RocketStyleInterpolationProps<CSS extends TObj = TObj> = {
|
|
102
|
+
$rocketstyle: CSS;
|
|
103
|
+
$rocketstate: Record<string, string | string[]> & {
|
|
104
|
+
pseudo: Partial<PseudoState>;
|
|
105
|
+
};
|
|
106
|
+
} & Record<string, any>;
|
|
107
|
+
/**
|
|
108
|
+
* A tagged-template css function whose interpolation functions
|
|
109
|
+
* receive typed props including `$rocketstyle` and `$rocketstate`.
|
|
110
|
+
*/
|
|
111
|
+
type RocketCss<CSS extends TObj = TObj> = (strings: TemplateStringsArray, ...values: Array<string | number | boolean | null | undefined | ((props: RocketStyleInterpolationProps<CSS>) => any) | any[]>) => any;
|
|
112
|
+
type StylesCb<CSS extends TObj = TObj> = (css: RocketCss<CSS>) => ReturnType<Css>;
|
|
113
|
+
type StylesCbArray = StylesCb[];
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/constants/index.d.ts
|
|
116
|
+
/** Supported theme mode flags. */
|
|
117
|
+
declare const THEME_MODES: {
|
|
118
|
+
readonly light: true;
|
|
119
|
+
readonly dark: true;
|
|
120
|
+
};
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/types/theme.d.ts
|
|
123
|
+
interface ThemeDefault {}
|
|
124
|
+
type Theme<T> = T extends unknown ? ThemeDefault : MergeTypes<[ThemeDefault, T]>;
|
|
125
|
+
type ThemeModeKeys = keyof typeof THEME_MODES;
|
|
126
|
+
type ThemeModeCallback = <A = any, B = any>(light: A, dark: B) => (mode: "light" | "dark") => A | B;
|
|
127
|
+
type ThemeMode = <A = any, B = any>(light: A, dark: B) => A | B;
|
|
128
|
+
type ThemeCb<CSS, T> = (theme: T, mode: ThemeModeCallback, css: Css) => Partial<CSS>;
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/types/dimensions.d.ts
|
|
131
|
+
type ExtractNullableDimensionKeys<T> = { [P in keyof T as T[P] extends false ? never : P]: T[P] };
|
|
132
|
+
type ExtractDimensionKey<T extends DimensionValue> = T extends DimensionValueObj ? T["propName"] : T;
|
|
133
|
+
type ExtractDimensionMulti<T extends DimensionValue> = T extends DimensionValueObj ? true : false;
|
|
134
|
+
type DimensionValuePrimitive = string;
|
|
135
|
+
type DimensionValueObj = {
|
|
136
|
+
propName: string;
|
|
137
|
+
multi?: boolean; /** When true, this dimension is evaluated last and its values receive the accumulated theme as argument. */
|
|
138
|
+
transform?: boolean;
|
|
139
|
+
};
|
|
140
|
+
type DimensionValue = DimensionValuePrimitive | DimensionValueObj;
|
|
141
|
+
type Dimensions = Record<string, DimensionValue>;
|
|
142
|
+
type MultiKeys<T extends Dimensions = Dimensions> = Partial<Record<ExtractDimensionKey<T[keyof T]>, true>>;
|
|
143
|
+
type DeepPartial<T> = { [K in keyof T]?: T[K] extends ((...args: any[]) => any) ? T[K] : NonNullable<T[K]> extends Record<string, any> ? DeepPartial<NonNullable<T[K]>> | Extract<T[K], null> : T[K] };
|
|
144
|
+
type DimensionResult<CT, T = any> = Record<string, boolean | null | DeepPartial<CT> | ((theme: CT, appTheme: T, mode: ThemeModeCallback, css: Css) => DeepPartial<CT>)>;
|
|
145
|
+
type DimensionObj<CT, T = any> = DimensionResult<CT, T>;
|
|
146
|
+
type DimensionCb<T, CT> = (theme: T, mode: ThemeModeCallback, css: Css) => DimensionResult<CT, T>;
|
|
147
|
+
type DimensionCallbackParam<T, CT> = DimensionObj<CT, T> | DimensionCb<T, CT>;
|
|
148
|
+
type TDKP = Record<ExtractDimensionKey<Dimensions[keyof Dimensions]>, Record<string, boolean | never | Record<string, boolean>> | unknown>;
|
|
149
|
+
type DimensionProps<K extends DimensionValue, D extends Dimensions, P extends CallBackParam, DKP extends TDKP> = { [I in ExtractDimensionKey<D[keyof D]>]: I extends ExtractDimensionKey<K> ? ExtractNullableDimensionKeys<Spread<[DKP[I], NullableKeys<ReturnCbParam<P>>]>> : DKP[I] };
|
|
150
|
+
type DimensionTypesHelper<DKP extends TDKP> = { [I in keyof DKP]: keyof DKP[I] };
|
|
151
|
+
type DimensionObjAttrs<D extends Dimensions, DKP extends TDKP> = { [I in keyof DKP]: ExtractDimensionMulti<D[I]> extends true ? Array<keyof DKP[I]> : keyof DKP[I] };
|
|
152
|
+
type DimensionBooleanAttrs<DKP extends TDKP> = Partial<Record<ValueOf<DimensionTypesHelper<DKP>>, boolean>>;
|
|
153
|
+
type ExtractDimensionProps<D extends Dimensions, DKP extends TDKP, UB extends boolean> = UB extends true ? Partial<ExtractNullableDimensionKeys<DimensionObjAttrs<D, DKP> & DimensionBooleanAttrs<DKP>>> : Partial<ExtractNullableDimensionKeys<DimensionObjAttrs<D, DKP>>>;
|
|
154
|
+
type ExtractDimensions<D extends Dimensions, DKP extends TDKP> = ExtractNullableDimensionKeys<DimensionObjAttrs<D, DKP>>;
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/types/config.d.ts
|
|
157
|
+
type RocketComponentType = ElementType & {
|
|
158
|
+
IS_ROCKETSTYLE: true;
|
|
159
|
+
$$rocketstyle: Record<string, unknown>;
|
|
160
|
+
};
|
|
161
|
+
type RocketProviderState<T extends RocketComponentType | TObj | unknown = unknown> = T extends RocketComponentType ? Partial<T["$$rocketstyle"]> & {
|
|
162
|
+
pseudo: PseudoState;
|
|
163
|
+
} : T;
|
|
164
|
+
type ConsumerCtxCBValue<T extends RocketComponentType, D extends Dimensions, DKP extends TDKP> = (attrs: RocketProviderState<T>) => DKP extends TDKP ? Partial<ExtractDimensions<D, DKP> & {
|
|
165
|
+
pseudo: PseudoState;
|
|
166
|
+
}> : TObj;
|
|
167
|
+
type ConsumerCtxCb<D extends Dimensions, DKP extends TDKP = TDKP> = <T extends RocketComponentType>(attrs: ConsumerCtxCBValue<T, D, DKP>) => ReturnType<ConsumerCtxCBValue<T, D, DKP>>;
|
|
168
|
+
type ConsumerCb<D extends Dimensions, DKP extends TDKP = TDKP> = (ctx: ConsumerCtxCb<D, DKP>) => ReturnType<ConsumerCtxCb<D, DKP>>;
|
|
169
|
+
type ConfigAttrs<C extends ElementType | unknown, D extends Dimensions, DKP extends TDKP, UB extends boolean> = Partial<{
|
|
170
|
+
name: string;
|
|
171
|
+
component: C;
|
|
172
|
+
provider: boolean;
|
|
173
|
+
consumer: ConsumerCb<D, DKP>;
|
|
174
|
+
DEBUG: boolean;
|
|
175
|
+
inversed: boolean;
|
|
176
|
+
passProps: UB extends true ? keyof DimensionBooleanAttrs<DKP>[] : never;
|
|
177
|
+
styled: boolean;
|
|
178
|
+
}>;
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/types/configuration.d.ts
|
|
181
|
+
type OptionFunc = (...arg: unknown[]) => Record<string, unknown>;
|
|
182
|
+
type InitConfiguration<C, D> = {
|
|
183
|
+
name?: string | undefined;
|
|
184
|
+
component: C;
|
|
185
|
+
useBooleans: boolean;
|
|
186
|
+
dimensions: D;
|
|
187
|
+
dimensionKeys: ArrayOfKeys<D>;
|
|
188
|
+
dimensionValues: ArrayOfValues<D>;
|
|
189
|
+
multiKeys: MultiKeys;
|
|
190
|
+
transformKeys: Partial<Record<string, true>>;
|
|
191
|
+
};
|
|
192
|
+
type Configuration<C = ElementType | unknown, D extends Dimensions = Dimensions> = InitConfiguration<C, D> & {
|
|
193
|
+
provider?: boolean | undefined;
|
|
194
|
+
consumer?: ConsumerCb<D> | undefined;
|
|
195
|
+
DEBUG?: boolean | undefined;
|
|
196
|
+
inversed?: boolean | undefined;
|
|
197
|
+
passProps?: string[] | undefined;
|
|
198
|
+
styled?: boolean | undefined;
|
|
199
|
+
attrs: OptionFunc[];
|
|
200
|
+
priorityAttrs: OptionFunc[];
|
|
201
|
+
filterAttrs: string[];
|
|
202
|
+
theme: OptionFunc[];
|
|
203
|
+
styles: StylesCbArray;
|
|
204
|
+
compose: Record<string, TFn | null | undefined | false>;
|
|
205
|
+
statics: Record<string, any>;
|
|
206
|
+
} & Record<string, any>;
|
|
207
|
+
type DefaultProps = Partial<PseudoProps>;
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/types/attrs.d.ts
|
|
210
|
+
type AttrsCb<A, T> = (props: Partial<A>, theme: T, helpers: {
|
|
211
|
+
mode?: ThemeModeKeys;
|
|
212
|
+
isDark?: boolean;
|
|
213
|
+
isLight?: boolean;
|
|
214
|
+
createElement: typeof render;
|
|
215
|
+
}) => Partial<A>;
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/types/hoc.d.ts
|
|
218
|
+
type GenericHoc = (component: ElementType) => ElementType;
|
|
219
|
+
type ComposeParam = Record<string, GenericHoc | null | undefined | false>;
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/types/rocketstyle.d.ts
|
|
222
|
+
type RocketStyleComponent<OA extends TObj = TObj, EA extends TObj = TObj, T extends TObj = TObj, CSS extends TObj = TObj, S extends TObj = TObj, HOC extends TObj = TObj, D extends Dimensions = Dimensions, UB extends boolean = boolean, DKP extends TDKP = TDKP> = IRocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP> & { [I in keyof D]: <K extends DimensionValue = D[I], P extends DimensionCallbackParam<Theme<T>, Styles<CSS>> = DimensionCallbackParam<Theme<T>, Styles<CSS>>>(param: P) => P extends DimensionCallbackParam<Theme<T>, Styles<CSS>> ? RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DimensionProps<K, D, P, DKP>> : RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP> };
|
|
223
|
+
/**
|
|
224
|
+
* @param OA Origin component props params.
|
|
225
|
+
* @param EA Extended prop types
|
|
226
|
+
* @param T Theme passed via context.
|
|
227
|
+
* @param CSS Custom theme accepted by styles.
|
|
228
|
+
* @param S Defined statics
|
|
229
|
+
* @param D Dimensions to be used for defining component states.
|
|
230
|
+
* @param UB Use booleans value
|
|
231
|
+
* @param DKP Dimensions key props.
|
|
232
|
+
* @param DFP Calculated final component props
|
|
233
|
+
*/
|
|
234
|
+
interface IRocketStyleComponent<OA extends TObj = TObj, EA extends TObj = TObj, T extends TObj = TObj, CSS extends TObj = TObj, S extends TObj = TObj, HOC extends TObj = TObj, D extends Dimensions = Dimensions, UB extends boolean = boolean, DKP extends TDKP = TDKP, DFP = MergeTypes<[OA, EA, DefaultProps, ExtractDimensionProps<D, DKP, UB>]>> {
|
|
235
|
+
(props: DFP): VNode | null;
|
|
236
|
+
config: <NC extends ElementType | unknown = unknown>({
|
|
237
|
+
name,
|
|
238
|
+
component: NC,
|
|
239
|
+
provider,
|
|
240
|
+
consumer,
|
|
241
|
+
DEBUG,
|
|
242
|
+
inversed,
|
|
243
|
+
passProps
|
|
244
|
+
}: ConfigAttrs<NC, D, DKP, UB>) => NC extends ElementType ? RocketStyleComponent<ExtractProps<NC>, EA, T, CSS, S, HOC, D, UB, DKP> : RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP>;
|
|
245
|
+
attrs: <P extends TObj | unknown = unknown>(param: P extends TObj ? Partial<MergeTypes<[DFP, P]>> | AttrsCb<MergeTypes<[DFP, P]>, Theme<T>> : Partial<DFP> | AttrsCb<DFP, Theme<T>>, config?: Partial<{
|
|
246
|
+
priority: boolean;
|
|
247
|
+
filter: P extends TObj ? Partial<keyof MergeTypes<[EA, P]>>[] : Partial<keyof EA>[];
|
|
248
|
+
}>) => P extends TObj ? RocketStyleComponent<OA, MergeTypes<[EA, P]>, T, CSS, S, HOC, D, UB, DKP> : RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP>;
|
|
249
|
+
theme: <P extends TObj = TObj>(param: Partial<P> | Partial<Styles<CSS>> | ThemeCb<P, Theme<T>>) => RocketStyleComponent<OA, EA, T, MergeTypes<[CSS, P]>, S, HOC, D, UB, DKP>;
|
|
250
|
+
styles: (param: StylesCb<CSS>) => RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP>;
|
|
251
|
+
compose: <P extends ComposeParam>(param: P) => P extends TObj ? RocketStyleComponent<OA, EA, T, CSS, S, MergeTypes<[HOC, P]>, D, UB, DKP> : RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP>;
|
|
252
|
+
statics: <P extends TObj | unknown = unknown>(param: P) => P extends TObj ? RocketStyleComponent<OA, EA, T, CSS, MergeTypes<[S, P]>, HOC, D, UB, DKP> : RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP>;
|
|
253
|
+
/** Access to all defined statics on the component. */
|
|
254
|
+
meta: S;
|
|
255
|
+
getStaticDimensions: (theme: TObj) => {
|
|
256
|
+
dimensions: DKP;
|
|
257
|
+
useBooleans: UB;
|
|
258
|
+
multiKeys: MultiKeys<D>;
|
|
259
|
+
};
|
|
260
|
+
getDefaultAttrs: (props: TObj, theme: TObj, mode: ThemeModeKeys) => TObj;
|
|
261
|
+
readonly $$rocketstyle: ExtractDimensions<D, DKP>;
|
|
262
|
+
readonly $$originTypes: OA;
|
|
263
|
+
readonly $$extendedTypes: EA;
|
|
264
|
+
readonly $$types: DFP;
|
|
265
|
+
IS_ROCKETSTYLE: true;
|
|
266
|
+
displayName: string;
|
|
267
|
+
}
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region src/types/rocketComponent.d.ts
|
|
270
|
+
type RocketComponent<C extends ElementType = ElementType, T extends TObj = TObj, CSS extends TObj = TObj, D extends Dimensions = DefaultDimensions, UB extends boolean = boolean> = (props: Configuration<C, D>) => RocketStyleComponent<ExtractProps<C>, TObj, T, CSS, TObj, TObj, D, UB, Record<string, any>>;
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region src/init.d.ts
|
|
273
|
+
type Rocketstyle = <D extends Dimensions = DefaultDimensions, UB extends boolean = true>({
|
|
274
|
+
dimensions,
|
|
275
|
+
useBooleans
|
|
276
|
+
}?: {
|
|
277
|
+
dimensions?: D;
|
|
278
|
+
useBooleans?: UB;
|
|
279
|
+
}) => <C extends ElementType>({
|
|
280
|
+
name,
|
|
281
|
+
component
|
|
282
|
+
}: {
|
|
283
|
+
name: string;
|
|
284
|
+
component: C;
|
|
285
|
+
}) => ReturnType<RocketComponent<C, Record<string, unknown>, Record<string, unknown>, D, UB>>;
|
|
286
|
+
declare const rocketstyle: Rocketstyle;
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/isRocketComponent.d.ts
|
|
289
|
+
type IsRocketComponent = <T>(component: T) => boolean;
|
|
290
|
+
/** Runtime type guard — checks if a component was created by `rocketstyle()`. */
|
|
291
|
+
declare const isRocketComponent: IsRocketComponent;
|
|
292
|
+
//#endregion
|
|
293
|
+
export { type AttrsCb, type ComponentFn, type ComposeParam, type ConfigAttrs, type ConsumerCb, type ConsumerCtxCBValue, type ConsumerCtxCb, type DefaultProps, type DimensionCallbackParam, type DimensionProps, type DimensionValue, type Dimensions, type ElementType, type ExtractDimensionProps, type ExtractDimensions, type ExtractProps, type GenericHoc, type IRocketStyleComponent, type IsRocketComponent, type MergeTypes, Provider, type RocketComponentType, type RocketProviderState, type RocketStyleComponent, type RocketStyleInterpolationProps, type Rocketstyle, type StylesCb, type StylesDefault, type TDKP, type TObj, type TProvider, type ThemeCb, type ThemeDefault, type ThemeMode, type ThemeModeCallback, type ThemeModeKeys, context, rocketstyle as default, rocketstyle, isRocketComponent };
|
|
294
|
+
//# sourceMappingURL=index2.d.ts.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
import { createContext, onUnmount, popContext, pushContext, useContext } from "@pyreon/core";
|
|
2
|
+
import { Provider as Provider$1, compose, config, context, get, hoistNonReactStatics, isEmpty, merge, omit, pick, render, set } from "@pyreon/ui-core";
|
|
3
|
+
import { signal } from "@pyreon/reactivity";
|
|
4
|
+
|
|
5
|
+
//#region src/constants/index.ts
|
|
6
|
+
/** Default theme mode used when no mode is provided via context. */
|
|
7
|
+
const MODE_DEFAULT = "light";
|
|
8
|
+
/** Pseudo-state interaction keys tracked for styling (hover, active, focus, pressed). */
|
|
9
|
+
const PSEUDO_KEYS = [
|
|
10
|
+
"hover",
|
|
11
|
+
"active",
|
|
12
|
+
"focus",
|
|
13
|
+
"pressed"
|
|
14
|
+
];
|
|
15
|
+
/** Meta pseudo-state keys representing non-interactive states (disabled, readOnly). */
|
|
16
|
+
const PSEUDO_META_KEYS = ["disabled", "readOnly"];
|
|
17
|
+
/** Supported theme mode flags. */
|
|
18
|
+
const THEME_MODES = {
|
|
19
|
+
light: true,
|
|
20
|
+
dark: true
|
|
21
|
+
};
|
|
22
|
+
/** Maps each theme mode to its inverse (light -> dark, dark -> light). */
|
|
23
|
+
const THEME_MODES_INVERSED = {
|
|
24
|
+
dark: "light",
|
|
25
|
+
light: "dark"
|
|
26
|
+
};
|
|
27
|
+
/** Reserved configuration keys accepted by the `.config()` chaining method. */
|
|
28
|
+
const CONFIG_KEYS = [
|
|
29
|
+
"provider",
|
|
30
|
+
"consumer",
|
|
31
|
+
"DEBUG",
|
|
32
|
+
"name",
|
|
33
|
+
"component",
|
|
34
|
+
"inversed",
|
|
35
|
+
"passProps",
|
|
36
|
+
"styled"
|
|
37
|
+
];
|
|
38
|
+
/** Keys for theme and styles chaining methods. */
|
|
39
|
+
const STYLING_KEYS = ["theme", "styles"];
|
|
40
|
+
const STATIC_KEYS = [...STYLING_KEYS, "compose"];
|
|
41
|
+
/** Union of all reserved keys that cannot be used as dimension names. */
|
|
42
|
+
const ALL_RESERVED_KEYS = [
|
|
43
|
+
...Object.keys(THEME_MODES),
|
|
44
|
+
...CONFIG_KEYS,
|
|
45
|
+
...STATIC_KEYS,
|
|
46
|
+
"attrs"
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/context/context.ts
|
|
51
|
+
/**
|
|
52
|
+
* Top-level theme and mode provider for rocketstyle components.
|
|
53
|
+
* Reads the parent context, merges incoming props, and resolves
|
|
54
|
+
* the active mode (with optional inversion for nested dark/light switching).
|
|
55
|
+
*
|
|
56
|
+
* In Pyreon, context is provided via pushContext/popContext instead of React.Provider.
|
|
57
|
+
*/
|
|
58
|
+
const Provider = ({ provider = Provider$1, inversed, ...props }) => {
|
|
59
|
+
const { theme, mode, provider: RocketstyleProvider, children } = {
|
|
60
|
+
...useContext(context),
|
|
61
|
+
...props,
|
|
62
|
+
provider
|
|
63
|
+
};
|
|
64
|
+
let newMode = MODE_DEFAULT;
|
|
65
|
+
if (mode) newMode = inversed ? THEME_MODES_INVERSED[mode] : mode;
|
|
66
|
+
return RocketstyleProvider({
|
|
67
|
+
mode: newMode,
|
|
68
|
+
isDark: newMode === "dark",
|
|
69
|
+
isLight: newMode === "light",
|
|
70
|
+
...theme !== void 0 ? { theme } : {},
|
|
71
|
+
provider,
|
|
72
|
+
children
|
|
73
|
+
}) ?? null;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/constants/defaultDimensions.ts
|
|
78
|
+
/**
|
|
79
|
+
* Default dimension configuration for rocketstyle components.
|
|
80
|
+
* Defines four built-in dimensions: `states`, `sizes`, `variants`,
|
|
81
|
+
* and `multiple` (a multi-select dimension).
|
|
82
|
+
*/
|
|
83
|
+
const DEFAULT_DIMENSIONS = {
|
|
84
|
+
states: "state",
|
|
85
|
+
sizes: "size",
|
|
86
|
+
variants: "variant",
|
|
87
|
+
multiple: {
|
|
88
|
+
propName: "multiple",
|
|
89
|
+
multi: true
|
|
90
|
+
},
|
|
91
|
+
modifiers: {
|
|
92
|
+
propName: "modifier",
|
|
93
|
+
multi: true,
|
|
94
|
+
transform: true
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/cache/LocalThemeManager.ts
|
|
100
|
+
/**
|
|
101
|
+
* WeakMap-based multi-tier cache for computed theme objects.
|
|
102
|
+
* Maintains separate caches for base themes, dimension themes,
|
|
103
|
+
* and their light/dark mode variants to avoid recalculation on re-renders.
|
|
104
|
+
*/
|
|
105
|
+
var ThemeManager = class {
|
|
106
|
+
baseTheme = /* @__PURE__ */ new WeakMap();
|
|
107
|
+
dimensionsThemes = /* @__PURE__ */ new WeakMap();
|
|
108
|
+
modeBaseTheme = {
|
|
109
|
+
light: /* @__PURE__ */ new WeakMap(),
|
|
110
|
+
dark: /* @__PURE__ */ new WeakMap()
|
|
111
|
+
};
|
|
112
|
+
modeDimensionTheme = {
|
|
113
|
+
light: /* @__PURE__ */ new WeakMap(),
|
|
114
|
+
dark: /* @__PURE__ */ new WeakMap()
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/context/localContext.ts
|
|
120
|
+
/**
|
|
121
|
+
* Local context for propagating pseudo-state (hover, focus, pressed)
|
|
122
|
+
* and additional styling attributes from a parent provider component
|
|
123
|
+
* to its rocketstyle children.
|
|
124
|
+
*/
|
|
125
|
+
const localContext = createContext({});
|
|
126
|
+
const EMPTY_CTX = { pseudo: {} };
|
|
127
|
+
const useLocalContext = (consumer) => {
|
|
128
|
+
const ctx = useContext(localContext);
|
|
129
|
+
if (!consumer) return EMPTY_CTX;
|
|
130
|
+
return {
|
|
131
|
+
pseudo: {},
|
|
132
|
+
...consumer((callback) => callback(ctx))
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/context/createLocalProvider.ts
|
|
138
|
+
/**
|
|
139
|
+
* Higher-order component that wraps a component with a LocalProvider,
|
|
140
|
+
* detecting pseudo-states (hover, focus, pressed) via mouse/focus events
|
|
141
|
+
* and broadcasting them through local context to child rocketstyle components.
|
|
142
|
+
*
|
|
143
|
+
* In Pyreon, context is provided via pushContext/popContext, and state is
|
|
144
|
+
* managed with signals instead of useState.
|
|
145
|
+
*/
|
|
146
|
+
const createLocalProvider = (WrappedComponent) => {
|
|
147
|
+
const HOCComponent = ({ onMouseEnter, onMouseLeave, onMouseUp, onMouseDown, onFocus, onBlur, $rocketstate, ...props }) => {
|
|
148
|
+
const hover = signal(false);
|
|
149
|
+
const focus = signal(false);
|
|
150
|
+
const pressed = signal(false);
|
|
151
|
+
const pseudoState = () => ({
|
|
152
|
+
hover: hover(),
|
|
153
|
+
focus: focus(),
|
|
154
|
+
pressed: pressed()
|
|
155
|
+
});
|
|
156
|
+
const events = {
|
|
157
|
+
onMouseEnter: (e) => {
|
|
158
|
+
hover.set(true);
|
|
159
|
+
if (onMouseEnter) onMouseEnter(e);
|
|
160
|
+
},
|
|
161
|
+
onMouseLeave: (e) => {
|
|
162
|
+
hover.set(false);
|
|
163
|
+
pressed.set(false);
|
|
164
|
+
if (onMouseLeave) onMouseLeave(e);
|
|
165
|
+
},
|
|
166
|
+
onMouseDown: (e) => {
|
|
167
|
+
pressed.set(true);
|
|
168
|
+
if (onMouseDown) onMouseDown(e);
|
|
169
|
+
},
|
|
170
|
+
onMouseUp: (e) => {
|
|
171
|
+
pressed.set(false);
|
|
172
|
+
if (onMouseUp) onMouseUp(e);
|
|
173
|
+
},
|
|
174
|
+
onFocus: (e) => {
|
|
175
|
+
focus.set(true);
|
|
176
|
+
if (onFocus) onFocus(e);
|
|
177
|
+
},
|
|
178
|
+
onBlur: (e) => {
|
|
179
|
+
focus.set(false);
|
|
180
|
+
if (onBlur) onBlur(e);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const updatedState = {
|
|
184
|
+
...$rocketstate,
|
|
185
|
+
pseudo: {
|
|
186
|
+
...$rocketstate?.pseudo,
|
|
187
|
+
...pseudoState()
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
pushContext(new Map([[localContext.id, updatedState]]));
|
|
191
|
+
onUnmount(() => popContext());
|
|
192
|
+
return WrappedComponent({
|
|
193
|
+
...props,
|
|
194
|
+
...events,
|
|
195
|
+
$rocketstate: updatedState
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
return HOCComponent;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/hooks/useTheme.ts
|
|
203
|
+
/**
|
|
204
|
+
* Retrieves the current theme object and resolved mode from context.
|
|
205
|
+
* Supports mode inversion so nested components can flip between
|
|
206
|
+
* light and dark without a new provider.
|
|
207
|
+
*
|
|
208
|
+
* In Pyreon, components run once — no useMemo needed.
|
|
209
|
+
*/
|
|
210
|
+
const useThemeAttrs = ({ inversed }) => {
|
|
211
|
+
const { theme = {}, mode: ctxMode = "light", isDark: ctxDark } = useContext(context) || {};
|
|
212
|
+
const mode = inversed ? THEME_MODES_INVERSED[ctxMode] : ctxMode;
|
|
213
|
+
const isDark = inversed ? !ctxDark : ctxDark;
|
|
214
|
+
return {
|
|
215
|
+
theme,
|
|
216
|
+
mode,
|
|
217
|
+
isDark,
|
|
218
|
+
isLight: !isDark
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/utils/attrs.ts
|
|
224
|
+
const removeUndefinedProps = (props) => {
|
|
225
|
+
const result = {};
|
|
226
|
+
for (const key in props) if (props[key] !== void 0) result[key] = props[key];
|
|
227
|
+
return result;
|
|
228
|
+
};
|
|
229
|
+
/** Picks only the props whose keys exist in the dimension keywords lookup and have truthy values. */
|
|
230
|
+
const pickStyledAttrs = (props, keywords) => {
|
|
231
|
+
const result = {};
|
|
232
|
+
for (const key of Object.keys(props)) if (keywords[key] && props[key]) result[key] = props[key];
|
|
233
|
+
return result;
|
|
234
|
+
};
|
|
235
|
+
const calculateChainOptions = (options) => (args) => {
|
|
236
|
+
if (!options || options.length === 0) return {};
|
|
237
|
+
return options.reduce((acc, item) => Object.assign(acc, item(...args)), {});
|
|
238
|
+
};
|
|
239
|
+
const calculateStylingAttrs = ({ useBooleans, multiKeys }) => ({ props, dimensions }) => {
|
|
240
|
+
const result = {};
|
|
241
|
+
Object.keys(dimensions).forEach((item) => {
|
|
242
|
+
const pickedProp = props[item];
|
|
243
|
+
const t = typeof pickedProp;
|
|
244
|
+
if (multiKeys?.[item] && Array.isArray(pickedProp)) result[item] = pickedProp;
|
|
245
|
+
else if (t === "string" || t === "number") result[item] = pickedProp;
|
|
246
|
+
else result[item] = void 0;
|
|
247
|
+
});
|
|
248
|
+
if (useBooleans) {
|
|
249
|
+
const propsKeys = Object.keys(props);
|
|
250
|
+
Object.entries(result).forEach(([key, value]) => {
|
|
251
|
+
const isMultiKey = multiKeys?.[key];
|
|
252
|
+
if (!value) {
|
|
253
|
+
let newDimensionValue;
|
|
254
|
+
const keywordSet = new Set(Object.keys(dimensions[key]));
|
|
255
|
+
if (isMultiKey) newDimensionValue = propsKeys.filter((propKey) => keywordSet.has(propKey));
|
|
256
|
+
else for (let i = propsKeys.length - 1; i >= 0; i--) {
|
|
257
|
+
const k = propsKeys[i];
|
|
258
|
+
if (keywordSet.has(k) && props[k]) {
|
|
259
|
+
newDimensionValue = k;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
result[key] = newDimensionValue;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
//#endregion
|
|
271
|
+
//#region src/hoc/rocketstyleAttrsHoc.ts
|
|
272
|
+
/**
|
|
273
|
+
* HOC that resolves the `.attrs()` chain before the inner component renders.
|
|
274
|
+
* Evaluates both regular and priority attrs callbacks with the current theme
|
|
275
|
+
* and mode, then merges the results with explicit props (priority attrs
|
|
276
|
+
* are applied first, regular attrs can be overridden by direct props).
|
|
277
|
+
*
|
|
278
|
+
* In Pyreon, there is no forwardRef — ref flows as a normal prop.
|
|
279
|
+
* Components are plain functions.
|
|
280
|
+
*/
|
|
281
|
+
const rocketStyleHOC = ({ inversed, attrs, priorityAttrs }) => {
|
|
282
|
+
const calculateAttrs = calculateChainOptions(attrs);
|
|
283
|
+
const calculatePriorityAttrs = calculateChainOptions(priorityAttrs);
|
|
284
|
+
const Enhanced = (WrappedComponent) => {
|
|
285
|
+
const HOCComponent = (props) => {
|
|
286
|
+
const { theme, mode, isDark, isLight } = useThemeAttrs({ inversed });
|
|
287
|
+
const callbackParams = [theme, {
|
|
288
|
+
render,
|
|
289
|
+
mode,
|
|
290
|
+
isDark,
|
|
291
|
+
isLight
|
|
292
|
+
}];
|
|
293
|
+
const filteredProps = removeUndefinedProps(props);
|
|
294
|
+
const prioritizedAttrs = calculatePriorityAttrs([filteredProps, ...callbackParams]);
|
|
295
|
+
const finalAttrs = calculateAttrs([{
|
|
296
|
+
...prioritizedAttrs,
|
|
297
|
+
...filteredProps
|
|
298
|
+
}, ...callbackParams]);
|
|
299
|
+
return WrappedComponent({
|
|
300
|
+
...prioritizedAttrs,
|
|
301
|
+
...finalAttrs,
|
|
302
|
+
...filteredProps
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
return HOCComponent;
|
|
306
|
+
};
|
|
307
|
+
return Enhanced;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/utils/chaining.ts
|
|
312
|
+
const chainOptions = (opts, defaultOpts = []) => {
|
|
313
|
+
const result = [...defaultOpts];
|
|
314
|
+
if (typeof opts === "function") result.push(opts);
|
|
315
|
+
else if (typeof opts === "object") result.push(() => opts);
|
|
316
|
+
return result;
|
|
317
|
+
};
|
|
318
|
+
const chainOrOptions = (keys, opts, defaultOpts) => keys.reduce((acc, item) => ({
|
|
319
|
+
...acc,
|
|
320
|
+
[item]: opts[item] || defaultOpts[item]
|
|
321
|
+
}), {});
|
|
322
|
+
const chainReservedKeyOptions = (keys, opts, defaultOpts) => keys.reduce((acc, item) => ({
|
|
323
|
+
...acc,
|
|
324
|
+
[item]: chainOptions(opts[item], defaultOpts[item] ?? [])
|
|
325
|
+
}), {});
|
|
326
|
+
|
|
327
|
+
//#endregion
|
|
328
|
+
//#region src/utils/compose.ts
|
|
329
|
+
const calculateHocsFuncs = (options = {}) => Object.values(options).filter((item) => typeof item === "function").reverse();
|
|
330
|
+
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region src/utils/dimensions.ts
|
|
333
|
+
const isValidKey = (value) => value !== void 0 && value !== null && value !== false;
|
|
334
|
+
const isMultiKey = (value) => {
|
|
335
|
+
if (typeof value === "object" && value !== null) return [true, get(value, "propName")];
|
|
336
|
+
return [false, value];
|
|
337
|
+
};
|
|
338
|
+
const getDimensionsMap = ({ themes, useBooleans }) => {
|
|
339
|
+
const result = {
|
|
340
|
+
keysMap: {},
|
|
341
|
+
keywords: {}
|
|
342
|
+
};
|
|
343
|
+
if (isEmpty(themes)) return result;
|
|
344
|
+
return Object.entries(themes).reduce((accumulator, [key, value]) => {
|
|
345
|
+
const { keysMap, keywords } = accumulator;
|
|
346
|
+
keywords[key] = true;
|
|
347
|
+
Object.entries(value).forEach(([itemKey, itemValue]) => {
|
|
348
|
+
if (!isValidKey(itemValue)) return;
|
|
349
|
+
if (useBooleans) keywords[itemKey] = true;
|
|
350
|
+
set(keysMap, [key, itemKey], true);
|
|
351
|
+
});
|
|
352
|
+
return accumulator;
|
|
353
|
+
}, result);
|
|
354
|
+
};
|
|
355
|
+
const getKeys = (obj) => Object.keys(obj);
|
|
356
|
+
const getValues = ((obj) => Object.values(obj));
|
|
357
|
+
const getDimensionsValues = ((obj) => getValues(obj).map((item) => {
|
|
358
|
+
if (typeof item === "object") return item.propName;
|
|
359
|
+
return item;
|
|
360
|
+
}));
|
|
361
|
+
const getMultipleDimensions = (obj) => getValues(obj).reduce((accumulator, value) => {
|
|
362
|
+
if (typeof value === "object") {
|
|
363
|
+
if (value.multi === true) accumulator[value.propName] = true;
|
|
364
|
+
}
|
|
365
|
+
return accumulator;
|
|
366
|
+
}, {});
|
|
367
|
+
const getTransformDimensions = (obj) => getValues(obj).reduce((accumulator, value) => {
|
|
368
|
+
if (typeof value === "object") {
|
|
369
|
+
if (value.transform === true) accumulator[value.propName] = true;
|
|
370
|
+
}
|
|
371
|
+
return accumulator;
|
|
372
|
+
}, {});
|
|
373
|
+
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region src/utils/statics.ts
|
|
376
|
+
const createStaticsChainingEnhancers = ({ context, dimensionKeys, func, options }) => {
|
|
377
|
+
[...dimensionKeys, ...STATIC_KEYS].forEach((item) => {
|
|
378
|
+
context[item] = (props) => func(options, { [item]: props });
|
|
379
|
+
});
|
|
380
|
+
};
|
|
381
|
+
const createStaticsEnhancers = ({ context, options }) => {
|
|
382
|
+
if (!isEmpty(options)) Object.assign(context, options);
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region src/utils/styles.ts
|
|
387
|
+
const calculateStyles = (styles) => {
|
|
388
|
+
if (!styles) return [];
|
|
389
|
+
return styles.map((item) => item(config.css));
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/utils/collection.ts
|
|
394
|
+
const removeNullableValues = (obj) => Object.entries(obj).filter(([, v]) => v != null && v !== false).reduce((acc, [k, v]) => ({
|
|
395
|
+
...acc,
|
|
396
|
+
[k]: v
|
|
397
|
+
}), {});
|
|
398
|
+
|
|
399
|
+
//#endregion
|
|
400
|
+
//#region src/utils/theme.ts
|
|
401
|
+
const MODE_CALLBACK_BRAND = Symbol.for("pyreon.themeModeCallback");
|
|
402
|
+
/** Creates a mode-switching function that returns the light or dark value based on the active mode. */
|
|
403
|
+
const themeModeCallback = (light, dark) => {
|
|
404
|
+
const fn = (mode) => {
|
|
405
|
+
if (!mode || mode === "light") return light;
|
|
406
|
+
return dark;
|
|
407
|
+
};
|
|
408
|
+
fn.__brand = MODE_CALLBACK_BRAND;
|
|
409
|
+
return fn;
|
|
410
|
+
};
|
|
411
|
+
const isModeCallback = (value) => typeof value === "function" && value.__brand === MODE_CALLBACK_BRAND;
|
|
412
|
+
const getThemeFromChain = (options, theme) => {
|
|
413
|
+
const result = {};
|
|
414
|
+
if (!options || isEmpty(options)) return result;
|
|
415
|
+
return options.reduce((acc, item) => merge(acc, item(theme, themeModeCallback, config.css)), result);
|
|
416
|
+
};
|
|
417
|
+
const getDimensionThemes = (theme, options) => {
|
|
418
|
+
const result = {};
|
|
419
|
+
if (isEmpty(options.dimensions)) return result;
|
|
420
|
+
return Object.entries(options.dimensions).reduce((acc, [key, value]) => {
|
|
421
|
+
const [, dimension] = isMultiKey(value);
|
|
422
|
+
const helper = options[key];
|
|
423
|
+
if (Array.isArray(helper) && helper.length > 0) acc[dimension] = removeNullableValues(getThemeFromChain(helper, theme));
|
|
424
|
+
return acc;
|
|
425
|
+
}, result);
|
|
426
|
+
};
|
|
427
|
+
const getTheme = ({ rocketstate, themes, baseTheme, transformKeys, appTheme }) => {
|
|
428
|
+
let finalTheme = { ...baseTheme };
|
|
429
|
+
const deferredTransforms = [];
|
|
430
|
+
Object.entries(rocketstate).forEach(([key, value]) => {
|
|
431
|
+
const keyTheme = themes[key] ?? {};
|
|
432
|
+
const isTransform = transformKeys?.[key];
|
|
433
|
+
const mergeValue = (item) => {
|
|
434
|
+
const val = keyTheme[item];
|
|
435
|
+
if (isTransform && typeof val === "function") deferredTransforms.push(val);
|
|
436
|
+
else finalTheme = merge({}, finalTheme, val);
|
|
437
|
+
};
|
|
438
|
+
if (Array.isArray(value)) value.forEach(mergeValue);
|
|
439
|
+
else mergeValue(value);
|
|
440
|
+
});
|
|
441
|
+
for (const transform of deferredTransforms) finalTheme = merge({}, finalTheme, transform(finalTheme, appTheme ?? {}, themeModeCallback, config.css));
|
|
442
|
+
return finalTheme;
|
|
443
|
+
};
|
|
444
|
+
const getThemeByMode = (object, mode) => Object.keys(object).reduce((acc, key) => {
|
|
445
|
+
const value = object[key];
|
|
446
|
+
if (typeof value === "object" && value !== null) acc[key] = getThemeByMode(value, mode);
|
|
447
|
+
else if (isModeCallback(value)) acc[key] = value(mode);
|
|
448
|
+
else acc[key] = value;
|
|
449
|
+
return acc;
|
|
450
|
+
}, {});
|
|
451
|
+
|
|
452
|
+
//#endregion
|
|
453
|
+
//#region src/rocketstyle.ts
|
|
454
|
+
/** Clones the current configuration and merges new options, returning a fresh rocketComponent. */
|
|
455
|
+
const cloneAndEnhance = (defaultOpts, opts) => rocketComponent({
|
|
456
|
+
...defaultOpts,
|
|
457
|
+
attrs: chainOptions(opts.attrs, defaultOpts.attrs),
|
|
458
|
+
filterAttrs: [...defaultOpts.filterAttrs ?? [], ...opts.filterAttrs ?? []],
|
|
459
|
+
priorityAttrs: chainOptions(opts.priorityAttrs, defaultOpts.priorityAttrs),
|
|
460
|
+
statics: {
|
|
461
|
+
...defaultOpts.statics,
|
|
462
|
+
...opts.statics
|
|
463
|
+
},
|
|
464
|
+
compose: {
|
|
465
|
+
...defaultOpts.compose,
|
|
466
|
+
...opts.compose
|
|
467
|
+
},
|
|
468
|
+
...chainOrOptions(CONFIG_KEYS, opts, defaultOpts),
|
|
469
|
+
...chainReservedKeyOptions([...defaultOpts.dimensionKeys, ...STYLING_KEYS], opts, defaultOpts)
|
|
470
|
+
});
|
|
471
|
+
const rocketComponent = (options) => {
|
|
472
|
+
const { component, styles } = options;
|
|
473
|
+
const { styled } = config;
|
|
474
|
+
const _calculateStylingAttrs = calculateStylingAttrs({
|
|
475
|
+
multiKeys: options.multiKeys,
|
|
476
|
+
useBooleans: options.useBooleans
|
|
477
|
+
});
|
|
478
|
+
const componentName = options.name ?? options.component.displayName ?? options.component.name;
|
|
479
|
+
const STYLED_COMPONENT = component.IS_ROCKETSTYLE ?? options.styled !== true ? component : styled(component, { boost: true })`
|
|
480
|
+
${calculateStyles(styles)};
|
|
481
|
+
`;
|
|
482
|
+
const RenderComponent = options.provider ? createLocalProvider(STYLED_COMPONENT) : STYLED_COMPONENT;
|
|
483
|
+
const ThemeManager$1 = new ThemeManager();
|
|
484
|
+
const hocsFuncs = [rocketStyleHOC(options), ...calculateHocsFuncs(options.compose)];
|
|
485
|
+
const EnhancedComponent = (props) => {
|
|
486
|
+
const localCtx = useLocalContext(options.consumer);
|
|
487
|
+
const { theme, mode } = useThemeAttrs(options);
|
|
488
|
+
const baseThemeHelper = ThemeManager$1.baseTheme;
|
|
489
|
+
if (!baseThemeHelper.has(theme)) baseThemeHelper.set(theme, getThemeFromChain(options.theme, theme));
|
|
490
|
+
const baseTheme = baseThemeHelper.get(theme);
|
|
491
|
+
const dimHelper = ThemeManager$1.dimensionsThemes;
|
|
492
|
+
if (!dimHelper.has(theme)) dimHelper.set(theme, getDimensionThemes(theme, options));
|
|
493
|
+
const themes = dimHelper.get(theme);
|
|
494
|
+
const modeBaseHelper = ThemeManager$1.modeBaseTheme[mode];
|
|
495
|
+
if (!modeBaseHelper.has(baseTheme)) modeBaseHelper.set(baseTheme, getThemeByMode(baseTheme, mode));
|
|
496
|
+
const currentModeBaseTheme = modeBaseHelper.get(baseTheme);
|
|
497
|
+
const modeDimHelper = ThemeManager$1.modeDimensionTheme[mode];
|
|
498
|
+
if (!modeDimHelper.has(themes)) modeDimHelper.set(themes, getThemeByMode(themes, mode));
|
|
499
|
+
const currentModeThemes = modeDimHelper.get(themes);
|
|
500
|
+
const { keysMap: dimensions, keywords: reservedPropNames } = getDimensionsMap({
|
|
501
|
+
themes,
|
|
502
|
+
useBooleans: options.useBooleans
|
|
503
|
+
});
|
|
504
|
+
const RESERVED_STYLING_PROPS_KEYS = Object.keys(reservedPropNames);
|
|
505
|
+
const { pseudo, ...mergeProps } = {
|
|
506
|
+
...localCtx,
|
|
507
|
+
...props
|
|
508
|
+
};
|
|
509
|
+
const pseudoRocketstate = {
|
|
510
|
+
...pseudo,
|
|
511
|
+
...pick(props, [...PSEUDO_KEYS, ...PSEUDO_META_KEYS])
|
|
512
|
+
};
|
|
513
|
+
const rocketstate = _calculateStylingAttrs({
|
|
514
|
+
props: pickStyledAttrs(mergeProps, reservedPropNames),
|
|
515
|
+
dimensions
|
|
516
|
+
});
|
|
517
|
+
const finalRocketstate = {
|
|
518
|
+
...rocketstate,
|
|
519
|
+
pseudo: pseudoRocketstate
|
|
520
|
+
};
|
|
521
|
+
const computedRocketstyle = getTheme({
|
|
522
|
+
rocketstate,
|
|
523
|
+
themes: currentModeThemes,
|
|
524
|
+
baseTheme: currentModeBaseTheme,
|
|
525
|
+
transformKeys: options.transformKeys,
|
|
526
|
+
appTheme: theme
|
|
527
|
+
});
|
|
528
|
+
const finalProps = {
|
|
529
|
+
...omit(mergeProps, [
|
|
530
|
+
...RESERVED_STYLING_PROPS_KEYS,
|
|
531
|
+
...PSEUDO_KEYS,
|
|
532
|
+
...options.filterAttrs
|
|
533
|
+
]),
|
|
534
|
+
...options.passProps ? pick(mergeProps, options.passProps) : {},
|
|
535
|
+
ref: props.ref,
|
|
536
|
+
$rocketstyle: computedRocketstyle,
|
|
537
|
+
$rocketstate: finalRocketstate
|
|
538
|
+
};
|
|
539
|
+
if (process.env.NODE_ENV !== "production") {
|
|
540
|
+
finalProps["data-rocketstyle"] = componentName;
|
|
541
|
+
if (options.DEBUG) {
|
|
542
|
+
const debugPayload = {
|
|
543
|
+
component: componentName,
|
|
544
|
+
rocketstate: finalRocketstate,
|
|
545
|
+
rocketstyle: computedRocketstyle,
|
|
546
|
+
dimensions,
|
|
547
|
+
mode,
|
|
548
|
+
reservedPropNames: RESERVED_STYLING_PROPS_KEYS,
|
|
549
|
+
filteredAttrs: options.filterAttrs
|
|
550
|
+
};
|
|
551
|
+
console.debug(`[rocketstyle] ${componentName} render:`, debugPayload);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return RenderComponent(finalProps);
|
|
555
|
+
};
|
|
556
|
+
const FinalComponent = compose(...hocsFuncs)(EnhancedComponent);
|
|
557
|
+
FinalComponent.IS_ROCKETSTYLE = true;
|
|
558
|
+
FinalComponent.displayName = componentName;
|
|
559
|
+
hoistNonReactStatics(FinalComponent, options.component);
|
|
560
|
+
createStaticsChainingEnhancers({
|
|
561
|
+
context: FinalComponent,
|
|
562
|
+
dimensionKeys: options.dimensionKeys,
|
|
563
|
+
func: cloneAndEnhance,
|
|
564
|
+
options
|
|
565
|
+
});
|
|
566
|
+
FinalComponent.IS_ROCKETSTYLE = true;
|
|
567
|
+
FinalComponent.displayName = componentName;
|
|
568
|
+
FinalComponent.meta = {};
|
|
569
|
+
createStaticsEnhancers({
|
|
570
|
+
context: FinalComponent.meta,
|
|
571
|
+
options: options.statics
|
|
572
|
+
});
|
|
573
|
+
Object.assign(FinalComponent, {
|
|
574
|
+
attrs: (attrs, { priority, filter } = {}) => {
|
|
575
|
+
const result = {};
|
|
576
|
+
if (filter) result.filterAttrs = filter;
|
|
577
|
+
if (priority) {
|
|
578
|
+
result.priorityAttrs = attrs;
|
|
579
|
+
return cloneAndEnhance(options, result);
|
|
580
|
+
}
|
|
581
|
+
result.attrs = attrs;
|
|
582
|
+
return cloneAndEnhance(options, result);
|
|
583
|
+
},
|
|
584
|
+
config: (opts = {}) => {
|
|
585
|
+
return cloneAndEnhance(options, pick(opts, CONFIG_KEYS));
|
|
586
|
+
},
|
|
587
|
+
statics: (opts) => cloneAndEnhance(options, { statics: opts }),
|
|
588
|
+
getStaticDimensions: (theme) => {
|
|
589
|
+
const { keysMap, keywords } = getDimensionsMap({
|
|
590
|
+
themes: getDimensionThemes(theme, options),
|
|
591
|
+
useBooleans: options.useBooleans
|
|
592
|
+
});
|
|
593
|
+
return {
|
|
594
|
+
dimensions: keysMap,
|
|
595
|
+
keywords,
|
|
596
|
+
useBooleans: options.useBooleans,
|
|
597
|
+
multiKeys: options.multiKeys
|
|
598
|
+
};
|
|
599
|
+
},
|
|
600
|
+
getDefaultAttrs: (props, theme, mode) => calculateChainOptions(options.attrs)([
|
|
601
|
+
props,
|
|
602
|
+
theme,
|
|
603
|
+
{
|
|
604
|
+
render,
|
|
605
|
+
mode,
|
|
606
|
+
isDark: mode === "light",
|
|
607
|
+
isLight: mode === "dark"
|
|
608
|
+
}
|
|
609
|
+
])
|
|
610
|
+
});
|
|
611
|
+
return FinalComponent;
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
//#endregion
|
|
615
|
+
//#region src/init.ts
|
|
616
|
+
const validateInit = (name, component, dimensions) => {
|
|
617
|
+
const errors = {};
|
|
618
|
+
if (!component) errors.component = "Parameter `component` is missing in params!";
|
|
619
|
+
if (!name) errors.name = "Parameter `name` is missing in params!";
|
|
620
|
+
if (isEmpty(dimensions)) errors.dimensions = "Parameter `dimensions` is missing in params!";
|
|
621
|
+
else {
|
|
622
|
+
const definedDimensions = getKeys(dimensions);
|
|
623
|
+
if (ALL_RESERVED_KEYS.some((item) => definedDimensions.some((d) => d === item))) errors.invalidDimensions = `Some of your \`dimensions\` is invalid and uses reserved static keys which are
|
|
624
|
+
${DEFAULT_DIMENSIONS.toString()}`;
|
|
625
|
+
}
|
|
626
|
+
if (!isEmpty(errors)) throw Error(JSON.stringify(errors));
|
|
627
|
+
};
|
|
628
|
+
const rocketstyle = ({ dimensions = DEFAULT_DIMENSIONS, useBooleans = true } = {}) => ({ name, component }) => {
|
|
629
|
+
if (process.env.NODE_ENV !== "production") validateInit(name, component, dimensions);
|
|
630
|
+
return rocketComponent({
|
|
631
|
+
name,
|
|
632
|
+
component,
|
|
633
|
+
useBooleans,
|
|
634
|
+
dimensions,
|
|
635
|
+
dimensionKeys: getKeys(dimensions),
|
|
636
|
+
dimensionValues: getDimensionsValues(dimensions),
|
|
637
|
+
multiKeys: getMultipleDimensions(dimensions),
|
|
638
|
+
transformKeys: getTransformDimensions(dimensions),
|
|
639
|
+
styled: true
|
|
640
|
+
});
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
//#endregion
|
|
644
|
+
//#region src/isRocketComponent.ts
|
|
645
|
+
/** Runtime type guard — checks if a component was created by `rocketstyle()`. */
|
|
646
|
+
const isRocketComponent = (component) => {
|
|
647
|
+
if (component && (typeof component === "object" || typeof component === "function") && component !== null && Object.hasOwn(component, "IS_ROCKETSTYLE")) return true;
|
|
648
|
+
return false;
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
//#endregion
|
|
652
|
+
//#region src/index.ts
|
|
653
|
+
var src_default = rocketstyle;
|
|
654
|
+
|
|
655
|
+
//#endregion
|
|
656
|
+
export { Provider, context, src_default as default, isRocketComponent, rocketstyle };
|
|
657
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pyreon/rocketstyle",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
"source": "./src/index.ts",
|
|
9
|
+
"import": "./lib/index.js",
|
|
10
|
+
"types": "./lib/index.d.ts"
|
|
11
|
+
},
|
|
12
|
+
"types": "./lib/index.d.ts",
|
|
13
|
+
"main": "./lib/index.js",
|
|
14
|
+
"files": [
|
|
15
|
+
"lib",
|
|
16
|
+
"!lib/**/*.map",
|
|
17
|
+
"!lib/analysis",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">= 18"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"prepublish": "bun run build",
|
|
29
|
+
"build": "bun run vl_rolldown_build",
|
|
30
|
+
"build:watch": "bun run vl_rolldown_build-watch",
|
|
31
|
+
"lint": "biome check src/",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:coverage": "vitest run --coverage",
|
|
34
|
+
"test:watch": "vitest",
|
|
35
|
+
"typecheck": "tsc --noEmit"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@pyreon/core": ">=0.3.0",
|
|
39
|
+
"@pyreon/reactivity": ">=0.3.0",
|
|
40
|
+
"@pyreon/ui-core": "^0.0.2",
|
|
41
|
+
"@pyreon/styler": "^0.0.2"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@vitus-labs/tools-rolldown": "^1.15.0",
|
|
45
|
+
"@vitus-labs/tools-typescript": "^1.15.0"
|
|
46
|
+
}
|
|
47
|
+
}
|