@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.
@@ -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>
@@ -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
+ }