@pyreon/attrs 0.15.0 → 0.18.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/lib/index.d.ts CHANGED
@@ -42,8 +42,32 @@ type Spread<A extends readonly [...any]> = A extends [infer L, ...infer R] ? Spr
42
42
  /** Recursively checks whether any element in the tuple is `any`. */
43
43
  type _HasAny<A> = A extends [infer L, ...infer R] ? (0 extends 1 & L ? true : _HasAny<R>) : false;
44
44
  type MergeTypes<A extends readonly [...any]> = _HasAny<A> extends true ? any : ExtractNullableKeys<Spread<A>>;
45
- /** Extracts the props type from a Pyreon component function. */
46
- type ExtractProps<TComponentOrTProps> = TComponentOrTProps extends ComponentFn<infer TProps> ? TProps : TComponentOrTProps;
45
+ /**
46
+ * Extracts the props type from a Pyreon component function — or passes
47
+ * through the input unchanged when it's already a props type.
48
+ *
49
+ * Multi-overload aware: matches up to 4 call signatures and produces the
50
+ * UNION of their first-argument types. Iterator / List / Element are
51
+ * 3-overload primitives — without overload-aware extraction, wrapping
52
+ * them through `attrs()` silently downgraded their public prop surface
53
+ * to the LAST (loosest) overload. Mirrors vitus-labs PR #222.
54
+ *
55
+ * See `@pyreon/rocketstyle` `ExtractProps` for the canonical shape — kept
56
+ * in sync across the 4 copies in core / elements / attrs / rocketstyle.
57
+ */
58
+ type ExtractProps<TComponentOrTProps> = TComponentOrTProps extends {
59
+ (props: infer P1, ...args: any): any;
60
+ (props: infer P2, ...args: any): any;
61
+ (props: infer P3, ...args: any): any;
62
+ (props: infer P4, ...args: any): any;
63
+ } ? P1 | P2 | P3 | P4 : TComponentOrTProps extends {
64
+ (props: infer P1, ...args: any): any;
65
+ (props: infer P2, ...args: any): any;
66
+ (props: infer P3, ...args: any): any;
67
+ } ? P1 | P2 | P3 : TComponentOrTProps extends {
68
+ (props: infer P1, ...args: any): any;
69
+ (props: infer P2, ...args: any): any;
70
+ } ? P1 | P2 : TComponentOrTProps extends ComponentFn<infer TProps> ? TProps : TComponentOrTProps;
47
71
  //#endregion
48
72
  //#region src/types/config.d.ts
49
73
  /** A component that has been enhanced by attrs — identified by the `IS_ATTRS` marker. */
