@vitus-labs/rocketstyle 2.0.0-alpha.9 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@ Multi-dimensional styling system for React.
5
5
  [![npm](https://img.shields.io/npm/v/@vitus-labs/rocketstyle)](https://www.npmjs.com/package/@vitus-labs/rocketstyle)
6
6
  [![license](https://img.shields.io/npm/l/@vitus-labs/rocketstyle)](https://github.com/vitus-labs/ui-system/blob/main/LICENSE)
7
7
 
8
- Organize component styles by dimensions — states, sizes, variants — instead of flat props. Chain theme values, attach styled-components CSS, and get full TypeScript inference. Built-in pseudo-state handling, light/dark mode, and provider/consumer patterns for parent-child state propagation.
8
+ Organize component styles by dimensions — states, sizes, variants — instead of flat props. Chain theme values, attach CSS via the active connector, and get full TypeScript inference. Built-in pseudo-state handling, light/dark mode, and provider/consumer patterns for parent-child state propagation.
9
9
 
10
10
  ## Features
11
11
 
@@ -21,7 +21,7 @@ Organize component styles by dimensions — states, sizes, variants — instead
21
21
  ## Installation
22
22
 
23
23
  ```bash
24
- npm install @vitus-labs/rocketstyle @vitus-labs/core styled-components
24
+ npm install @vitus-labs/rocketstyle @vitus-labs/core
25
25
  ```
26
26
 
27
27
  ## Quick Start
@@ -110,7 +110,7 @@ Pseudo-state keys nest directly in the theme object:
110
110
 
111
111
  ### Styles Function
112
112
 
113
- The `.styles()` method defines the styled-components template that receives the computed theme:
113
+ The `.styles()` method defines the CSS template that receives the computed theme:
114
114
 
115
115
  ```tsx
116
116
  .styles((css) => css`
@@ -200,7 +200,7 @@ Button.states((theme, mode, css) => ({
200
200
 
201
201
  ### .styles(callback)
202
202
 
203
- Define the styled-components CSS template.
203
+ Define the CSS template using the active connector's `css` tagged template.
204
204
 
205
205
  ```tsx
206
206
  Button.styles((css) => css`
package/lib/index.d.ts CHANGED
@@ -35,6 +35,11 @@ declare const DEFAULT_DIMENSIONS: {
35
35
  readonly propName: "multiple";
36
36
  readonly multi: true;
37
37
  };
38
+ readonly modifiers: {
39
+ readonly propName: "modifier";
40
+ readonly multi: true;
41
+ readonly transform: true;
42
+ };
38
43
  };
39
44
  type DefaultDimensions = typeof DEFAULT_DIMENSIONS;
40
45
  //#endregion
@@ -82,7 +87,7 @@ type ExtractProps<TComponentOrTProps> = TComponentOrTProps extends ComponentType
82
87
  //#endregion
83
88
  //#region src/types/styles.d.ts
84
89
  interface StylesDefault {}
85
- type Styles<S> = S extends unknown ? StylesDefault : MergeTypes<[StylesDefault, S]>;
90
+ type Styles<S = unknown> = StylesDefault;
86
91
  type Css = typeof config.css;
87
92
  /**
88
93
  * Props available inside `.styles()` interpolation functions.
@@ -129,15 +134,17 @@ type ExtractDimensionMulti<T extends DimensionValue> = T extends DimensionValueO
129
134
  type DimensionValuePrimitive = string;
130
135
  type DimensionValueObj = {
131
136
  propName: string;
132
- multi?: boolean;
137
+ multi?: boolean; /** When true, this dimension is evaluated last and its values receive the accumulated theme as argument. */
138
+ transform?: boolean;
133
139
  };
134
140
  type DimensionValue = DimensionValuePrimitive | DimensionValueObj;
135
141
  type Dimensions = Record<string, DimensionValue>;
136
142
  type MultiKeys<T extends Dimensions = Dimensions> = Partial<Record<ExtractDimensionKey<T[keyof T]>, true>>;
137
- type DimensionResult<CT> = Record<string, boolean | null | Partial<CT>>;
138
- type DimensionObj<CT> = DimensionResult<CT>;
139
- type DimensionCb<T, CT> = (theme: T, mode: ThemeModeCallback, css: Css) => DimensionResult<CT>;
140
- type DimensionCallbackParam<T, CT> = DimensionObj<CT> | DimensionCb<T, CT>;
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>;
141
148
  type TDKP = Record<ExtractDimensionKey<Dimensions[keyof Dimensions]>, Record<string, boolean | never | Record<string, boolean>> | unknown>;
142
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] };
143
150
  type DimensionTypesHelper<DKP extends TDKP> = { [I in keyof DKP]: keyof DKP[I] };
@@ -180,6 +187,7 @@ type InitConfiguration<C, D> = {
180
187
  dimensionKeys: ArrayOfKeys<D>;
181
188
  dimensionValues: ArrayOfValues<D>;
182
189
  multiKeys: MultiKeys;
190
+ transformKeys: Partial<Record<string, true>>;
183
191
  };
184
192
  type Configuration<C = ElementType | unknown, D extends Dimensions = Dimensions> = InitConfiguration<C, D> & {
185
193
  provider?: boolean;
@@ -365,7 +373,7 @@ interface IRocketStyleComponent<OA extends TObj = {}, EA extends TObj = {}, T ex
365
373
  * }))
366
374
  * ```
367
375
  */
368
- theme: <P extends TObj = {}>(param: Partial<P> | ThemeCb<P, Theme<T>>) => RocketStyleComponent<OA, EA, T, MergeTypes<[CSS, P]>, S, HOC, D, UB, DKP>;
376
+ theme: <P extends TObj = {}>(param: Partial<P> | Partial<Styles<CSS>> | ThemeCb<P, Theme<T>>) => RocketStyleComponent<OA, EA, T, MergeTypes<[CSS, P]>, S, HOC, D, UB, DKP>;
369
377
  /**
370
378
  * A chaining method to define default rendered styles
371
379
  * @param param Callback of styled-components `css` function
package/lib/index.js CHANGED
@@ -85,6 +85,11 @@ const DEFAULT_DIMENSIONS = {
85
85
  multiple: {
86
86
  propName: "multiple",
87
87
  multi: true
88
+ },
89
+ modifiers: {
90
+ propName: "modifier",
91
+ multi: true,
92
+ transform: true
88
93
  }
89
94
  };
90
95
 
@@ -197,12 +202,18 @@ const useThemeAttrs = ({ inversed }) => {
197
202
  const { theme = {}, mode: ctxMode = "light", isDark: ctxDark } = useContext(context) || {};
198
203
  const mode = inversed ? THEME_MODES_INVERSED[ctxMode] : ctxMode;
199
204
  const isDark = inversed ? !ctxDark : ctxDark;
200
- return {
205
+ const isLight = !isDark;
206
+ return useMemo(() => ({
201
207
  theme,
202
208
  mode,
203
209
  isDark,
204
- isLight: !isDark
205
- };
210
+ isLight
211
+ }), [
212
+ theme,
213
+ mode,
214
+ isDark,
215
+ isLight
216
+ ]);
206
217
  };
207
218
 
208
219
  //#endregion
@@ -213,13 +224,16 @@ const useThemeAttrs = ({ inversed }) => {
213
224
  * to its rocketstyle children.
214
225
  */
215
226
  const context$1 = createContext({});
227
+ const EMPTY_CTX = { pseudo: {} };
216
228
  const useLocalContext = (consumer) => {
217
229
  const ctx = useContext(context$1);
218
- if (!consumer) return { pseudo: {} };
219
- return {
220
- pseudo: {},
221
- ...consumer((callback) => callback(ctx))
222
- };
230
+ return useMemo(() => {
231
+ if (!consumer) return EMPTY_CTX;
232
+ return {
233
+ pseudo: {},
234
+ ...consumer((callback) => callback(ctx))
235
+ };
236
+ }, [consumer, ctx]);
223
237
  };
224
238
  const LocalProvider = context$1.Provider;
225
239
 
@@ -259,14 +273,11 @@ const RocketStyleProviderComponent = (WrappedComponent) => forwardRef(({ onMouse
259
273
 
260
274
  //#endregion
261
275
  //#region src/utils/attrs.ts
262
- const removeUndefinedProps = (props) => Object.keys(props).reduce((acc, key) => {
263
- const currentValue = props[key];
264
- if (currentValue !== void 0) return {
265
- ...acc,
266
- [key]: currentValue
267
- };
268
- return acc;
269
- }, {});
276
+ const removeUndefinedProps = (props) => {
277
+ const result = {};
278
+ for (const key in props) if (props[key] !== void 0) result[key] = props[key];
279
+ return result;
280
+ };
270
281
  const pickStyledAttrs = (props, keywords) => Object.keys(props).reduce((acc, key) => {
271
282
  if (keywords[key] && props[key]) acc[key] = props[key];
272
283
  return acc;
@@ -280,23 +291,26 @@ const calculateStylingAttrs = ({ useBooleans, multiKeys }) => ({ props, dimensio
280
291
  const result = {};
281
292
  Object.keys(dimensions).forEach((item) => {
282
293
  const pickedProp = props[item];
283
- const valueTypes = ["number", "string"];
294
+ const t = typeof pickedProp;
284
295
  if (multiKeys?.[item] && Array.isArray(pickedProp)) result[item] = pickedProp;
285
- else if (valueTypes.includes(typeof pickedProp)) result[item] = pickedProp;
296
+ else if (t === "string" || t === "number") result[item] = pickedProp;
286
297
  else result[item] = void 0;
287
298
  });
288
299
  if (useBooleans) {
289
- const propsKeys = Object.keys(props).reverse();
300
+ const propsKeys = Object.keys(props);
290
301
  Object.entries(result).forEach(([key, value]) => {
291
302
  const isMultiKey = multiKeys[key];
292
303
  if (!value) {
293
304
  let newDimensionValue;
294
- const keywords = Object.keys(dimensions[key]);
295
- if (isMultiKey) newDimensionValue = propsKeys.filter((key) => keywords.includes(key));
296
- else newDimensionValue = propsKeys.find((key) => {
297
- if (keywords.includes(key) && props[key]) return key;
298
- return false;
299
- });
305
+ const keywordSet = new Set(Object.keys(dimensions[key]));
306
+ if (isMultiKey) newDimensionValue = propsKeys.filter((key) => keywordSet.has(key));
307
+ else for (let i = propsKeys.length - 1; i >= 0; i--) {
308
+ const k = propsKeys[i];
309
+ if (keywordSet.has(k) && props[k]) {
310
+ newDimensionValue = k;
311
+ break;
312
+ }
313
+ }
300
314
  result[key] = newDimensionValue;
301
315
  }
302
316
  });
@@ -396,6 +410,12 @@ const getMultipleDimensions = (obj) => getValues(obj).reduce((accumulator, value
396
410
  }
397
411
  return accumulator;
398
412
  }, {});
413
+ const getTransformDimensions = (obj) => getValues(obj).reduce((accumulator, value) => {
414
+ if (typeof value === "object") {
415
+ if (value.transform === true) accumulator[value.propName] = true;
416
+ }
417
+ return accumulator;
418
+ }, {});
399
419
 
400
420
  //#endregion
401
421
  //#region src/utils/statics.ts
@@ -424,12 +444,17 @@ const removeNullableValues = (obj) => Object.entries(obj).filter(([, v]) => v !=
424
444
 
425
445
  //#endregion
426
446
  //#region src/utils/theme.ts
447
+ const MODE_CALLBACK_BRAND = Symbol.for("vl.themeModeCallback");
427
448
  /** Creates a mode-switching function that returns the light or dark value based on the active mode. */
428
- const themeModeCallback = (light, dark) => (mode) => {
429
- if (!mode || mode === "light") return light;
430
- return dark;
449
+ const themeModeCallback = (light, dark) => {
450
+ const fn = (mode) => {
451
+ if (!mode || mode === "light") return light;
452
+ return dark;
453
+ };
454
+ fn.__brand = MODE_CALLBACK_BRAND;
455
+ return fn;
431
456
  };
432
- const isModeCallback = (value) => typeof value === "function" && value.toString() === themeModeCallback().toString();
457
+ const isModeCallback = (value) => typeof value === "function" && value.__brand === MODE_CALLBACK_BRAND;
433
458
  const getThemeFromChain = (options, theme) => {
434
459
  const result = {};
435
460
  if (!options || isEmpty(options)) return result;
@@ -445,15 +470,21 @@ const getDimensionThemes = (theme, options) => {
445
470
  return acc;
446
471
  }, result);
447
472
  };
448
- const getTheme = ({ rocketstate, themes, baseTheme }) => {
473
+ const getTheme = ({ rocketstate, themes, baseTheme, transformKeys, appTheme }) => {
449
474
  let finalTheme = { ...baseTheme };
475
+ const deferredTransforms = [];
450
476
  Object.entries(rocketstate).forEach(([key, value]) => {
451
477
  const keyTheme = themes[key];
452
- if (Array.isArray(value)) value.forEach((item) => {
453
- finalTheme = merge({}, finalTheme, keyTheme[item]);
454
- });
455
- else finalTheme = merge({}, finalTheme, keyTheme[value]);
478
+ const isTransform = transformKeys?.[key];
479
+ const mergeValue = (item) => {
480
+ const val = keyTheme[item];
481
+ if (isTransform && typeof val === "function") deferredTransforms.push(val);
482
+ else finalTheme = merge({}, finalTheme, val);
483
+ };
484
+ if (Array.isArray(value)) value.forEach(mergeValue);
485
+ else mergeValue(value);
456
486
  });
487
+ for (const transform of deferredTransforms) finalTheme = merge({}, finalTheme, transform(finalTheme, appTheme ?? {}, themeModeCallback, config.css));
457
488
  return finalTheme;
458
489
  };
459
490
  const getThemeByMode = (object, mode) => Object.keys(object).reduce((acc, key) => {
@@ -550,11 +581,24 @@ const rocketComponent = (options) => {
550
581
  ...rocketstate,
551
582
  pseudo: pseudoRocketstate
552
583
  };
553
- const rocketstyle = getTheme({
584
+ let rsKey = "";
585
+ for (const k in rocketstate) {
586
+ const v = rocketstate[k];
587
+ rsKey += `${k}=`;
588
+ rsKey += `${Array.isArray(v) ? v.join(",") : v}|`;
589
+ }
590
+ const rocketstyle = useMemo(() => getTheme({
554
591
  rocketstate,
555
592
  themes: currentModeThemes,
556
- baseTheme: currentModeBaseTheme
557
- });
593
+ baseTheme: currentModeBaseTheme,
594
+ transformKeys: options.transformKeys,
595
+ appTheme: theme
596
+ }), [
597
+ rsKey,
598
+ currentModeThemes,
599
+ currentModeBaseTheme,
600
+ theme
601
+ ]);
558
602
  const finalProps = {
559
603
  ...omit(mergeProps, [
560
604
  ...RESERVED_STYLING_PROPS_KEYS,
@@ -665,6 +709,7 @@ const rocketstyle = ({ dimensions = DEFAULT_DIMENSIONS, useBooleans = true } = {
665
709
  dimensionKeys: getKeys(dimensions),
666
710
  dimensionValues: getDimensionsValues(dimensions),
667
711
  multiKeys: getMultipleDimensions(dimensions),
712
+ transformKeys: getTransformDimensions(dimensions),
668
713
  styled: true
669
714
  });
670
715
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitus-labs/rocketstyle",
3
- "version": "2.0.0-alpha.9+f2f47db",
3
+ "version": "2.0.0-beta.0",
4
4
  "license": "MIT",
5
5
  "author": "Vit Bokisch <vit@bokisch.cz>",
6
6
  "maintainers": [
@@ -14,6 +14,7 @@
14
14
  "types": "./lib/index.d.ts"
15
15
  },
16
16
  "types": "./lib/index.d.ts",
17
+ "main": "./lib/index.js",
17
18
  "files": [
18
19
  "lib",
19
20
  "!lib/**/*.map",
@@ -60,19 +61,20 @@
60
61
  "test:coverage": "vitest run --coverage",
61
62
  "test:watch": "vitest",
62
63
  "cover": "coveralls < .coverage/lcov.info",
63
- "typecheck": "tsc --noEmit"
64
+ "typecheck": "tsc --noEmit",
65
+ "version": "node ../../scripts/sync-peer-deps.mjs"
64
66
  },
65
67
  "peerDependencies": {
66
- "@vitus-labs/core": "^1.4.0",
68
+ "@vitus-labs/core": "2.0.0-alpha.27",
67
69
  "react": ">= 19"
68
70
  },
69
71
  "devDependencies": {
70
- "@vitus-labs/core": "2.0.0-alpha.9+f2f47db",
71
- "@vitus-labs/elements": "2.0.0-alpha.9+f2f47db",
72
- "@vitus-labs/tools-rolldown": "^1.6.0",
73
- "@vitus-labs/tools-storybook": "^1.6.0",
74
- "@vitus-labs/tools-typescript": "^1.6.0",
75
- "@vitus-labs/unistyle": "2.0.0-alpha.9+f2f47db"
72
+ "@vitus-labs/core": "2.0.0-beta.0",
73
+ "@vitus-labs/elements": "2.0.0-beta.0",
74
+ "@vitus-labs/tools-rolldown": "1.10.0",
75
+ "@vitus-labs/tools-storybook": "1.10.0",
76
+ "@vitus-labs/tools-typescript": "1.10.0",
77
+ "@vitus-labs/unistyle": "2.0.0-beta.0"
76
78
  },
77
- "gitHead": "f2f47db887d6a846ee5f9e96f290c59cdde4772a"
79
+ "gitHead": "dd8b9f356086ecd8bfb69c87fcad1e8bfa9ab1f4"
78
80
  }