@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 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
+ }