@vitus-labs/rocketstyle 2.6.2 → 2.7.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.
Files changed (2) hide show
  1. package/lib/index.js +149 -110
  2. package/package.json +11 -6
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Provider as Provider$1, compose, config, context, get, hoistNonReactStatics, isEmpty, merge, omit, pick, render, set, useStableValue } from "@vitus-labs/core";
2
- import { createContext, useCallback, useContext, useImperativeHandle, useMemo, useRef, useState } from "react";
2
+ import { createContext, memo, useContext, useImperativeHandle, useMemo, useRef, useState } from "react";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
 
5
5
  //#region src/constants/index.ts
@@ -125,36 +125,59 @@ var ThemeManager = class {
125
125
  * Tracks hover, focus, and pressed pseudo-states via mouse and focus
126
126
  * event handlers. Returns the current state flags and wrapped event
127
127
  * callbacks that preserve any user-provided handlers.
128
+ *
129
+ * Consumer handlers are captured in a ref so the wrapped event callbacks
130
+ * keep stable identity across re-renders — otherwise inline arrow
131
+ * handlers (`onClick={() => …}`) would re-create the wrappers every
132
+ * render and defeat downstream memoization.
128
133
  */
129
134
  const usePseudoState = ({ onBlur, onFocus, onMouseDown, onMouseEnter, onMouseLeave, onMouseUp }) => {
130
135
  const [hover, setHover] = useState(false);
131
136
  const [focus, setFocus] = useState(false);
132
137
  const [pressed, setPressed] = useState(false);
133
- const handleOnMouseEnter = useCallback((e) => {
134
- setHover(true);
135
- if (onMouseEnter) onMouseEnter(e);
136
- }, [onMouseEnter]);
137
- const handleOnMouseLeave = useCallback((e) => {
138
- setHover(false);
139
- setPressed(false);
140
- if (onMouseLeave) onMouseLeave(e);
141
- }, [onMouseLeave]);
142
- const handleOnMouseDown = useCallback((e) => {
143
- setPressed(true);
144
- if (onMouseDown) onMouseDown(e);
145
- }, [onMouseDown]);
146
- const handleOnMouseUp = useCallback((e) => {
147
- setPressed(false);
148
- if (onMouseUp) onMouseUp(e);
149
- }, [onMouseUp]);
150
- const handleOnFocus = useCallback((e) => {
151
- setFocus(true);
152
- if (onFocus) onFocus(e);
153
- }, [onFocus]);
154
- const handleOnBlur = useCallback((e) => {
155
- setFocus(false);
156
- if (onBlur) onBlur(e);
157
- }, [onBlur]);
138
+ const latest = useRef({
139
+ onBlur,
140
+ onFocus,
141
+ onMouseDown,
142
+ onMouseEnter,
143
+ onMouseLeave,
144
+ onMouseUp
145
+ });
146
+ latest.current = {
147
+ onBlur,
148
+ onFocus,
149
+ onMouseDown,
150
+ onMouseEnter,
151
+ onMouseLeave,
152
+ onMouseUp
153
+ };
154
+ const events = useMemo(() => ({
155
+ onMouseEnter: (e) => {
156
+ setHover(true);
157
+ latest.current.onMouseEnter?.(e);
158
+ },
159
+ onMouseLeave: (e) => {
160
+ setHover(false);
161
+ setPressed(false);
162
+ latest.current.onMouseLeave?.(e);
163
+ },
164
+ onMouseDown: (e) => {
165
+ setPressed(true);
166
+ latest.current.onMouseDown?.(e);
167
+ },
168
+ onMouseUp: (e) => {
169
+ setPressed(false);
170
+ latest.current.onMouseUp?.(e);
171
+ },
172
+ onFocus: (e) => {
173
+ setFocus(true);
174
+ latest.current.onFocus?.(e);
175
+ },
176
+ onBlur: (e) => {
177
+ setFocus(false);
178
+ latest.current.onBlur?.(e);
179
+ }
180
+ }), []);
158
181
  return {
159
182
  state: useMemo(() => ({
160
183
  hover,
@@ -165,21 +188,7 @@ const usePseudoState = ({ onBlur, onFocus, onMouseDown, onMouseEnter, onMouseLea
165
188
  focus,
166
189
  pressed
167
190
  ]),
168
- events: useMemo(() => ({
169
- onMouseEnter: handleOnMouseEnter,
170
- onMouseLeave: handleOnMouseLeave,
171
- onMouseDown: handleOnMouseDown,
172
- onMouseUp: handleOnMouseUp,
173
- onFocus: handleOnFocus,
174
- onBlur: handleOnBlur
175
- }), [
176
- handleOnMouseEnter,
177
- handleOnMouseLeave,
178
- handleOnMouseDown,
179
- handleOnMouseUp,
180
- handleOnFocus,
181
- handleOnBlur
182
- ])
191
+ events
183
192
  };
184
193
  };
185
194
 
@@ -192,8 +201,8 @@ const usePseudoState = ({ onBlur, onFocus, onMouseDown, onMouseEnter, onMouseLea
192
201
  */
193
202
  const useRocketstyleRef = ({ $rocketstyleRef, ref }) => {
194
203
  const internalRef = useRef(null);
195
- useImperativeHandle($rocketstyleRef, () => internalRef.current);
196
- useImperativeHandle(ref, () => internalRef.current);
204
+ useImperativeHandle($rocketstyleRef, () => internalRef.current, []);
205
+ useImperativeHandle(ref, () => internalRef.current, []);
197
206
  return internalRef;
198
207
  };
199
208
 
@@ -208,18 +217,12 @@ const useThemeAttrs = ({ inversed }) => {
208
217
  const { theme = {}, mode: ctxMode = "light", isDark: ctxDark } = useContext(context) || {};
209
218
  const mode = inversed ? THEME_MODES_INVERSED[ctxMode] : ctxMode;
210
219
  const isDark = inversed ? !ctxDark : ctxDark;
211
- const isLight = !isDark;
212
- return useMemo(() => ({
213
- theme,
214
- mode,
215
- isDark,
216
- isLight
217
- }), [
220
+ return {
218
221
  theme,
219
222
  mode,
220
223
  isDark,
221
- isLight
222
- ]);
224
+ isLight: !isDark
225
+ };
223
226
  };
224
227
 
225
228
  //#endregion
@@ -293,37 +296,34 @@ const pickStyledAttrs = (props, keywords) => {
293
296
  return result;
294
297
  };
295
298
  const calculateChainOptions = (options) => (args) => {
296
- const result = {};
297
- if (isEmpty(options)) return result;
299
+ if (isEmpty(options)) return {};
298
300
  return options.reduce((acc, item) => Object.assign(acc, item(...args)), {});
299
301
  };
300
302
  const calculateStylingAttrs = ({ useBooleans, multiKeys }) => ({ props, dimensions }) => {
301
303
  const result = {};
302
- Object.keys(dimensions).forEach((item) => {
304
+ for (const item in dimensions) {
303
305
  const pickedProp = props[item];
304
306
  const t = typeof pickedProp;
305
307
  if (multiKeys?.[item] && Array.isArray(pickedProp)) result[item] = pickedProp;
306
308
  else if (t === "string" || t === "number") result[item] = pickedProp;
307
309
  else result[item] = void 0;
308
- });
310
+ }
309
311
  if (useBooleans) {
310
312
  const propsKeys = Object.keys(props);
311
- Object.entries(result).forEach(([key, value]) => {
312
- const isMultiKey = multiKeys[key];
313
- if (!value) {
314
- let newDimensionValue;
315
- const keywordSet = new Set(Object.keys(dimensions[key]));
316
- if (isMultiKey) newDimensionValue = propsKeys.filter((key) => keywordSet.has(key));
317
- else for (let i = propsKeys.length - 1; i >= 0; i--) {
318
- const k = propsKeys[i];
319
- if (keywordSet.has(k) && props[k]) {
320
- newDimensionValue = k;
321
- break;
322
- }
313
+ for (const key in result) if (!result[key]) {
314
+ const isMultiKey = multiKeys?.[key];
315
+ let newDimensionValue;
316
+ const dimObj = dimensions[key];
317
+ if (isMultiKey) newDimensionValue = propsKeys.filter((k) => Object.hasOwn(dimObj, k));
318
+ else for (let i = propsKeys.length - 1; i >= 0; i--) {
319
+ const k = propsKeys[i];
320
+ if (Object.hasOwn(dimObj, k) && props[k]) {
321
+ newDimensionValue = k;
322
+ break;
323
323
  }
324
- result[key] = newDimensionValue;
325
324
  }
326
- });
325
+ result[key] = newDimensionValue;
326
+ }
327
327
  }
328
328
  return result;
329
329
  };
@@ -333,33 +333,66 @@ const calculateStylingAttrs = ({ useBooleans, multiKeys }) => ({ props, dimensio
333
333
  /**
334
334
  * HOC that resolves the `.attrs()` chain before the inner component renders.
335
335
  * Evaluates both regular and priority attrs callbacks with the current theme
336
- * and mode, then merges the results with explicit props (priority attrs
337
- * are applied first, regular attrs can be overridden by direct props).
336
+ * and mode, then merges the results with explicit props (priority attrs are
337
+ * applied first, regular attrs can be overridden by direct props).
338
+ *
339
+ * Fast path: when no chain is configured (the common case for rocketstyle
340
+ * components built with .theme()/dimensions only), skip the deep-equal
341
+ * stabilization + memo dance and forward props directly. Mirrors the
342
+ * pattern in @vitus-labs/attrs' attrsHoc.
338
343
  */
339
344
  const rocketStyleHOC = ({ inversed, attrs, priorityAttrs }) => {
340
345
  const calculateAttrs = calculateChainOptions(attrs);
341
346
  const calculatePriorityAttrs = calculateChainOptions(priorityAttrs);
347
+ const hasAttrs = (attrs?.length ?? 0) > 0;
348
+ const hasPriorityAttrs = (priorityAttrs?.length ?? 0) > 0;
349
+ if (!(hasAttrs || hasPriorityAttrs)) {
350
+ const Enhanced = (WrappedComponent) => {
351
+ const HOC = ({ ref, ...props }) => {
352
+ useThemeAttrs({ inversed });
353
+ return /* @__PURE__ */ jsx(WrappedComponent, {
354
+ $rocketstyleRef: ref,
355
+ ...props
356
+ });
357
+ };
358
+ return HOC;
359
+ };
360
+ return Enhanced;
361
+ }
342
362
  const Enhanced = (WrappedComponent) => {
343
363
  const HOC = ({ ref, ...props }) => {
344
364
  const { theme, mode, isDark, isLight } = useThemeAttrs({ inversed });
345
- const callbackParams = [theme, {
365
+ const stableProps = useStableValue(props);
366
+ const filteredProps = useMemo(() => removeUndefinedProps(stableProps), [stableProps]);
367
+ const themeBag = useMemo(() => ({
346
368
  render,
347
369
  mode,
348
370
  isDark,
349
371
  isLight
350
- }];
351
- const filteredProps = removeUndefinedProps(props);
352
- const prioritizedAttrs = calculatePriorityAttrs([filteredProps, ...callbackParams]);
353
- const finalAttrs = calculateAttrs([{
354
- ...prioritizedAttrs,
355
- ...filteredProps
356
- }, ...callbackParams]);
357
- return /* @__PURE__ */ jsx(WrappedComponent, {
358
- $rocketstyleRef: ref,
359
- ...prioritizedAttrs,
360
- ...finalAttrs,
361
- ...filteredProps
362
- });
372
+ }), [
373
+ mode,
374
+ isDark,
375
+ isLight
376
+ ]);
377
+ return /* @__PURE__ */ jsx(WrappedComponent, { ...useMemo(() => {
378
+ const callbackParams = [theme, themeBag];
379
+ const prioritizedAttrs = hasPriorityAttrs ? calculatePriorityAttrs([filteredProps, ...callbackParams]) : null;
380
+ const finalAttrs = hasAttrs ? calculateAttrs([prioritizedAttrs ? {
381
+ ...prioritizedAttrs,
382
+ ...filteredProps
383
+ } : filteredProps, ...callbackParams]) : null;
384
+ return {
385
+ $rocketstyleRef: ref,
386
+ ...prioritizedAttrs,
387
+ ...finalAttrs,
388
+ ...filteredProps
389
+ };
390
+ }, [
391
+ filteredProps,
392
+ ref,
393
+ theme,
394
+ themeBag
395
+ ]) });
363
396
  };
364
397
  return HOC;
365
398
  };
@@ -374,14 +407,22 @@ const chainOptions = (opts, defaultOpts = []) => {
374
407
  else if (typeof opts === "object") result.push(() => opts);
375
408
  return result;
376
409
  };
377
- const chainOrOptions = (keys, opts, defaultOpts) => keys.reduce((acc, item) => ({
378
- ...acc,
379
- [item]: opts[item] || defaultOpts[item]
380
- }), {});
381
- const chainReservedKeyOptions = (keys, opts, defaultOpts) => keys.reduce((acc, item) => ({
382
- ...acc,
383
- [item]: chainOptions(opts[item], defaultOpts[item])
384
- }), {});
410
+ const chainOrOptions = (keys, opts, defaultOpts) => {
411
+ const result = {};
412
+ for (let i = 0; i < keys.length; i++) {
413
+ const item = keys[i];
414
+ result[item] = opts[item] || defaultOpts[item];
415
+ }
416
+ return result;
417
+ };
418
+ const chainReservedKeyOptions = (keys, opts, defaultOpts) => {
419
+ const result = {};
420
+ for (let i = 0; i < keys.length; i++) {
421
+ const item = keys[i];
422
+ result[item] = chainOptions(opts[item], defaultOpts[item]);
423
+ }
424
+ return result;
425
+ };
385
426
 
386
427
  //#endregion
387
428
  //#region src/utils/compose.ts
@@ -450,10 +491,11 @@ const calculateStyles = (styles) => {
450
491
 
451
492
  //#endregion
452
493
  //#region src/utils/collection.ts
453
- const removeNullableValues = (obj) => Object.entries(obj).filter(([, v]) => v != null && v !== false).reduce((acc, [k, v]) => ({
454
- ...acc,
455
- [k]: v
456
- }), {});
494
+ const removeNullableValues = (obj) => {
495
+ const result = {};
496
+ for (const [k, v] of Object.entries(obj)) if (v != null && v !== false) result[k] = v;
497
+ return result;
498
+ };
457
499
 
458
500
  //#endregion
459
501
  //#region src/utils/theme.ts
@@ -625,6 +667,11 @@ const rocketComponent = (options) => {
625
667
  useBooleans: options.useBooleans
626
668
  }), [themes]);
627
669
  const RESERVED_STYLING_PROPS_KEYS = useMemo(() => Object.keys(reservedPropNames), [reservedPropNames]);
670
+ const omitKeysSet = useMemo(() => new Set([
671
+ ...RESERVED_STYLING_PROPS_KEYS,
672
+ ...PSEUDO_KEYS,
673
+ ...options.filterAttrs
674
+ ]), [RESERVED_STYLING_PROPS_KEYS]);
628
675
  const { pseudo, ...mergeProps } = {
629
676
  ...localCtx,
630
677
  ...props
@@ -657,11 +704,7 @@ const rocketComponent = (options) => {
657
704
  theme
658
705
  ]);
659
706
  const finalProps = {
660
- ...omit(mergeProps, [
661
- ...RESERVED_STYLING_PROPS_KEYS,
662
- ...PSEUDO_KEYS,
663
- ...options.filterAttrs
664
- ]),
707
+ ...omit(mergeProps, omitKeysSet),
665
708
  ...options.passProps ? pick(mergeProps, options.passProps) : {},
666
709
  ref: ref ?? $rocketstyleRef ? internalRef : void 0,
667
710
  $rocketstyle: rocketstyle,
@@ -684,7 +727,7 @@ const rocketComponent = (options) => {
684
727
  }
685
728
  return /* @__PURE__ */ jsx(RenderComponent, { ...finalProps });
686
729
  };
687
- const RocketComponent = compose(...hocsFuncs)(EnhancedComponent);
730
+ const RocketComponent = compose(...hocsFuncs)(memo(EnhancedComponent));
688
731
  RocketComponent.IS_ROCKETSTYLE = true;
689
732
  RocketComponent.displayName = componentName;
690
733
  hoistNonReactStatics(RocketComponent, options.component);
@@ -694,8 +737,6 @@ const rocketComponent = (options) => {
694
737
  func: cloneAndEnhance,
695
738
  options
696
739
  });
697
- RocketComponent.IS_ROCKETSTYLE = true;
698
- RocketComponent.displayName = componentName;
699
740
  RocketComponent.meta = {};
700
741
  createStaticsEnhancers({
701
742
  context: RocketComponent.meta,
@@ -744,16 +785,14 @@ const rocketComponent = (options) => {
744
785
 
745
786
  //#endregion
746
787
  //#region src/init.ts
788
+ const RESERVED_KEYS_SET = new Set(ALL_RESERVED_KEYS);
747
789
  const validateInit = (name, component, dimensions) => {
748
790
  const errors = {};
749
791
  if (!component) errors.component = "Parameter `component` is missing in params!";
750
792
  if (!name) errors.name = "Parameter `name` is missing in params!";
751
793
  if (isEmpty(dimensions)) errors.dimensions = "Parameter `dimensions` is missing in params!";
752
- else {
753
- const definedDimensions = getKeys(dimensions);
754
- 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
794
+ else if (getKeys(dimensions).some((d) => RESERVED_KEYS_SET.has(d))) errors.invalidDimensions = `Some of your \`dimensions\` is invalid and uses reserved static keys which are
755
795
  ${DEFAULT_DIMENSIONS.toString()}`;
756
- }
757
796
  if (!isEmpty(errors)) throw Error(JSON.stringify(errors));
758
797
  };
759
798
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitus-labs/rocketstyle",
3
- "version": "2.6.2",
3
+ "version": "2.7.0",
4
4
  "license": "MIT",
5
5
  "author": "Vit Bokisch <vit@bokisch.cz>",
6
6
  "maintainers": [
@@ -56,6 +56,7 @@
56
56
  "prepublish": "bun run build",
57
57
  "build": "bun run vl_rolldown_build",
58
58
  "build:watch": "bun run vl_rolldown_build-watch",
59
+ "bench": "bun benchmarks/render-bench.tsx",
59
60
  "lint": "biome check src/",
60
61
  "test": "vitest run",
61
62
  "test:coverage": "vitest run --coverage",
@@ -64,15 +65,19 @@
64
65
  "typecheck": "tsc --noEmit"
65
66
  },
66
67
  "peerDependencies": {
67
- "@vitus-labs/core": "^2.6.2",
68
+ "@vitus-labs/core": "^2.7.0",
68
69
  "react": ">= 19"
69
70
  },
70
71
  "devDependencies": {
72
+ "@vitus-labs/connector-styler": "workspace:*",
71
73
  "@vitus-labs/core": "workspace:*",
72
74
  "@vitus-labs/elements": "workspace:*",
73
- "@vitus-labs/tools-rolldown": "2.3.1",
74
- "@vitus-labs/tools-storybook": "2.3.1",
75
- "@vitus-labs/tools-typescript": "2.3.1",
76
- "@vitus-labs/unistyle": "workspace:*"
75
+ "@vitus-labs/styler": "workspace:*",
76
+ "@vitus-labs/tools-rolldown": "2.5.0",
77
+ "@vitus-labs/tools-storybook": "2.5.0",
78
+ "@vitus-labs/tools-typescript": "2.5.0",
79
+ "@vitus-labs/unistyle": "workspace:*",
80
+ "jsdom": "^29.1.1",
81
+ "tinybench": "^6.0.1"
77
82
  }
78
83
  }