@pyreon/attrs 0.11.1 → 0.11.3

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.
@@ -0,0 +1,241 @@
1
+ import { calculateChainOptions, removeUndefinedProps } from "../utils/attrs"
2
+ import { chainOptions } from "../utils/chaining"
3
+ import { removeNullableValues } from "../utils/collection"
4
+ import { calculateHocsFuncs } from "../utils/compose"
5
+ import { createStaticsEnhancers } from "../utils/statics"
6
+
7
+ // --------------------------------------------------------
8
+ // removeUndefinedProps
9
+ // --------------------------------------------------------
10
+ describe("removeUndefinedProps", () => {
11
+ it("should remove properties with undefined values", () => {
12
+ const result = removeUndefinedProps({
13
+ a: 1,
14
+ b: undefined,
15
+ c: "hello",
16
+ })
17
+ expect(result).toEqual({ a: 1, c: "hello" })
18
+ })
19
+
20
+ it("should keep null values", () => {
21
+ const result = removeUndefinedProps({ a: null, b: undefined })
22
+ expect(result).toEqual({ a: null })
23
+ })
24
+
25
+ it("should keep false values", () => {
26
+ const result = removeUndefinedProps({ a: false, b: undefined })
27
+ expect(result).toEqual({ a: false })
28
+ })
29
+
30
+ it("should keep zero values", () => {
31
+ const result = removeUndefinedProps({ a: 0, b: undefined })
32
+ expect(result).toEqual({ a: 0 })
33
+ })
34
+
35
+ it("should keep empty string values", () => {
36
+ const result = removeUndefinedProps({ a: "", b: undefined })
37
+ expect(result).toEqual({ a: "" })
38
+ })
39
+
40
+ it("should return empty object when all values are undefined", () => {
41
+ const result = removeUndefinedProps({ a: undefined, b: undefined })
42
+ expect(result).toEqual({})
43
+ })
44
+
45
+ it("should return all props when none are undefined", () => {
46
+ const input = { a: 1, b: "test", c: true }
47
+ const result = removeUndefinedProps(input)
48
+ expect(result).toEqual(input)
49
+ })
50
+
51
+ it("should handle empty object", () => {
52
+ const result = removeUndefinedProps({})
53
+ expect(result).toEqual({})
54
+ })
55
+ })
56
+
57
+ // --------------------------------------------------------
58
+ // removeNullableValues
59
+ // --------------------------------------------------------
60
+ describe("removeNullableValues", () => {
61
+ it("should remove null values", () => {
62
+ const result = removeNullableValues({ a: 1, b: null })
63
+ expect(result).toEqual({ a: 1 })
64
+ })
65
+
66
+ it("should remove undefined values", () => {
67
+ const result = removeNullableValues({ a: 1, b: undefined })
68
+ expect(result).toEqual({ a: 1 })
69
+ })
70
+
71
+ it("should remove false values", () => {
72
+ const result = removeNullableValues({ a: 1, b: false })
73
+ expect(result).toEqual({ a: 1 })
74
+ })
75
+
76
+ it("should keep zero values", () => {
77
+ const result = removeNullableValues({ a: 0, b: null })
78
+ expect(result).toEqual({ a: 0 })
79
+ })
80
+
81
+ it("should keep empty string values", () => {
82
+ const result = removeNullableValues({ a: "", b: null })
83
+ expect(result).toEqual({ a: "" })
84
+ })
85
+
86
+ it("should keep truthy values", () => {
87
+ const result = removeNullableValues({ a: 1, b: "test", c: true })
88
+ expect(result).toEqual({ a: 1, b: "test", c: true })
89
+ })
90
+
91
+ it("should handle empty object", () => {
92
+ const result = removeNullableValues({})
93
+ expect(result).toEqual({})
94
+ })
95
+ })
96
+
97
+ // --------------------------------------------------------
98
+ // calculateChainOptions
99
+ // --------------------------------------------------------
100
+ describe("calculateChainOptions", () => {
101
+ it("should return empty object when no options provided", () => {
102
+ const calculate = calculateChainOptions(undefined)
103
+ const result = calculate([{}])
104
+ expect(result).toEqual({})
105
+ })
106
+
107
+ it("should return empty object for empty options array", () => {
108
+ const calculate = calculateChainOptions([])
109
+ const result = calculate([{}])
110
+ expect(result).toEqual({})
111
+ })
112
+
113
+ it("should execute a single option function", () => {
114
+ const fn = (props: any) => ({
115
+ color: props.variant === "primary" ? "blue" : "gray",
116
+ })
117
+ const calculate = calculateChainOptions([fn])
118
+ const result = calculate([{ variant: "primary" }])
119
+ expect(result).toEqual({ color: "blue" })
120
+ })
121
+
122
+ it("should merge results from multiple option functions", () => {
123
+ const fn1 = (_: any) => ({ color: "blue" })
124
+ const fn2 = (_: any) => ({ size: "large" })
125
+ const calculate = calculateChainOptions([fn1, fn2])
126
+ const result = calculate([{}])
127
+ expect(result).toEqual({ color: "blue", size: "large" })
128
+ })
129
+
130
+ it("should let later functions override earlier ones", () => {
131
+ const fn1 = (_: any) => ({ color: "blue" })
132
+ const fn2 = (_: any) => ({ color: "red" })
133
+ const calculate = calculateChainOptions([fn1, fn2])
134
+ const result = calculate([{}])
135
+ expect(result).toEqual({ color: "red" })
136
+ })
137
+
138
+ it("should pass arguments to each option function", () => {
139
+ const fn = vi.fn((_: any) => ({}))
140
+ const calculate = calculateChainOptions([fn])
141
+ const props = { variant: "primary" }
142
+ calculate([props])
143
+ expect(fn).toHaveBeenCalledWith(props)
144
+ })
145
+ })
146
+
147
+ // --------------------------------------------------------
148
+ // chainOptions
149
+ // --------------------------------------------------------
150
+ describe("chainOptions", () => {
151
+ it("should return default options when opts is undefined", () => {
152
+ const defaults = [() => ({})]
153
+ const result = chainOptions(undefined, defaults)
154
+ expect(result).toEqual(defaults)
155
+ })
156
+
157
+ it("should append function to defaults", () => {
158
+ const fn1 = () => ({ a: 1 })
159
+ const fn2 = () => ({ b: 2 })
160
+ const result = chainOptions(fn2, [fn1])
161
+ expect(result).toHaveLength(2)
162
+ expect(result[0]).toBe(fn1)
163
+ expect(result[1]).toBe(fn2)
164
+ })
165
+
166
+ it("should wrap object in a function and append", () => {
167
+ const obj = { color: "blue" }
168
+ const result = chainOptions(obj, [])
169
+ expect(result).toHaveLength(1)
170
+ expect(result[0]?.()).toEqual(obj)
171
+ })
172
+
173
+ it("should return empty array when no defaults and undefined opts", () => {
174
+ const result = chainOptions(undefined, [])
175
+ expect(result).toEqual([])
176
+ })
177
+
178
+ it("should not mutate the defaults array", () => {
179
+ const defaults = [() => ({})]
180
+ const fn = () => ({ a: 1 })
181
+ const result = chainOptions(fn, defaults)
182
+ expect(defaults).toHaveLength(1)
183
+ expect(result).toHaveLength(2)
184
+ })
185
+ })
186
+
187
+ // --------------------------------------------------------
188
+ // createStaticsEnhancers
189
+ // --------------------------------------------------------
190
+ describe("createStaticsEnhancers", () => {
191
+ it("should assign options to context", () => {
192
+ const context: Record<string, any> = {}
193
+ createStaticsEnhancers({
194
+ context,
195
+ options: { theme: "dark", variant: "primary" },
196
+ })
197
+ expect(context).toEqual({ theme: "dark", variant: "primary" })
198
+ })
199
+
200
+ it("should not modify context when options is empty", () => {
201
+ const context: Record<string, any> = { existing: true }
202
+ createStaticsEnhancers({ context, options: {} })
203
+ expect(context).toEqual({ existing: true })
204
+ })
205
+
206
+ it("should merge with existing context properties", () => {
207
+ const context: Record<string, any> = { existing: true }
208
+ createStaticsEnhancers({ context, options: { newProp: "value" } })
209
+ expect(context).toEqual({ existing: true, newProp: "value" })
210
+ })
211
+ })
212
+
213
+ // --------------------------------------------------------
214
+ // calculateHocsFuncs
215
+ // --------------------------------------------------------
216
+ describe("calculateHocsFuncs", () => {
217
+ it("should return empty array for empty options", () => {
218
+ const result = calculateHocsFuncs({})
219
+ expect(result).toEqual([])
220
+ })
221
+
222
+ it("should filter out non-function values", () => {
223
+ const fn = (x: any) => x
224
+ const result = calculateHocsFuncs({ a: fn, b: "string", c: 123 })
225
+ expect(result).toHaveLength(1)
226
+ expect(result[0]).toBe(fn)
227
+ })
228
+
229
+ it("should reverse the order of functions", () => {
230
+ const fn1 = (x: any) => x
231
+ const fn2 = (x: any) => x
232
+ const result = calculateHocsFuncs({ a: fn1, b: fn2 })
233
+ expect(result[0]).toBe(fn2)
234
+ expect(result[1]).toBe(fn1)
235
+ })
236
+
237
+ it("should return empty array for undefined input", () => {
238
+ const result = calculateHocsFuncs(undefined as any)
239
+ expect(result).toEqual([])
240
+ })
241
+ })
package/src/attrs.ts ADDED
@@ -0,0 +1,126 @@
1
+ import { compose, hoistNonReactStatics, omit, pick } from "@pyreon/ui-core"
2
+ import { attrsHoc } from "./hoc"
3
+ import type { AttrsComponent as AttrsComponentType } from "./types/AttrsComponent"
4
+ import type { Configuration, ExtendedConfiguration } from "./types/configuration"
5
+ import type { InitAttrsComponent } from "./types/InitAttrsComponent"
6
+ import { calculateChainOptions } from "./utils/attrs"
7
+ import { chainOptions } from "./utils/chaining"
8
+ import { calculateHocsFuncs } from "./utils/compose"
9
+ import { createStaticsEnhancers } from "./utils/statics"
10
+
11
+ /**
12
+ * Clones the current configuration and merges new options, then creates a
13
+ * fresh component. This makes the chaining API immutable — each `.attrs()`
14
+ * / `.config()` / `.statics()` call returns a brand-new component with an
15
+ * updated configuration rather than mutating the existing one.
16
+ */
17
+ type CloneAndEnhance = (
18
+ defaultOpts: Configuration,
19
+ opts: Partial<ExtendedConfiguration>,
20
+ ) => ReturnType<typeof attrsComponent>
21
+
22
+ const cloneAndEnhance: CloneAndEnhance = (defaultOpts, opts) =>
23
+ attrsComponent({
24
+ ...defaultOpts,
25
+ ...(opts.name ? { name: opts.name } : undefined),
26
+ ...(opts.component ? { component: opts.component } : undefined),
27
+ attrs: chainOptions(opts.attrs, defaultOpts.attrs),
28
+ filterAttrs: [...(defaultOpts.filterAttrs ?? []), ...(opts.filterAttrs ?? [])],
29
+ priorityAttrs: chainOptions(opts.priorityAttrs, defaultOpts.priorityAttrs),
30
+ statics: { ...defaultOpts.statics, ...opts.statics },
31
+ compose: { ...defaultOpts.compose, ...opts.compose },
32
+ } as Parameters<typeof attrsComponent>[0])
33
+
34
+ /**
35
+ * Core factory that builds an attrs-enhanced Pyreon component.
36
+ *
37
+ * Creates a plain ComponentFn that:
38
+ * 1. Wraps the original with attrsHoc (default props) + user HOCs from `.compose()`.
39
+ * 2. Filters out internal props listed in `filterAttrs`.
40
+ * 3. Attaches `data-attrs` attribute in development for debugging.
41
+ *
42
+ * Then adds chaining methods (`.attrs()`, `.config()`, `.compose()`, `.statics()`)
43
+ * as static properties — each calls `cloneAndEnhance` to produce a new component.
44
+ *
45
+ * In Pyreon, there is no forwardRef — ref flows as a normal prop.
46
+ * Components are plain functions that run once per mount.
47
+ */
48
+ const attrsComponent: InitAttrsComponent = (options) => {
49
+ const componentName = options.name ?? options.component.displayName ?? options.component.name
50
+
51
+ const RenderComponent = options.component
52
+
53
+ // Build the HOC chain: attrsHoc is always first (resolves default props),
54
+ // followed by user-composed HOCs in reverse order (outermost wraps first).
55
+ const hocsFuncs = [attrsHoc(options), ...calculateHocsFuncs(options.compose)]
56
+
57
+ // The inner component receives already-computed props from the HOC chain.
58
+ // It handles prop filtering and final rendering.
59
+ const EnhancedComponent = (props: Record<string, any>) => {
60
+ const needsFiltering = options.filterAttrs && options.filterAttrs.length > 0
61
+
62
+ const filteredProps = needsFiltering ? omit(props, options.filterAttrs) : props
63
+
64
+ const finalProps =
65
+ process.env.NODE_ENV !== "production"
66
+ ? { ...filteredProps, "data-attrs": componentName }
67
+ : filteredProps
68
+
69
+ return RenderComponent(finalProps)
70
+ }
71
+
72
+ // Apply the full HOC chain: compose(attrsHoc, ...userHocs)(EnhancedComponent)
73
+ const AttrsComponent: AttrsComponentType = compose(...hocsFuncs)(EnhancedComponent)
74
+
75
+ AttrsComponent.IS_ATTRS = true
76
+ AttrsComponent.displayName = componentName
77
+ AttrsComponent.meta = {}
78
+
79
+ // Copy static properties from the original component.
80
+ hoistNonReactStatics(AttrsComponent, options.component)
81
+
82
+ // Populate `component.meta` with user-defined statics from `.statics()`.
83
+ createStaticsEnhancers({
84
+ context: AttrsComponent.meta,
85
+ options: options.statics,
86
+ })
87
+
88
+ // ─── Chaining Methods ──────────────────────────────────
89
+ // Each method creates a new component via cloneAndEnhance.
90
+ // The original component is never mutated.
91
+ Object.assign(AttrsComponent, {
92
+ attrs: (attrs: any, { priority, filter }: any = {}) => {
93
+ const result: Record<string, any> = {}
94
+
95
+ if (filter) {
96
+ result.filterAttrs = filter
97
+ }
98
+
99
+ if (priority) {
100
+ result.priorityAttrs = attrs as ExtendedConfiguration["priorityAttrs"]
101
+
102
+ return cloneAndEnhance(options, result)
103
+ }
104
+
105
+ result.attrs = attrs as ExtendedConfiguration["attrs"]
106
+
107
+ return cloneAndEnhance(options, result)
108
+ },
109
+
110
+ config: (opts: any = {}) => {
111
+ const result = pick(opts)
112
+
113
+ return cloneAndEnhance(options, result)
114
+ },
115
+
116
+ compose: (opts: any) => cloneAndEnhance(options, { compose: opts }),
117
+
118
+ statics: (opts: any) => cloneAndEnhance(options, { statics: opts }),
119
+
120
+ getDefaultAttrs: (props: any) => calculateChainOptions(options.attrs)([props]),
121
+ })
122
+
123
+ return AttrsComponent
124
+ }
125
+
126
+ export default attrsComponent
@@ -0,0 +1,60 @@
1
+ import type { Configuration } from "../types/configuration"
2
+ import type { ComponentFn } from "../types/utils"
3
+ import { calculateChainOptions, removeUndefinedProps } from "../utils/attrs"
4
+
5
+ export type AttrsStyleHOC = ({
6
+ attrs,
7
+ priorityAttrs,
8
+ }: Pick<Configuration, "attrs" | "priorityAttrs">) => (
9
+ WrappedComponent: ComponentFn<any>,
10
+ ) => ComponentFn<any>
11
+
12
+ /**
13
+ * Creates the core HOC that computes default props from the `.attrs()` chain.
14
+ *
15
+ * This is always the outermost HOC in the compose chain, so it runs first.
16
+ * It resolves both priority and normal attrs callbacks, then merges them
17
+ * with the consumer's explicit props following this precedence:
18
+ *
19
+ * priorityAttrs < normalAttrs < explicit props (last wins)
20
+ *
21
+ * In Pyreon, components are plain functions — no forwardRef needed.
22
+ * The ref flows as a normal prop through the chain.
23
+ */
24
+ const createAttrsHOC: AttrsStyleHOC = ({ attrs, priorityAttrs }) => {
25
+ // Pre-build the chain reducers once (not per render).
26
+ const calculateAttrs = calculateChainOptions(attrs)
27
+ const calculatePriorityAttrs = calculateChainOptions(priorityAttrs)
28
+
29
+ const attrsHoc = (WrappedComponent: ComponentFn<any>) => {
30
+ const HOCComponent: ComponentFn<any> = (props) => {
31
+ // Strip undefined values so they don't shadow defaults from attrs callbacks.
32
+ const filteredProps = removeUndefinedProps(props)
33
+
34
+ // 1. Resolve priority attrs (lowest precedence defaults).
35
+ const prioritizedAttrs = calculatePriorityAttrs([filteredProps])
36
+ // 2. Resolve normal attrs — these see priority + explicit props as input.
37
+ const finalAttrs = calculateAttrs([
38
+ {
39
+ ...prioritizedAttrs,
40
+ ...filteredProps,
41
+ },
42
+ ])
43
+
44
+ // 3. Merge: priority < normal attrs < explicit props (last wins).
45
+ const finalProps = {
46
+ ...prioritizedAttrs,
47
+ ...finalAttrs,
48
+ ...filteredProps,
49
+ }
50
+
51
+ return WrappedComponent(finalProps)
52
+ }
53
+
54
+ return HOCComponent
55
+ }
56
+
57
+ return attrsHoc
58
+ }
59
+
60
+ export default createAttrsHOC
@@ -0,0 +1,3 @@
1
+ import attrsHoc from "./attrsHoc"
2
+
3
+ export { attrsHoc }
package/src/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ import type { Attrs } from "./init"
2
+ import attrs from "./init"
3
+ import type { IsAttrsComponent } from "./isAttrsComponent"
4
+ import isAttrsComponent from "./isAttrsComponent"
5
+ import type { AttrsComponent } from "./types/AttrsComponent"
6
+ import type { AttrsCb } from "./types/attrs"
7
+ import type { AttrsComponentType, ConfigAttrs } from "./types/config"
8
+ import type { ComposeParam, GenericHoc } from "./types/hoc"
9
+ import type { ComponentFn, ElementType, TObj } from "./types/utils"
10
+
11
+ export type {
12
+ Attrs,
13
+ AttrsCb,
14
+ AttrsComponent,
15
+ AttrsComponentType,
16
+ ComponentFn,
17
+ ComposeParam,
18
+ ConfigAttrs,
19
+ ElementType,
20
+ GenericHoc,
21
+ IsAttrsComponent,
22
+ TObj,
23
+ }
24
+
25
+ export { attrs, isAttrsComponent }
26
+ export default attrs
package/src/init.ts ADDED
@@ -0,0 +1,59 @@
1
+ import { isEmpty } from "@pyreon/ui-core"
2
+ import attrsComponent from "./attrs"
3
+ import type { InitAttrsComponent } from "./types/InitAttrsComponent"
4
+ import type { ElementType } from "./types/utils"
5
+
6
+ /**
7
+ * Public entry point for creating an attrs-enhanced component.
8
+ *
9
+ * ```tsx
10
+ * const Button = attrs({ name: 'Button', component: Element })
11
+ * .attrs({ tag: 'button' })
12
+ * .attrs<{ primary?: boolean }>(({ primary }) => ({
13
+ * backgroundColor: primary ? 'blue' : 'gray',
14
+ * }))
15
+ * ```
16
+ */
17
+ export type Attrs = <C extends ElementType>({
18
+ name,
19
+ component,
20
+ }: {
21
+ name: string
22
+ component: C
23
+ }) => ReturnType<InitAttrsComponent<C>>
24
+
25
+ const attrs: Attrs = ({ name, component }) => {
26
+ // Validate required params in development — fail fast with clear errors.
27
+ if (process.env.NODE_ENV !== "production") {
28
+ type Errors = Partial<{
29
+ component: string
30
+ name: string
31
+ }>
32
+
33
+ const errors: Errors = {}
34
+ if (!component) {
35
+ errors.component = "Parameter `component` is missing in params!"
36
+ }
37
+
38
+ if (!name) {
39
+ errors.name = "Parameter `name` is missing in params!"
40
+ }
41
+
42
+ if (!isEmpty(errors)) {
43
+ throw Error(JSON.stringify(errors))
44
+ }
45
+ }
46
+
47
+ // Bootstrap with empty configuration — all chains start from scratch.
48
+ return attrsComponent({
49
+ name,
50
+ component,
51
+ attrs: [],
52
+ priorityAttrs: [],
53
+ filterAttrs: [],
54
+ compose: {},
55
+ statics: {},
56
+ })
57
+ }
58
+
59
+ export default attrs
@@ -0,0 +1,16 @@
1
+ export type IsAttrsComponent = <T>(component: T) => boolean
2
+
3
+ /** Runtime type guard — checks if a component was created by `attrs()`. */
4
+ const isAttrsComponent: IsAttrsComponent = (component) => {
5
+ if (
6
+ component &&
7
+ (typeof component === "object" || typeof component === "function") &&
8
+ Object.hasOwn(component as object, "IS_ATTRS")
9
+ ) {
10
+ return true
11
+ }
12
+
13
+ return false
14
+ }
15
+
16
+ export default isAttrsComponent
@@ -0,0 +1,83 @@
1
+ import type { VNode } from "@pyreon/core"
2
+ import type { AttrsCb } from "./attrs"
3
+ import type { ConfigAttrs } from "./config"
4
+ import type { ComposeParam } from "./hoc"
5
+ import type { ElementType, ExtractProps, MergeTypes, TObj } from "./utils"
6
+
7
+ /**
8
+ * Props passed to the inner enhanced component.
9
+ * In Pyreon there's no forwardRef — ref flows as a normal prop.
10
+ */
11
+ export type InnerComponentProps = {
12
+ "data-attrs"?: string | undefined
13
+ }
14
+
15
+ /**
16
+ * @param OA Origin component props params.
17
+ * @param EA Extended prop types
18
+ * @param S Defined statics
19
+ * @param HOC High-order components
20
+ * @param DFP Calculated final component props
21
+ */
22
+ export interface AttrsComponent<
23
+ C extends ElementType = ElementType,
24
+ // original component props
25
+ OA extends TObj = {},
26
+ // extended component props
27
+ EA extends TObj = {},
28
+ // statics
29
+ S extends TObj = {},
30
+ // hocs
31
+ HOC extends TObj = {},
32
+ // calculated final props
33
+ DFP extends Record<string, any> = MergeTypes<[OA, EA]>,
34
+ > {
35
+ // The component is callable — Pyreon components are plain functions
36
+ (props: DFP): VNode | null
37
+
38
+ // CONFIG chaining method
39
+ config: <NC extends ElementType | unknown = unknown>({
40
+ name,
41
+ component: NC,
42
+ DEBUG,
43
+ }: ConfigAttrs<NC>) => NC extends ElementType
44
+ ? AttrsComponent<NC, ExtractProps<NC>, EA, S, HOC>
45
+ : AttrsComponent<C, OA, EA, S, HOC>
46
+
47
+ // ATTRS chaining method
48
+ attrs: <P extends TObj | unknown = unknown>(
49
+ param: P extends TObj ? Partial<DFP & P> | AttrsCb<DFP & P> : Partial<DFP> | AttrsCb<DFP>,
50
+ config?: Partial<{
51
+ priority: boolean
52
+ filter: unknown extends P ? string[] : (keyof (EA & P))[]
53
+ }>,
54
+ ) => P extends TObj
55
+ ? AttrsComponent<C, OA, MergeTypes<[EA, P]>, S, HOC>
56
+ : AttrsComponent<C, OA, EA, S, HOC>
57
+
58
+ // COMPOSE chaining method
59
+ compose: <P extends ComposeParam>(
60
+ param: P,
61
+ ) => P extends TObj
62
+ ? AttrsComponent<C, OA, EA, S, MergeTypes<[HOC, P]>>
63
+ : AttrsComponent<C, OA, EA, S, HOC>
64
+
65
+ // STATICS chaining method
66
+ statics: <P extends TObj | unknown = unknown>(
67
+ param: P,
68
+ ) => P extends TObj
69
+ ? AttrsComponent<C, OA, EA, MergeTypes<[S, P]>, HOC>
70
+ : AttrsComponent<C, OA, EA, S, HOC>
71
+
72
+ /** Access to all defined statics on the component. */
73
+ meta: S
74
+
75
+ getDefaultAttrs: (props: TObj) => TObj
76
+
77
+ readonly $$originTypes: OA
78
+ readonly $$extendedTypes: EA
79
+ readonly $$types: DFP
80
+
81
+ IS_ATTRS: true
82
+ displayName: string
83
+ }
@@ -0,0 +1,19 @@
1
+ import type { AttrsComponent } from "./AttrsComponent"
2
+ import type { Configuration } from "./configuration"
3
+ import type { ElementType, ExtractProps } from "./utils"
4
+
5
+ /**
6
+ * Type of the internal `attrsComponent` factory function.
7
+ * Takes a full Configuration and returns an AttrsComponent whose
8
+ * original props (OA) are extracted from the component type C,
9
+ * with all extension slots (EA, S, HOC) starting empty.
10
+ */
11
+ export type InitAttrsComponent<C extends ElementType = ElementType> = (
12
+ params: Configuration<C>,
13
+ ) => AttrsComponent<
14
+ C,
15
+ ExtractProps<C>, // OA — original component props
16
+ {}, // EA — extended props (empty initially)
17
+ {}, // S — statics (empty initially)
18
+ {} // HOC — composed HOCs (empty initially)
19
+ >
@@ -0,0 +1,2 @@
1
+ /** Callback form of `.attrs()` — receives current props and returns partial overrides. */
2
+ export type AttrsCb<A> = (props: Partial<A>) => Partial<A>
@@ -0,0 +1,13 @@
1
+ import type { ElementType } from "./utils"
2
+
3
+ /** A component that has been enhanced by attrs — identified by the `IS_ATTRS` marker. */
4
+ export type AttrsComponentType = ElementType & {
5
+ IS_ATTRS: true
6
+ }
7
+
8
+ /** Parameters accepted by the `.config()` chaining method. */
9
+ export type ConfigAttrs<C extends ElementType | unknown> = Partial<{
10
+ name: string
11
+ component: C
12
+ DEBUG: boolean
13
+ }>