@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.
Files changed (51) hide show
  1. package/package.json +14 -12
  2. package/src/__tests__/attrs.test.ts +190 -0
  3. package/src/__tests__/chaining.test.ts +86 -0
  4. package/src/__tests__/collection.test.ts +35 -0
  5. package/src/__tests__/compose.test.ts +36 -0
  6. package/src/__tests__/context.test.ts +200 -0
  7. package/src/__tests__/createLocalProvider.test.ts +248 -0
  8. package/src/__tests__/dimensions.test.ts +183 -0
  9. package/src/__tests__/e2e-styler.test.ts +291 -0
  10. package/src/__tests__/hooks.test.ts +207 -0
  11. package/src/__tests__/isRocketComponent.test.ts +48 -0
  12. package/src/__tests__/misc.test.ts +204 -0
  13. package/src/__tests__/providerConsumer.test.ts +248 -0
  14. package/src/__tests__/rocketstyleIntegration.test.ts +615 -0
  15. package/src/__tests__/themeUtils.test.ts +463 -0
  16. package/src/cache/LocalThemeManager.ts +14 -0
  17. package/src/cache/index.ts +3 -0
  18. package/src/constants/booleanTags.ts +32 -0
  19. package/src/constants/defaultDimensions.ts +23 -0
  20. package/src/constants/index.ts +44 -0
  21. package/src/context/context.ts +51 -0
  22. package/src/context/createLocalProvider.ts +84 -0
  23. package/src/context/localContext.ts +37 -0
  24. package/src/hoc/index.ts +3 -0
  25. package/src/hoc/rocketstyleAttrsHoc.ts +63 -0
  26. package/src/hooks/index.ts +4 -0
  27. package/src/hooks/usePseudoState.ts +79 -0
  28. package/src/hooks/useTheme.ts +36 -0
  29. package/src/index.ts +77 -0
  30. package/src/init.ts +93 -0
  31. package/src/isRocketComponent.ts +16 -0
  32. package/src/rocketstyle.ts +320 -0
  33. package/src/types/attrs.ts +13 -0
  34. package/src/types/config.ts +48 -0
  35. package/src/types/configuration.ts +69 -0
  36. package/src/types/dimensions.ts +106 -0
  37. package/src/types/hoc.ts +5 -0
  38. package/src/types/pseudo.ts +19 -0
  39. package/src/types/rocketComponent.ts +24 -0
  40. package/src/types/rocketstyle.ts +156 -0
  41. package/src/types/styles.ts +46 -0
  42. package/src/types/theme.ts +19 -0
  43. package/src/types/utils.ts +55 -0
  44. package/src/utils/attrs.ts +134 -0
  45. package/src/utils/chaining.ts +58 -0
  46. package/src/utils/collection.ts +9 -0
  47. package/src/utils/compose.ts +11 -0
  48. package/src/utils/dimensions.ts +126 -0
  49. package/src/utils/statics.ts +44 -0
  50. package/src/utils/styles.ts +18 -0
  51. 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
+ }