@pyreon/rocketstyle 0.11.0 → 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 +14 -12
- package/src/__tests__/attrs.test.ts +190 -0
- package/src/__tests__/chaining.test.ts +86 -0
- package/src/__tests__/collection.test.ts +35 -0
- package/src/__tests__/compose.test.ts +36 -0
- package/src/__tests__/context.test.ts +200 -0
- package/src/__tests__/createLocalProvider.test.ts +248 -0
- package/src/__tests__/dimensions.test.ts +183 -0
- package/src/__tests__/e2e-styler.test.ts +291 -0
- package/src/__tests__/hooks.test.ts +207 -0
- package/src/__tests__/isRocketComponent.test.ts +48 -0
- package/src/__tests__/misc.test.ts +204 -0
- package/src/__tests__/providerConsumer.test.ts +248 -0
- package/src/__tests__/rocketstyleIntegration.test.ts +615 -0
- package/src/__tests__/themeUtils.test.ts +463 -0
- package/src/cache/LocalThemeManager.ts +14 -0
- package/src/cache/index.ts +3 -0
- package/src/constants/booleanTags.ts +32 -0
- package/src/constants/defaultDimensions.ts +23 -0
- package/src/constants/index.ts +44 -0
- package/src/context/context.ts +51 -0
- package/src/context/createLocalProvider.ts +84 -0
- package/src/context/localContext.ts +37 -0
- package/src/hoc/index.ts +3 -0
- package/src/hoc/rocketstyleAttrsHoc.ts +63 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/usePseudoState.ts +79 -0
- package/src/hooks/useTheme.ts +36 -0
- package/src/index.ts +77 -0
- package/src/init.ts +93 -0
- package/src/isRocketComponent.ts +16 -0
- package/src/rocketstyle.ts +320 -0
- package/src/types/attrs.ts +13 -0
- package/src/types/config.ts +48 -0
- package/src/types/configuration.ts +69 -0
- package/src/types/dimensions.ts +106 -0
- package/src/types/hoc.ts +5 -0
- package/src/types/pseudo.ts +19 -0
- package/src/types/rocketComponent.ts +24 -0
- package/src/types/rocketstyle.ts +156 -0
- package/src/types/styles.ts +46 -0
- package/src/types/theme.ts +19 -0
- package/src/types/utils.ts +55 -0
- package/src/utils/attrs.ts +134 -0
- package/src/utils/chaining.ts +58 -0
- package/src/utils/collection.ts +9 -0
- package/src/utils/compose.ts +11 -0
- package/src/utils/dimensions.ts +126 -0
- package/src/utils/statics.ts +44 -0
- package/src/utils/styles.ts +18 -0
- package/src/utils/theme.ts +196 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { VNodeChild } from "@pyreon/core"
|
|
2
|
+
import type { AttrsCb } from "./attrs"
|
|
3
|
+
import type { ConfigAttrs } from "./config"
|
|
4
|
+
import type { DefaultProps } from "./configuration"
|
|
5
|
+
import type {
|
|
6
|
+
DimensionCallbackParam,
|
|
7
|
+
DimensionProps,
|
|
8
|
+
Dimensions,
|
|
9
|
+
DimensionValue,
|
|
10
|
+
ExtractDimensionProps,
|
|
11
|
+
ExtractDimensions,
|
|
12
|
+
MultiKeys,
|
|
13
|
+
TDKP,
|
|
14
|
+
} from "./dimensions"
|
|
15
|
+
import type { ComposeParam } from "./hoc"
|
|
16
|
+
import type { Styles, StylesCb } from "./styles"
|
|
17
|
+
import type { Theme, ThemeCb, ThemeModeKeys } from "./theme"
|
|
18
|
+
import type { ElementType, ExtractProps, MergeTypes, TObj } from "./utils"
|
|
19
|
+
|
|
20
|
+
export type InnerComponentProps = {
|
|
21
|
+
"data-rocketstyle"?: string | undefined
|
|
22
|
+
} & Record<string, any>
|
|
23
|
+
|
|
24
|
+
export type RocketStyleComponent<
|
|
25
|
+
OA extends TObj = {},
|
|
26
|
+
EA extends TObj = {},
|
|
27
|
+
T extends TObj = {},
|
|
28
|
+
CSS extends TObj = {},
|
|
29
|
+
S extends TObj = {},
|
|
30
|
+
HOC extends TObj = {},
|
|
31
|
+
D extends Dimensions = Dimensions,
|
|
32
|
+
UB extends boolean = boolean,
|
|
33
|
+
DKP extends TDKP = TDKP,
|
|
34
|
+
> = IRocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP> & {
|
|
35
|
+
[I in keyof D]: <
|
|
36
|
+
K extends DimensionValue = D[I],
|
|
37
|
+
P extends DimensionCallbackParam<Theme<T>, Styles<CSS>> = DimensionCallbackParam<
|
|
38
|
+
Theme<T>,
|
|
39
|
+
Styles<CSS>
|
|
40
|
+
>,
|
|
41
|
+
>(
|
|
42
|
+
param: P,
|
|
43
|
+
) => P extends DimensionCallbackParam<Theme<T>, Styles<CSS>>
|
|
44
|
+
? RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DimensionProps<K, D, P, DKP>>
|
|
45
|
+
: RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param OA Origin component props params.
|
|
50
|
+
* @param EA Extended prop types
|
|
51
|
+
* @param T Theme passed via context.
|
|
52
|
+
* @param CSS Custom theme accepted by styles.
|
|
53
|
+
* @param S Defined statics
|
|
54
|
+
* @param D Dimensions to be used for defining component states.
|
|
55
|
+
* @param UB Use booleans value
|
|
56
|
+
* @param DKP Dimensions key props.
|
|
57
|
+
* @param DFP Calculated final component props
|
|
58
|
+
*/
|
|
59
|
+
export interface IRocketStyleComponent<
|
|
60
|
+
// original component props
|
|
61
|
+
OA extends TObj = {},
|
|
62
|
+
// extended component props
|
|
63
|
+
EA extends TObj = {},
|
|
64
|
+
// theme
|
|
65
|
+
T extends TObj = {},
|
|
66
|
+
// custom style properties
|
|
67
|
+
CSS extends TObj = {},
|
|
68
|
+
// statics
|
|
69
|
+
S extends TObj = {},
|
|
70
|
+
// hocs
|
|
71
|
+
HOC extends TObj = {},
|
|
72
|
+
// dimensions
|
|
73
|
+
D extends Dimensions = Dimensions,
|
|
74
|
+
// use booleans
|
|
75
|
+
UB extends boolean = boolean,
|
|
76
|
+
// dimension key props
|
|
77
|
+
DKP extends TDKP = TDKP,
|
|
78
|
+
// calculated final props
|
|
79
|
+
DFP = MergeTypes<[OA, EA, DefaultProps, ExtractDimensionProps<D, DKP, UB>]>,
|
|
80
|
+
> {
|
|
81
|
+
// The component is callable — Pyreon components are plain functions
|
|
82
|
+
(props: DFP): VNodeChild
|
|
83
|
+
|
|
84
|
+
// CONFIG chaining method
|
|
85
|
+
config: <NC extends ElementType | unknown = unknown>({
|
|
86
|
+
name,
|
|
87
|
+
component: NC,
|
|
88
|
+
provider,
|
|
89
|
+
consumer,
|
|
90
|
+
DEBUG,
|
|
91
|
+
inversed,
|
|
92
|
+
passProps,
|
|
93
|
+
}: ConfigAttrs<NC, D, DKP, UB>) => NC extends ElementType
|
|
94
|
+
? RocketStyleComponent<ExtractProps<NC>, EA, T, CSS, S, HOC, D, UB, DKP>
|
|
95
|
+
: RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP>
|
|
96
|
+
|
|
97
|
+
// ATTRS chaining method
|
|
98
|
+
attrs: <P extends TObj | unknown = unknown>(
|
|
99
|
+
param: P extends TObj
|
|
100
|
+
?
|
|
101
|
+
| Partial<DFP & P>
|
|
102
|
+
| ((
|
|
103
|
+
props: Partial<DFP & P>,
|
|
104
|
+
theme: Theme<T>,
|
|
105
|
+
helpers: any,
|
|
106
|
+
) => Partial<P> & Record<string, unknown>)
|
|
107
|
+
: Partial<DFP> | AttrsCb<DFP, Theme<T>>,
|
|
108
|
+
config?: Partial<{
|
|
109
|
+
priority: boolean
|
|
110
|
+
filter: P extends TObj ? Partial<keyof (EA & P)>[] : Partial<keyof EA>[]
|
|
111
|
+
}>,
|
|
112
|
+
) => P extends TObj
|
|
113
|
+
? RocketStyleComponent<OA, MergeTypes<[EA, P]>, T, CSS, S, HOC, D, UB, DKP>
|
|
114
|
+
: RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP>
|
|
115
|
+
|
|
116
|
+
// THEME chaining method
|
|
117
|
+
theme: <P extends TObj = TObj>(
|
|
118
|
+
param: Partial<P> | Partial<Styles<CSS>> | ThemeCb<P, Theme<T>>,
|
|
119
|
+
) => RocketStyleComponent<OA, EA, T, MergeTypes<[CSS, P]>, S, HOC, D, UB, DKP>
|
|
120
|
+
|
|
121
|
+
// STYLES chaining method
|
|
122
|
+
styles: (param: StylesCb<CSS>) => RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP>
|
|
123
|
+
|
|
124
|
+
// COMPOSE chaining method
|
|
125
|
+
compose: <P extends ComposeParam>(
|
|
126
|
+
param: P,
|
|
127
|
+
) => P extends TObj
|
|
128
|
+
? RocketStyleComponent<OA, EA, T, CSS, S, MergeTypes<[HOC, P]>, D, UB, DKP>
|
|
129
|
+
: RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP>
|
|
130
|
+
|
|
131
|
+
// STATICS chaining method
|
|
132
|
+
statics: <P extends TObj | unknown = unknown>(
|
|
133
|
+
param: P,
|
|
134
|
+
) => P extends TObj
|
|
135
|
+
? RocketStyleComponent<OA, EA, T, CSS, MergeTypes<[S, P]>, HOC, D, UB, DKP>
|
|
136
|
+
: RocketStyleComponent<OA, EA, T, CSS, S, HOC, D, UB, DKP>
|
|
137
|
+
|
|
138
|
+
/** Access to all defined statics on the component. */
|
|
139
|
+
meta: S
|
|
140
|
+
|
|
141
|
+
getStaticDimensions: (theme: TObj) => {
|
|
142
|
+
dimensions: DKP
|
|
143
|
+
useBooleans: UB
|
|
144
|
+
multiKeys: MultiKeys<D>
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getDefaultAttrs: (props: TObj, theme: TObj, mode: ThemeModeKeys) => TObj
|
|
148
|
+
|
|
149
|
+
readonly $$rocketstyle: ExtractDimensions<D, DKP>
|
|
150
|
+
readonly $$originTypes: OA
|
|
151
|
+
readonly $$extendedTypes: EA
|
|
152
|
+
readonly $$types: DFP
|
|
153
|
+
|
|
154
|
+
IS_ROCKETSTYLE: true
|
|
155
|
+
displayName: string
|
|
156
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { config } from "@pyreon/ui-core"
|
|
2
|
+
import type { PseudoState } from "./pseudo"
|
|
3
|
+
import type { TObj } from "./utils"
|
|
4
|
+
|
|
5
|
+
// biome-ignore lint/suspicious/noEmptyInterface: this is an interface to be extended in consuming projects
|
|
6
|
+
export interface StylesDefault {}
|
|
7
|
+
|
|
8
|
+
// biome-ignore lint/correctness/noUnusedVariables: S kept for backward compatibility
|
|
9
|
+
export type Styles<S = unknown> = StylesDefault
|
|
10
|
+
|
|
11
|
+
export type Css = typeof config.css
|
|
12
|
+
export type Style = ReturnType<Css>
|
|
13
|
+
export type OptionStyles = ((css: Css) => ReturnType<Css>)[]
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Props available inside `.styles()` interpolation functions.
|
|
17
|
+
*
|
|
18
|
+
* - `$rocketstyle` — computed theme (inferred from `.theme()` chain)
|
|
19
|
+
* - `$rocketstate` — active dimension values + pseudo state
|
|
20
|
+
*/
|
|
21
|
+
export type RocketStyleInterpolationProps<CSS extends TObj = TObj> = {
|
|
22
|
+
$rocketstyle: CSS
|
|
23
|
+
$rocketstate: Record<string, string | string[]> & {
|
|
24
|
+
pseudo: Partial<PseudoState>
|
|
25
|
+
}
|
|
26
|
+
} & Record<string, any>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A tagged-template css function whose interpolation functions
|
|
30
|
+
* receive typed props including `$rocketstyle` and `$rocketstate`.
|
|
31
|
+
*/
|
|
32
|
+
export type RocketCss<CSS extends TObj = TObj> = (
|
|
33
|
+
strings: TemplateStringsArray,
|
|
34
|
+
...values: Array<
|
|
35
|
+
| string
|
|
36
|
+
| number
|
|
37
|
+
| boolean
|
|
38
|
+
| null
|
|
39
|
+
| undefined
|
|
40
|
+
| ((props: RocketStyleInterpolationProps<CSS>) => any)
|
|
41
|
+
| any[]
|
|
42
|
+
>
|
|
43
|
+
) => any
|
|
44
|
+
|
|
45
|
+
export type StylesCb<CSS extends TObj = TObj> = (css: RocketCss<CSS>) => ReturnType<Css>
|
|
46
|
+
export type StylesCbArray = StylesCb[]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { THEME_MODES } from "../constants"
|
|
2
|
+
import type { Css } from "./styles"
|
|
3
|
+
import type { MergeTypes } from "./utils"
|
|
4
|
+
|
|
5
|
+
// biome-ignore lint/suspicious/noEmptyInterface: this is an interface to be extended in consuming projects
|
|
6
|
+
export interface ThemeDefault {}
|
|
7
|
+
|
|
8
|
+
export type Theme<T> = T extends unknown ? ThemeDefault : MergeTypes<[ThemeDefault, T]>
|
|
9
|
+
|
|
10
|
+
export type ThemeModeKeys = keyof typeof THEME_MODES
|
|
11
|
+
|
|
12
|
+
export type ThemeModeCallback = <A = any, B = any>(
|
|
13
|
+
light: A,
|
|
14
|
+
dark: B,
|
|
15
|
+
) => (mode: "light" | "dark") => A | B
|
|
16
|
+
|
|
17
|
+
export type ThemeMode = <A = any, B = any>(light: A, dark: B) => A | B
|
|
18
|
+
|
|
19
|
+
export type ThemeCb<CSS, T> = (theme: T, mode: ThemeModeCallback, css: Css) => Partial<CSS>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { VNodeChild } from "@pyreon/core"
|
|
2
|
+
|
|
3
|
+
export type TObj = Record<string, unknown>
|
|
4
|
+
export type TFn = (...args: any) => any
|
|
5
|
+
export type CallBackParam = TObj | TFn
|
|
6
|
+
export type DisplayName = string
|
|
7
|
+
|
|
8
|
+
/** In Pyreon, components are plain functions — no forwardRef needed. */
|
|
9
|
+
export type ComponentFn<P = any> = ((props: P) => VNodeChild) & Partial<Record<string, any>>
|
|
10
|
+
|
|
11
|
+
export type ElementType<T extends TObj | unknown = any> = ComponentFn<T>
|
|
12
|
+
|
|
13
|
+
export type ValueOf<T> = T[keyof T]
|
|
14
|
+
|
|
15
|
+
export type ArrayOfValues<T> = T[keyof T][]
|
|
16
|
+
|
|
17
|
+
export type ArrayOfKeys<T> = Array<keyof T>
|
|
18
|
+
|
|
19
|
+
export type SimpleHoc<P extends Record<string, unknown> = Record<string, unknown>> = <
|
|
20
|
+
T extends ComponentFn<any>,
|
|
21
|
+
>(
|
|
22
|
+
WrappedComponent: T,
|
|
23
|
+
) => ComponentFn<MergeTypes<[P, ExtractProps<T>]>>
|
|
24
|
+
|
|
25
|
+
type IsFalseOrNullable<T> = T extends null | undefined | false ? never : true
|
|
26
|
+
export type NullableKeys<T> = { [K in keyof T]: IsFalseOrNullable<T[K]> }
|
|
27
|
+
|
|
28
|
+
export type ReturnCbParam<P extends TFn | TObj> = P extends TFn ? ReturnType<P> : P
|
|
29
|
+
|
|
30
|
+
// ─── MergeTypes ───────────────────────────────────────────────
|
|
31
|
+
type Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : never
|
|
32
|
+
|
|
33
|
+
type IsAny<T> = 0 extends 1 & T ? true : false
|
|
34
|
+
|
|
35
|
+
type ExtractNullableKeys<T> = {
|
|
36
|
+
[P in keyof T as IsAny<T[P]> extends true
|
|
37
|
+
? P
|
|
38
|
+
: [T[P]] extends [never]
|
|
39
|
+
? never
|
|
40
|
+
: [T[P]] extends [null | undefined]
|
|
41
|
+
? never
|
|
42
|
+
: P]: T[P]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type SpreadTwo<L, R> = Id<Pick<L, Exclude<keyof L, keyof R>> & R>
|
|
46
|
+
|
|
47
|
+
export type Spread<A extends readonly [...any]> = A extends [infer L, ...infer R]
|
|
48
|
+
? SpreadTwo<L, Spread<R>>
|
|
49
|
+
: unknown
|
|
50
|
+
|
|
51
|
+
export type MergeTypes<A extends readonly [...any]> = ExtractNullableKeys<Spread<A>>
|
|
52
|
+
|
|
53
|
+
// ─── ExtractProps ─────────────────────────────────────────────
|
|
54
|
+
export type ExtractProps<TComponentOrTProps> =
|
|
55
|
+
TComponentOrTProps extends ComponentFn<infer TProps> ? TProps : TComponentOrTProps
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { MultiKeys } from "../types/dimensions"
|
|
2
|
+
|
|
3
|
+
// --------------------------------------------------------
|
|
4
|
+
// remove undefined props
|
|
5
|
+
// --------------------------------------------------------
|
|
6
|
+
/** Strips keys with `undefined` values so they don't shadow default props during merging. */
|
|
7
|
+
type RemoveUndefinedProps = <T extends Record<string, any>>(props: T) => Partial<T>
|
|
8
|
+
|
|
9
|
+
export const removeUndefinedProps: RemoveUndefinedProps = (props) => {
|
|
10
|
+
const result: Partial<typeof props> = {}
|
|
11
|
+
for (const key in props) {
|
|
12
|
+
if (props[key] !== undefined) result[key] = props[key]
|
|
13
|
+
}
|
|
14
|
+
return result
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// --------------------------------------------------------
|
|
18
|
+
// pick styled props
|
|
19
|
+
// --------------------------------------------------------
|
|
20
|
+
/** Picks only the props whose keys exist in the dimension keywords lookup and have truthy values. */
|
|
21
|
+
export const pickStyledAttrs = <
|
|
22
|
+
T extends Record<string, any>,
|
|
23
|
+
K extends Record<string, true | undefined>,
|
|
24
|
+
>(
|
|
25
|
+
props: T,
|
|
26
|
+
keywords: K,
|
|
27
|
+
): { [I in keyof K & keyof T]: T[I] } => {
|
|
28
|
+
const result: Record<string, unknown> = {}
|
|
29
|
+
for (const key of Object.keys(props)) {
|
|
30
|
+
if (keywords[key] && props[key]) result[key] = props[key]
|
|
31
|
+
}
|
|
32
|
+
return result as { [I in keyof K & keyof T]: T[I] }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// --------------------------------------------------------
|
|
36
|
+
// combine values
|
|
37
|
+
// --------------------------------------------------------
|
|
38
|
+
/**
|
|
39
|
+
* Returns a curried function that evaluates an array of `.attrs()` callbacks,
|
|
40
|
+
* spreading each result into a single merged props object via `Object.assign`.
|
|
41
|
+
*/
|
|
42
|
+
type OptionFunc<A> = (...arg: A[]) => Record<string, unknown>
|
|
43
|
+
type CalculateChainOptions = <A>(
|
|
44
|
+
options?: OptionFunc<A>[],
|
|
45
|
+
) => (args: A[]) => ReturnType<OptionFunc<A>>
|
|
46
|
+
|
|
47
|
+
export const calculateChainOptions: CalculateChainOptions = (options) => (args) => {
|
|
48
|
+
if (!options || options.length === 0) return {}
|
|
49
|
+
|
|
50
|
+
return options.reduce<Record<string, unknown>>(
|
|
51
|
+
(acc, item) => Object.assign(acc, item(...args)),
|
|
52
|
+
{},
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// --------------------------------------------------------
|
|
57
|
+
// get style attributes
|
|
58
|
+
// --------------------------------------------------------
|
|
59
|
+
/**
|
|
60
|
+
* Resolves the active value for each styling dimension from component props.
|
|
61
|
+
* First checks for explicit prop values (string, number, or array for multi-keys),
|
|
62
|
+
* then falls back to boolean shorthand props when `useBooleans` is enabled.
|
|
63
|
+
*/
|
|
64
|
+
type CalculateStylingAttrs = ({
|
|
65
|
+
useBooleans,
|
|
66
|
+
multiKeys,
|
|
67
|
+
}: {
|
|
68
|
+
useBooleans?: boolean
|
|
69
|
+
multiKeys?: MultiKeys
|
|
70
|
+
}) => ({
|
|
71
|
+
props,
|
|
72
|
+
dimensions,
|
|
73
|
+
}: {
|
|
74
|
+
props: Record<string, unknown>
|
|
75
|
+
dimensions: Record<string, unknown>
|
|
76
|
+
}) => Record<string, any>
|
|
77
|
+
export const calculateStylingAttrs: CalculateStylingAttrs =
|
|
78
|
+
({ useBooleans, multiKeys }) =>
|
|
79
|
+
({ props, dimensions }) => {
|
|
80
|
+
const result: Record<string, any> = {}
|
|
81
|
+
|
|
82
|
+
// (1) find dimension keys values & initialize
|
|
83
|
+
// object with possible options
|
|
84
|
+
Object.keys(dimensions).forEach((item) => {
|
|
85
|
+
const pickedProp = props[item]
|
|
86
|
+
const t = typeof pickedProp
|
|
87
|
+
|
|
88
|
+
// if the property is multi key, allow assign array as well
|
|
89
|
+
if (multiKeys?.[item] && Array.isArray(pickedProp)) {
|
|
90
|
+
result[item] = pickedProp
|
|
91
|
+
}
|
|
92
|
+
// assign when it's only a string or number otherwise it's considered
|
|
93
|
+
// as invalid param
|
|
94
|
+
else if (t === "string" || t === "number") {
|
|
95
|
+
result[item] = pickedProp
|
|
96
|
+
} else {
|
|
97
|
+
result[item] = undefined
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// (2) if booleans are being used let's find the rest
|
|
102
|
+
if (useBooleans) {
|
|
103
|
+
const propsKeys = Object.keys(props)
|
|
104
|
+
|
|
105
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: complex logic is inherent to this function
|
|
106
|
+
Object.entries(result).forEach(([key, value]) => {
|
|
107
|
+
const isMultiKey = multiKeys?.[key]
|
|
108
|
+
|
|
109
|
+
// when value in result is not assigned yet
|
|
110
|
+
if (!value) {
|
|
111
|
+
let newDimensionValue: string | string[] | undefined
|
|
112
|
+
const keywordSet = new Set(Object.keys(dimensions[key] as Record<string, unknown>))
|
|
113
|
+
|
|
114
|
+
if (isMultiKey) {
|
|
115
|
+
newDimensionValue = propsKeys.filter((propKey) => keywordSet.has(propKey))
|
|
116
|
+
} else {
|
|
117
|
+
// iterate backwards to guarantee the last one will have
|
|
118
|
+
// a priority over previous ones
|
|
119
|
+
for (let i = propsKeys.length - 1; i >= 0; i--) {
|
|
120
|
+
const k = propsKeys[i] as string
|
|
121
|
+
if (keywordSet.has(k) && props[k]) {
|
|
122
|
+
newDimensionValue = k
|
|
123
|
+
break
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
result[key] = newDimensionValue
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return result
|
|
134
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
type Func = (...args: any) => Record<string, unknown>
|
|
2
|
+
type Obj = Record<string, unknown>
|
|
3
|
+
|
|
4
|
+
// --------------------------------------------------------
|
|
5
|
+
// Chain Options
|
|
6
|
+
// --------------------------------------------------------
|
|
7
|
+
/**
|
|
8
|
+
* Appends a new option (function or plain object) to an existing chain
|
|
9
|
+
* of option callbacks. Objects are wrapped in a thunk for uniform handling.
|
|
10
|
+
*/
|
|
11
|
+
type ChainOptions = (opts: Obj | Func | undefined, defaultOpts: Func[]) => Func[]
|
|
12
|
+
|
|
13
|
+
export const chainOptions: ChainOptions = (opts, defaultOpts = []) => {
|
|
14
|
+
const result = [...defaultOpts]
|
|
15
|
+
|
|
16
|
+
if (typeof opts === "function") result.push(opts)
|
|
17
|
+
else if (typeof opts === "object") result.push(() => opts)
|
|
18
|
+
|
|
19
|
+
return result
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// --------------------------------------------------------
|
|
23
|
+
// Chain Or Options
|
|
24
|
+
// --------------------------------------------------------
|
|
25
|
+
/**
|
|
26
|
+
* For each key, picks the new value if truthy, otherwise falls back
|
|
27
|
+
* to the default. Used for config keys that replace rather than merge.
|
|
28
|
+
*/
|
|
29
|
+
type ChainOrOptions = (
|
|
30
|
+
keys: readonly string[],
|
|
31
|
+
opts: Obj,
|
|
32
|
+
defaultOpts: Obj,
|
|
33
|
+
) => Record<string, unknown>
|
|
34
|
+
|
|
35
|
+
export const chainOrOptions: ChainOrOptions = (keys, opts, defaultOpts) =>
|
|
36
|
+
keys.reduce((acc, item) => ({ ...acc, [item]: opts[item] || defaultOpts[item] }), {})
|
|
37
|
+
|
|
38
|
+
// --------------------------------------------------------
|
|
39
|
+
// Chain Reserved Options
|
|
40
|
+
// --------------------------------------------------------
|
|
41
|
+
/**
|
|
42
|
+
* Chains option callbacks for reserved dimension and styling keys,
|
|
43
|
+
* delegating to `chainOptions` for each key individually.
|
|
44
|
+
*/
|
|
45
|
+
type ChainReservedKeyOptions = (
|
|
46
|
+
keys: readonly string[],
|
|
47
|
+
opts: Record<string, Obj | Func>,
|
|
48
|
+
defaultOpts: Record<string, Func[]>,
|
|
49
|
+
) => Record<string, ReturnType<typeof chainOptions>>
|
|
50
|
+
|
|
51
|
+
export const chainReservedKeyOptions: ChainReservedKeyOptions = (keys, opts, defaultOpts) =>
|
|
52
|
+
keys.reduce(
|
|
53
|
+
(acc, item) => ({
|
|
54
|
+
...acc,
|
|
55
|
+
[item]: chainOptions(opts[item], defaultOpts[item] ?? []),
|
|
56
|
+
}),
|
|
57
|
+
{},
|
|
58
|
+
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// --------------------------------------------------------
|
|
2
|
+
// Remove Nullable values
|
|
3
|
+
// --------------------------------------------------------
|
|
4
|
+
/** Filters out entries with `null`, `undefined`, or `false` values from an object. */
|
|
5
|
+
type RemoveNullableValues = (obj: Record<string, any>) => Record<string, any>
|
|
6
|
+
export const removeNullableValues: RemoveNullableValues = (obj) =>
|
|
7
|
+
Object.entries(obj)
|
|
8
|
+
.filter(([, v]) => v != null && v !== false)
|
|
9
|
+
.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts HOC functions from the compose configuration object,
|
|
3
|
+
* filters out non-function entries, and reverses them so the
|
|
4
|
+
* outermost HOC in the chain wraps first (inside-out composition).
|
|
5
|
+
*/
|
|
6
|
+
type CalculateHocsFuncs = (options: Record<string, any>) => ((arg: any) => any)[]
|
|
7
|
+
|
|
8
|
+
export const calculateHocsFuncs: CalculateHocsFuncs = (options = {}) =>
|
|
9
|
+
Object.values(options)
|
|
10
|
+
.filter((item) => typeof item === "function")
|
|
11
|
+
.reverse()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { get, isEmpty, set } from "@pyreon/ui-core"
|
|
2
|
+
import type { Dimensions, DimensionValue, MultiKeys } from "../types/dimensions"
|
|
3
|
+
|
|
4
|
+
// --------------------------------------------------------
|
|
5
|
+
// Is value a valid key
|
|
6
|
+
// --------------------------------------------------------
|
|
7
|
+
/** Checks whether a dimension value is defined and not explicitly disabled (false). */
|
|
8
|
+
type IsValidKey = (value: any) => boolean
|
|
9
|
+
export const isValidKey: IsValidKey = (value) =>
|
|
10
|
+
value !== undefined && value !== null && value !== false
|
|
11
|
+
|
|
12
|
+
// --------------------------------------------------------
|
|
13
|
+
// Is value a multi key
|
|
14
|
+
// --------------------------------------------------------
|
|
15
|
+
/** Determines if a dimension value is a multi-key config object, returning [isMulti, propName]. */
|
|
16
|
+
type IsMultiKey = (value: string | Record<string, unknown>) => [boolean, string]
|
|
17
|
+
export const isMultiKey: IsMultiKey = (value) => {
|
|
18
|
+
if (typeof value === "object" && value !== null) return [true, get(value, "propName") as string]
|
|
19
|
+
return [false, value]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// --------------------------------------------------------
|
|
23
|
+
// calculate dimensions map
|
|
24
|
+
// --------------------------------------------------------
|
|
25
|
+
/**
|
|
26
|
+
* Builds a two-level map (`keysMap`) of dimension -> option -> true,
|
|
27
|
+
* and a flat `keywords` lookup of all prop names reserved by dimensions
|
|
28
|
+
* (including boolean shorthand keys when `useBooleans` is enabled).
|
|
29
|
+
*/
|
|
30
|
+
type GetDimensionsMap = <T extends Record<string, any>>({
|
|
31
|
+
themes,
|
|
32
|
+
useBooleans,
|
|
33
|
+
}: {
|
|
34
|
+
themes: T
|
|
35
|
+
useBooleans?: boolean
|
|
36
|
+
}) => { keysMap: Record<string, any>; keywords: Record<string, any> }
|
|
37
|
+
|
|
38
|
+
export const getDimensionsMap: GetDimensionsMap = ({ themes, useBooleans }) => {
|
|
39
|
+
const result = {
|
|
40
|
+
keysMap: {} as Record<string, any>,
|
|
41
|
+
keywords: {} as Record<string, any>,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isEmpty(themes)) return result
|
|
45
|
+
|
|
46
|
+
return Object.entries(themes).reduce((accumulator, [key, value]) => {
|
|
47
|
+
const { keysMap, keywords } = accumulator
|
|
48
|
+
keywords[key] = true
|
|
49
|
+
|
|
50
|
+
Object.entries(value).forEach(([itemKey, itemValue]) => {
|
|
51
|
+
if (!isValidKey(itemValue)) return
|
|
52
|
+
|
|
53
|
+
if (useBooleans) {
|
|
54
|
+
keywords[itemKey] = true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
set(keysMap, [key, itemKey], true)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return accumulator
|
|
61
|
+
}, result)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// --------------------------------------------------------
|
|
65
|
+
// simple object getters
|
|
66
|
+
// --------------------------------------------------------
|
|
67
|
+
/** Returns the keys of an object with proper typing. */
|
|
68
|
+
type GetKeys = <T extends Record<string, unknown>>(obj: T) => Array<keyof T>
|
|
69
|
+
export const getKeys: GetKeys = (obj) => Object.keys(obj)
|
|
70
|
+
|
|
71
|
+
type GetValues = <T extends Record<string, unknown>, K extends keyof T>(obj: T) => T[K][]
|
|
72
|
+
export const getValues = (<T extends Record<string, unknown>>(obj: T) =>
|
|
73
|
+
Object.values(obj)) as GetValues
|
|
74
|
+
|
|
75
|
+
// --------------------------------------------------------
|
|
76
|
+
// get dimensions values array
|
|
77
|
+
// --------------------------------------------------------
|
|
78
|
+
/** Extracts the prop name from each dimension value, unwrapping multi-key config objects. */
|
|
79
|
+
type ValueType<T> = T extends string ? T : T[][0]
|
|
80
|
+
type GetDimensionsValues = <T extends Dimensions, K extends keyof T>(obj: T) => ValueType<T[K]>[]
|
|
81
|
+
|
|
82
|
+
export const getDimensionsValues = (<T extends Dimensions>(obj: T) =>
|
|
83
|
+
getValues(obj).map((item: DimensionValue) => {
|
|
84
|
+
if (typeof item === "object") {
|
|
85
|
+
return item.propName as string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return item
|
|
89
|
+
})) as GetDimensionsValues
|
|
90
|
+
|
|
91
|
+
// --------------------------------------------------------
|
|
92
|
+
// get multiple dimensions map
|
|
93
|
+
// --------------------------------------------------------
|
|
94
|
+
/** Builds a lookup of dimension prop names that accept multiple simultaneous values. */
|
|
95
|
+
type GetMultipleDimensions = <T extends Dimensions>(obj: T) => MultiKeys<T>
|
|
96
|
+
|
|
97
|
+
export const getMultipleDimensions: GetMultipleDimensions = (obj) =>
|
|
98
|
+
getValues(obj).reduce(
|
|
99
|
+
(accumulator, value: DimensionValue) => {
|
|
100
|
+
if (typeof value === "object") {
|
|
101
|
+
if (value.multi === true) accumulator[value.propName] = true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return accumulator
|
|
105
|
+
},
|
|
106
|
+
{} as Record<string, true>,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
// --------------------------------------------------------
|
|
110
|
+
// get transform dimensions map
|
|
111
|
+
// --------------------------------------------------------
|
|
112
|
+
/** Builds a lookup of dimension prop names that are transform dimensions (evaluated last with function values). */
|
|
113
|
+
type TransformKeys = Partial<Record<string, true>>
|
|
114
|
+
type GetTransformDimensions = <T extends Dimensions>(obj: T) => TransformKeys
|
|
115
|
+
|
|
116
|
+
export const getTransformDimensions: GetTransformDimensions = (obj) =>
|
|
117
|
+
getValues(obj).reduce(
|
|
118
|
+
(accumulator, value: DimensionValue) => {
|
|
119
|
+
if (typeof value === "object") {
|
|
120
|
+
if (value.transform === true) accumulator[value.propName] = true
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return accumulator
|
|
124
|
+
},
|
|
125
|
+
{} as Record<string, true>,
|
|
126
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { isEmpty } from "@pyreon/ui-core"
|
|
2
|
+
import { STATIC_KEYS } from "../constants"
|
|
3
|
+
|
|
4
|
+
// --------------------------------------------------------
|
|
5
|
+
// helpers for create statics chaining methods on component
|
|
6
|
+
// --------------------------------------------------------
|
|
7
|
+
/**
|
|
8
|
+
* Attaches chaining static methods (e.g. `.states()`, `.sizes()`, `.theme()`)
|
|
9
|
+
* to a component. Each method calls `cloneAndEnhance` with the corresponding key.
|
|
10
|
+
*/
|
|
11
|
+
type CreateStaticsChainingEnhancers = <O extends Record<string, any>, DK extends string[]>(props: {
|
|
12
|
+
context: Record<string, any>
|
|
13
|
+
dimensionKeys: DK
|
|
14
|
+
func: (defaultOpts: O, opts: any) => void
|
|
15
|
+
options: O
|
|
16
|
+
}) => void
|
|
17
|
+
|
|
18
|
+
export const createStaticsChainingEnhancers: CreateStaticsChainingEnhancers = ({
|
|
19
|
+
context,
|
|
20
|
+
dimensionKeys,
|
|
21
|
+
func,
|
|
22
|
+
options,
|
|
23
|
+
}) => {
|
|
24
|
+
const keys = [...dimensionKeys, ...STATIC_KEYS]
|
|
25
|
+
|
|
26
|
+
keys.forEach((item) => {
|
|
27
|
+
context[item] = (props: any) => func(options, { [item]: props })
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// --------------------------------------------------------
|
|
32
|
+
// helpers for create statics on component
|
|
33
|
+
// --------------------------------------------------------
|
|
34
|
+
/** Copies user-defined static properties onto the component's `meta` object. */
|
|
35
|
+
type CreateStaticsEnhancers = (params: {
|
|
36
|
+
context: Record<string, any>
|
|
37
|
+
options: Record<string, any>
|
|
38
|
+
}) => void
|
|
39
|
+
|
|
40
|
+
export const createStaticsEnhancers: CreateStaticsEnhancers = ({ context, options }) => {
|
|
41
|
+
if (!isEmpty(options)) {
|
|
42
|
+
Object.assign(context, options)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { config } from "@pyreon/ui-core"
|
|
2
|
+
import type { StylesCbArray } from "../types/styles"
|
|
3
|
+
|
|
4
|
+
// --------------------------------------------------------
|
|
5
|
+
// Calculate styles
|
|
6
|
+
// --------------------------------------------------------
|
|
7
|
+
/**
|
|
8
|
+
* Evaluates an array of style callback functions with the configured
|
|
9
|
+
* `css` tagged-template helper, producing the final CSS interpolations
|
|
10
|
+
* to be passed into the styled-component template literal.
|
|
11
|
+
*/
|
|
12
|
+
type CalculateStyles = (styles: StylesCbArray | undefined) => ReturnType<StylesCbArray[number]>[]
|
|
13
|
+
|
|
14
|
+
export const calculateStyles: CalculateStyles = (styles) => {
|
|
15
|
+
if (!styles) return []
|
|
16
|
+
|
|
17
|
+
return styles.map((item) => item(config.css as Parameters<typeof item>[0]))
|
|
18
|
+
}
|