package/lib/index.js CHANGED
@@ -29,14 +29,18 @@ const calculateChainOptions = (options) => (args) => {
29
29
  const createAttrsHOC = ({ attrs, priorityAttrs }) => {
30
30
  const calculateAttrs = calculateChainOptions(attrs);
31
31
  const calculatePriorityAttrs = calculateChainOptions(priorityAttrs);
32
+ const hasAttrs = (attrs?.length ?? 0) > 0;
33
+ const hasPriorityAttrs = (priorityAttrs?.length ?? 0) > 0;
34
+ const hasAnyChain = hasAttrs || hasPriorityAttrs;
32
35
  const attrsHoc = (WrappedComponent) => {
33
36
  const HOCComponent = (props) => {
34
37
  const filteredProps = removeUndefinedProps(props);
35
- const prioritizedAttrs = calculatePriorityAttrs([filteredProps]);
36
- const finalAttrs = calculateAttrs([{
38
+ if (!hasAnyChain) return WrappedComponent(filteredProps);
39
+ const prioritizedAttrs = hasPriorityAttrs ? calculatePriorityAttrs([filteredProps]) : null;
40
+ const finalAttrs = hasAttrs ? calculateAttrs([prioritizedAttrs ? {
37
41
  ...prioritizedAttrs,
38
42
  ...filteredProps
39
- }]);
43
+ } : filteredProps]) : null;
40
44
  return WrappedComponent({
41
45
  ...prioritizedAttrs,
42
46
  ...finalAttrs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/attrs",
3
- "version": "0.15.0",
3
+ "version": "0.18.0",
4
4
  "description": "Attrs HOC chaining for Pyreon components",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -41,14 +41,14 @@
41
41
  "typecheck": "tsc --noEmit"
42
42
  },
43
43
  "devDependencies": {
44
- "@pyreon/typescript": "^0.15.0",
44
+ "@pyreon/typescript": "^0.18.0",
45
45
  "@vitus-labs/tools-rolldown": "^2.3.0"
46
46
  },
47
- "peerDependencies": {
48
- "@pyreon/core": "^0.15.0",
49
- "@pyreon/ui-core": "^0.15.0"
50
- },
51
47
  "engines": {
52
48
  "node": ">= 22"
49
+ },
50
+ "dependencies": {
51
+ "@pyreon/core": "^0.18.0",
52
+ "@pyreon/ui-core": "^0.18.0"
53
53
  }
54
54
  }
@@ -25,21 +25,35 @@ const createAttrsHOC: AttrsStyleHOC = ({ attrs, priorityAttrs }) => {
25
25
  // Pre-build the chain reducers once (not per render).
26
26
  const calculateAttrs = calculateChainOptions(attrs)
27
27
  const calculatePriorityAttrs = calculateChainOptions(priorityAttrs)
28
+ // Most components never call .attrs() — short-circuit the merge work below
29
+ // so the no-chain mount path skips 2 reducer invocations + 3 object spreads.
30
+ // Mirrors vitus-labs's attrsHoc fast-path; React-side memoization
31
+ // (useMemo / useStableValue) is omitted because Pyreon components run once
32
+ // per mount, not on every render.
33
+ const hasAttrs = (attrs?.length ?? 0) > 0
34
+ const hasPriorityAttrs = (priorityAttrs?.length ?? 0) > 0
35
+ const hasAnyChain = hasAttrs || hasPriorityAttrs
28
36
 
29
37
  const attrsHoc = (WrappedComponent: ComponentFn<any>) => {
30
38
  const HOCComponent: ComponentFn<any> = (props) => {
31
39
  // Strip undefined values so they don't shadow defaults from attrs callbacks.
32
40
  const filteredProps = removeUndefinedProps(props)
33
41
 
42
+ // Fast path: no attrs configured — skip reducers + spreads entirely.
43
+ if (!hasAnyChain) return WrappedComponent(filteredProps)
44
+
34
45
  // 1. Resolve priority attrs (lowest precedence defaults).
35
- const prioritizedAttrs = calculatePriorityAttrs([filteredProps])
46
+ const prioritizedAttrs = hasPriorityAttrs
47
+ ? calculatePriorityAttrs([filteredProps])
48
+ : null
36
49
  // 2. Resolve normal attrs — these see priority + explicit props as input.
37
- const finalAttrs = calculateAttrs([
38
- {
39
- ...prioritizedAttrs,
40
- ...filteredProps,
41
- },
42
- ])
50
+ const finalAttrs = hasAttrs
51
+ ? calculateAttrs([
52
+ prioritizedAttrs
53
+ ? { ...prioritizedAttrs, ...filteredProps }
54
+ : filteredProps,
55
+ ])
56
+ : null
43
57
 
44
58
  // 3. Merge: priority < normal attrs < explicit props (last wins).
45
59
  const finalProps = {
@@ -101,6 +101,37 @@ export type MergeTypes<A extends readonly [...any]> =
101
101
 
102
102
  // ─── ExtractProps ─────────────────────────────────────────────
103
103
 
104
- /** Extracts the props type from a Pyreon component function. */
105
- export type ExtractProps<TComponentOrTProps> =
106
- TComponentOrTProps extends ComponentFn<infer TProps> ? TProps : TComponentOrTProps
104
+ /**
105
+ * Extracts the props type from a Pyreon component function — or passes
106
+ * through the input unchanged when it's already a props type.
107
+ *
108
+ * Multi-overload aware: matches up to 4 call signatures and produces the
109
+ * UNION of their first-argument types. Iterator / List / Element are
110
+ * 3-overload primitives — without overload-aware extraction, wrapping
111
+ * them through `attrs()` silently downgraded their public prop surface
112
+ * to the LAST (loosest) overload. Mirrors vitus-labs PR #222.
113
+ *
114
+ * See `@pyreon/rocketstyle` `ExtractProps` for the canonical shape — kept
115
+ * in sync across the 4 copies in core / elements / attrs / rocketstyle.
116
+ */
117
+ export type ExtractProps<TComponentOrTProps> = TComponentOrTProps extends {
118
+ (props: infer P1, ...args: any): any
119
+ (props: infer P2, ...args: any): any
120
+ (props: infer P3, ...args: any): any
121
+ (props: infer P4, ...args: any): any
122
+ }
123
+ ? P1 | P2 | P3 | P4
124
+ : TComponentOrTProps extends {
125
+ (props: infer P1, ...args: any): any
126
+ (props: infer P2, ...args: any): any
127
+ (props: infer P3, ...args: any): any
128
+ }
129
+ ? P1 | P2 | P3
130
+ : TComponentOrTProps extends {
131
+ (props: infer P1, ...args: any): any
132
+ (props: infer P2, ...args: any): any
133
+ }
134
+ ? P1 | P2
135
+ : TComponentOrTProps extends ComponentFn<infer TProps>
136
+ ? TProps
137
+ : TComponentOrTProps