@pyreon/attrs 0.11.1 → 0.11.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/package.json +6 -5
- package/src/__tests__/attrs.test.ts +488 -0
- package/src/__tests__/attrsHoc.test.ts +136 -0
- package/src/__tests__/utils.test.ts +241 -0
- package/src/attrs.ts +126 -0
- package/src/hoc/attrsHoc.ts +60 -0
- package/src/hoc/index.ts +3 -0
- package/src/index.ts +26 -0
- package/src/init.ts +59 -0
- package/src/isAttrsComponent.ts +16 -0
- package/src/types/AttrsComponent.ts +83 -0
- package/src/types/InitAttrsComponent.ts +19 -0
- package/src/types/attrs.ts +2 -0
- package/src/types/config.ts +13 -0
- package/src/types/configuration.ts +40 -0
- package/src/types/hoc.ts +10 -0
- package/src/types/utils.ts +106 -0
- package/src/utils/attrs.ts +40 -0
- package/src/utils/chaining.ts +21 -0
- package/src/utils/collection.ts +14 -0
- package/src/utils/compose.ts +14 -0
- package/src/utils/statics.ts +16 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ElementType, TFn } from "./utils"
|
|
2
|
+
|
|
3
|
+
export type OptionFunc = (...arg: unknown[]) => Record<string, unknown>
|
|
4
|
+
|
|
5
|
+
export type InitConfiguration<C> = {
|
|
6
|
+
name?: string | undefined
|
|
7
|
+
component: C
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Internal configuration accumulated across the chaining API.
|
|
12
|
+
* Arrays hold the full chain — each `.attrs()` call appends to these.
|
|
13
|
+
*/
|
|
14
|
+
export type Configuration<C = ElementType | unknown> = InitConfiguration<C> & {
|
|
15
|
+
DEBUG?: boolean | undefined
|
|
16
|
+
/** Chain of default-props callbacks (resolved in order, later wins). */
|
|
17
|
+
attrs: OptionFunc[]
|
|
18
|
+
/** Chain of priority-props callbacks (resolved before `attrs`, can be overridden by both). */
|
|
19
|
+
priorityAttrs: OptionFunc[]
|
|
20
|
+
/** Prop names to omit before passing to the underlying component. */
|
|
21
|
+
filterAttrs: string[]
|
|
22
|
+
/** Named HOCs — set to null/false to remove from chain. */
|
|
23
|
+
compose: Record<string, TFn | null | undefined | false>
|
|
24
|
+
/** Metadata accessible via `Component.meta`. */
|
|
25
|
+
statics: Record<string, any>
|
|
26
|
+
} & Record<string, any>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Single-item variant of Configuration — represents what a single
|
|
30
|
+
* `.attrs()` / `.config()` call contributes (one function, not an array).
|
|
31
|
+
* Used by `cloneAndEnhance` to merge into the accumulated Configuration.
|
|
32
|
+
*/
|
|
33
|
+
export type ExtendedConfiguration<C = ElementType | unknown> = InitConfiguration<C> & {
|
|
34
|
+
DEBUG?: boolean | undefined
|
|
35
|
+
attrs: OptionFunc
|
|
36
|
+
priorityAttrs: OptionFunc
|
|
37
|
+
filterAttrs: string[]
|
|
38
|
+
compose: Record<string, TFn | null | undefined | false>
|
|
39
|
+
statics: Record<string, any>
|
|
40
|
+
} & Record<string, any>
|
package/src/types/hoc.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ElementType } from "./utils"
|
|
2
|
+
|
|
3
|
+
export type GenericHoc = (component: ElementType) => ElementType
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parameters for `.compose()` — a record of named HOCs.
|
|
7
|
+
* Setting a key to `null`, `undefined`, or `false` removes a
|
|
8
|
+
* previously defined HOC from the chain.
|
|
9
|
+
*/
|
|
10
|
+
export type ComposeParam = Record<string, GenericHoc | null | undefined | false>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { VNodeChild } from "@pyreon/core"
|
|
2
|
+
|
|
3
|
+
// ─── Base Types ───────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export type TObj = Record<string, unknown>
|
|
6
|
+
export type TFn = (...args: any) => any
|
|
7
|
+
export type CallBackParam = TObj | TFn
|
|
8
|
+
export type DisplayName = string
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A Pyreon component function that accepts additional static properties.
|
|
12
|
+
* Aligned with @pyreon/core's ComponentFn — returns VNodeChild (not just VNode | null)
|
|
13
|
+
* to support components returning strings, numbers, undefined, etc.
|
|
14
|
+
*/
|
|
15
|
+
export type ComponentFn<P = any> = ((props: P) => VNodeChild) & Partial<Record<string, any>>
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* An element type — either a Pyreon component function or an intrinsic tag string.
|
|
19
|
+
*/
|
|
20
|
+
export type ElementType<T extends TObj | unknown = any> = ComponentFn<T>
|
|
21
|
+
|
|
22
|
+
export type ValueOf<T> = T[keyof T]
|
|
23
|
+
|
|
24
|
+
export type ArrayOfValues<T> = T[keyof T]
|
|
25
|
+
|
|
26
|
+
export type ArrayOfKeys<T> = keyof T[]
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A HOC that wraps a component and merges additional props `P`
|
|
30
|
+
* with the wrapped component's own props.
|
|
31
|
+
*/
|
|
32
|
+
export type SimpleHoc<P extends Record<string, unknown> = Record<string, unknown>> = <
|
|
33
|
+
T extends ComponentFn<any>,
|
|
34
|
+
>(
|
|
35
|
+
WrappedComponent: T,
|
|
36
|
+
) => ComponentFn<MergeTypes<[P, ExtractProps<T>]>>
|
|
37
|
+
|
|
38
|
+
/** Maps each key to `never` if the value is null, undefined, or false. */
|
|
39
|
+
type IsFalseOrNullable<T> = T extends null | undefined | false ? never : true
|
|
40
|
+
export type NullableKeys<T> = { [K in keyof T]: IsFalseOrNullable<T[K]> }
|
|
41
|
+
|
|
42
|
+
/** Unwraps a callback to its return type, or returns the object as-is. */
|
|
43
|
+
export type ReturnCbParam<P extends TFn | TObj> = P extends TFn ? ReturnType<P> : P
|
|
44
|
+
|
|
45
|
+
// ─── MergeTypes ───────────────────────────────────────────────
|
|
46
|
+
//
|
|
47
|
+
// Merges a tuple of object types left-to-right (like Object.assign),
|
|
48
|
+
// then strips keys whose values resolved to `never`, `null`, or `undefined`.
|
|
49
|
+
//
|
|
50
|
+
// Usage: MergeTypes<[BaseProps, ExtendedProps, OverrideProps]>
|
|
51
|
+
//
|
|
52
|
+
// This is the backbone of the chaining API — each `.attrs<P>()`
|
|
53
|
+
// call produces MergeTypes<[PreviousProps, P]> so later definitions
|
|
54
|
+
// override earlier ones while preserving the rest.
|
|
55
|
+
// ──────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Forces TypeScript to expand/flatten a type for better IDE display.
|
|
59
|
+
* Short-circuits for `any` — the mapped type would turn `any` into
|
|
60
|
+
* `{ [x: string]: any; [x: number]: any; [x: symbol]: any }`, losing
|
|
61
|
+
* its identity as `any` and breaking downstream type checks.
|
|
62
|
+
*/
|
|
63
|
+
type Id<T> = 0 extends 1 & T ? T : T extends infer U ? { [K in keyof U]: U[K] } : never
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Strips keys whose values are `never`, `null`, or `undefined`.
|
|
67
|
+
* Uses tuple wrapping `[T[P]] extends [never]` to avoid distribution
|
|
68
|
+
* over union types (a bare `T[P] extends never` would incorrectly
|
|
69
|
+
* match union members).
|
|
70
|
+
*
|
|
71
|
+
* Short-circuits for `any` — the `as` clause in mapped types loses
|
|
72
|
+
* index signatures, which would turn `any` into an empty type.
|
|
73
|
+
*/
|
|
74
|
+
type IsAny<T> = 0 extends 1 & T ? true : false
|
|
75
|
+
|
|
76
|
+
type ExtractNullableKeys<T> = 0 extends 1 & T
|
|
77
|
+
? T
|
|
78
|
+
: {
|
|
79
|
+
[P in keyof T as IsAny<T[P]> extends true
|
|
80
|
+
? P
|
|
81
|
+
: [T[P]] extends [never]
|
|
82
|
+
? never
|
|
83
|
+
: [T[P]] extends [null | undefined]
|
|
84
|
+
? never
|
|
85
|
+
: P]: T[P]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Merges two types: keeps all keys from L that don't exist in R, then adds all of R. */
|
|
89
|
+
type SpreadTwo<L, R> = Id<Pick<L, Exclude<keyof L, keyof R>> & R>
|
|
90
|
+
|
|
91
|
+
/** Recursively spreads a tuple of types left-to-right. */
|
|
92
|
+
type Spread<A extends readonly [...any]> = A extends [infer L, ...infer R]
|
|
93
|
+
? SpreadTwo<L, Spread<R>>
|
|
94
|
+
: unknown
|
|
95
|
+
|
|
96
|
+
/** Recursively checks whether any element in the tuple is `any`. */
|
|
97
|
+
type _HasAny<A> = A extends [infer L, ...infer R] ? (0 extends 1 & L ? true : _HasAny<R>) : false
|
|
98
|
+
|
|
99
|
+
export type MergeTypes<A extends readonly [...any]> =
|
|
100
|
+
_HasAny<A> extends true ? any : ExtractNullableKeys<Spread<A>>
|
|
101
|
+
|
|
102
|
+
// ─── ExtractProps ─────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/** Extracts the props type from a Pyreon component function. */
|
|
105
|
+
export type ExtractProps<TComponentOrTProps> =
|
|
106
|
+
TComponentOrTProps extends ComponentFn<infer TProps> ? TProps : TComponentOrTProps
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isEmpty } from "@pyreon/ui-core"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Strips keys with `undefined` values from a props object.
|
|
5
|
+
* This prevents undefined consumer props from overriding defaults
|
|
6
|
+
* computed by `.attrs()` callbacks. Only explicitly set values
|
|
7
|
+
* (including `null`) should override defaults.
|
|
8
|
+
*/
|
|
9
|
+
type RemoveUndefinedProps = <T extends Record<string, any>>(
|
|
10
|
+
props: T,
|
|
11
|
+
) => { [I in keyof T as T[I] extends undefined ? never : I]: T[I] }
|
|
12
|
+
|
|
13
|
+
export const removeUndefinedProps = (<T extends Record<string, any>>(props: T) =>
|
|
14
|
+
Object.keys(props).reduce<Record<string, unknown>>((acc, key) => {
|
|
15
|
+
const currentValue = props[key]
|
|
16
|
+
if (currentValue !== undefined) acc[key] = currentValue
|
|
17
|
+
return acc
|
|
18
|
+
}, {})) as RemoveUndefinedProps
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Reduces an array of option functions (from chained `.attrs()` calls)
|
|
22
|
+
* into a single merged result. Each function is called with `args`
|
|
23
|
+
* (typically the current props) and its return value is merged
|
|
24
|
+
* left-to-right via Object.assign — so later `.attrs()` calls
|
|
25
|
+
* override earlier ones.
|
|
26
|
+
*
|
|
27
|
+
* Returns a curried function: first call binds the chain, second
|
|
28
|
+
* call provides the arguments and executes the reduction.
|
|
29
|
+
*/
|
|
30
|
+
type OptionFunc<A> = (...arg: A[]) => Record<string, unknown>
|
|
31
|
+
type CalculateChainOptions = <A>(
|
|
32
|
+
options?: OptionFunc<A>[],
|
|
33
|
+
) => (args: A[]) => ReturnType<OptionFunc<A>>
|
|
34
|
+
|
|
35
|
+
export const calculateChainOptions: CalculateChainOptions = (options) => (args) => {
|
|
36
|
+
const result = {}
|
|
37
|
+
if (!options || isEmpty(options)) return result
|
|
38
|
+
|
|
39
|
+
return options.reduce((acc, item) => Object.assign(acc, item(...args)), {})
|
|
40
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type Func = (...args: any) => Record<string, unknown>
|
|
2
|
+
type Obj = Record<string, unknown>
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Appends a new attrs option to the existing chain of option functions.
|
|
6
|
+
*
|
|
7
|
+
* The `.attrs()` API accepts either an object or a callback. This function
|
|
8
|
+
* normalizes both forms into a function (objects are wrapped in `() => obj`)
|
|
9
|
+
* and appends to the existing array. The array is cloned so each chained
|
|
10
|
+
* component gets its own copy — maintaining immutability across the chain.
|
|
11
|
+
*/
|
|
12
|
+
type ChainOptions = (opts: Obj | Func | undefined, defaultOpts: Func[]) => Func[]
|
|
13
|
+
|
|
14
|
+
export const chainOptions: ChainOptions = (opts, defaultOpts = []) => {
|
|
15
|
+
const result = [...defaultOpts]
|
|
16
|
+
|
|
17
|
+
if (typeof opts === "function") result.push(opts)
|
|
18
|
+
else if (typeof opts === "object") result.push(() => opts)
|
|
19
|
+
|
|
20
|
+
return result
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters out keys with `null`, `undefined`, or `false` values.
|
|
3
|
+
* Used to clean compose config — setting a HOC to `false`/`null` removes it.
|
|
4
|
+
*/
|
|
5
|
+
type RemoveNullableValues = (obj: Record<string, any>) => Record<string, any>
|
|
6
|
+
export const removeNullableValues: RemoveNullableValues = (obj) => {
|
|
7
|
+
const result: Record<string, any> = {}
|
|
8
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
9
|
+
if (v != null && v !== false) {
|
|
10
|
+
result[k] = v
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return result
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts HOC functions from the `.compose()` configuration and reverses
|
|
3
|
+
* them for correct application order. Setting a key to `null`, `undefined`,
|
|
4
|
+
* or `false` removes a previously defined HOC — only actual functions are kept.
|
|
5
|
+
*
|
|
6
|
+
* The reversal is needed because `compose(a, b, c)(Component)` applies as
|
|
7
|
+
* `a(b(c(Component)))`, so the last-defined HOC should wrap innermost.
|
|
8
|
+
*/
|
|
9
|
+
type CalculateHocsFuncs = (options: Record<string, any>) => ((arg: any) => any)[]
|
|
10
|
+
|
|
11
|
+
export const calculateHocsFuncs: CalculateHocsFuncs = (options = {}) =>
|
|
12
|
+
Object.values(options)
|
|
13
|
+
.filter((item) => typeof item === "function")
|
|
14
|
+
.reverse()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { isEmpty } from "@pyreon/ui-core"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Copies user-defined statics from `.statics()` into the component's
|
|
5
|
+
* `meta` object. These are accessible at `Component.meta.myStatic`.
|
|
6
|
+
*/
|
|
7
|
+
type CreateStaticsEnhancers = (params: {
|
|
8
|
+
context: Record<string, any>
|
|
9
|
+
options: Record<string, any>
|
|
10
|
+
}) => void
|
|
11
|
+
|
|
12
|
+
export const createStaticsEnhancers: CreateStaticsEnhancers = ({ context, options }) => {
|
|
13
|
+
if (!isEmpty(options)) {
|
|
14
|
+
Object.assign(context, options)
|
|
15
|
+
}
|
|
16
|
+
}
|