clava 0.0.1 → 0.1.0

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/src/types.ts ADDED
@@ -0,0 +1,325 @@
1
+ import type * as CSS from "csstype";
2
+
3
+ export type ClassValue =
4
+ | string
5
+ | number
6
+ | boolean
7
+ | null
8
+ | undefined
9
+ | void
10
+ | ClassValue[];
11
+
12
+ export type JSXCSSProperties = CSS.Properties<string | number>;
13
+
14
+ export type HTMLCSSProperties = CSS.PropertiesHyphen<string | number>;
15
+
16
+ export type CSSProperties = CSS.Properties;
17
+
18
+ export type StyleProperty = JSXCSSProperties | HTMLCSSProperties | string;
19
+
20
+ export interface JSXProps {
21
+ className: string;
22
+ style: JSXCSSProperties;
23
+ }
24
+
25
+ export interface HTMLProps {
26
+ class: string;
27
+ style: string;
28
+ }
29
+
30
+ export interface HTMLObjProps {
31
+ class: string;
32
+ style: HTMLCSSProperties;
33
+ }
34
+
35
+ export interface StyleProps {
36
+ jsx: JSXProps;
37
+ html: HTMLProps;
38
+ htmlObj: HTMLObjProps;
39
+ }
40
+
41
+ export type ComponentResult = JSXProps | HTMLProps | HTMLObjProps;
42
+
43
+ export type ComponentProps<V = {}> = VariantValues<V> &
44
+ Partial<ComponentResult>;
45
+
46
+ export type GetVariants<V> = (variants?: VariantValues<V>) => VariantValues<V>;
47
+
48
+ // Key source types - what can be passed as additional parameters to splitProps
49
+ export type KeySourceArray = readonly string[];
50
+ export type KeySourceComponent = {
51
+ keys: readonly (string | number | symbol)[];
52
+ getVariants: () => Record<string, unknown>;
53
+ };
54
+ export type KeySource = KeySourceArray | KeySourceComponent;
55
+
56
+ // Extract keys from a source
57
+ type SourceKeys<S> = S extends readonly (infer K)[]
58
+ ? K
59
+ : S extends { keys: readonly (infer K)[] }
60
+ ? K
61
+ : never;
62
+
63
+ // Extract defaults from a source (components have defaults, arrays don't)
64
+ type SourceDefaults<S> = S extends { getVariants: () => infer Defaults }
65
+ ? Defaults
66
+ : {};
67
+
68
+ // Result type for one source - pick keys from T and add defaults
69
+ type SourceResult<T, S> = Pick<T, Extract<keyof T, SourceKeys<S>>> &
70
+ Omit<SourceDefaults<S>, keyof T>;
71
+
72
+ // Standalone splitProps function type - first source is required
73
+ export type SplitPropsFunction = <
74
+ const T extends Record<string, unknown>,
75
+ const S1 extends KeySource,
76
+ const Sources extends readonly KeySource[],
77
+ >(
78
+ props: T,
79
+ source1: S1,
80
+ ...sources: Sources
81
+ ) => SplitPropsFunctionResult<T, S1, Sources>;
82
+
83
+ // Result type for standalone splitProps function
84
+ type SplitPropsFunctionResult<
85
+ T,
86
+ S1 extends KeySource,
87
+ Sources extends readonly KeySource[],
88
+ > = Sources extends readonly []
89
+ ? [SourceResult<T, S1>, Omit<T, SourceKeys<S1>>]
90
+ : Sources extends readonly [infer S2 extends KeySource]
91
+ ? [
92
+ SourceResult<T, S1>,
93
+ SourceResult<T, S2>,
94
+ Omit<T, SourceKeys<S1> | SourceKeys<S2>>,
95
+ ]
96
+ : Sources extends readonly [
97
+ infer S2 extends KeySource,
98
+ infer S3 extends KeySource,
99
+ ]
100
+ ? [
101
+ SourceResult<T, S1>,
102
+ SourceResult<T, S2>,
103
+ SourceResult<T, S3>,
104
+ Omit<T, SourceKeys<S1> | SourceKeys<S2> | SourceKeys<S3>>,
105
+ ]
106
+ : Sources extends readonly [
107
+ infer S2 extends KeySource,
108
+ infer S3 extends KeySource,
109
+ infer S4 extends KeySource,
110
+ ]
111
+ ? [
112
+ SourceResult<T, S1>,
113
+ SourceResult<T, S2>,
114
+ SourceResult<T, S3>,
115
+ SourceResult<T, S4>,
116
+ Omit<
117
+ T,
118
+ SourceKeys<S1> | SourceKeys<S2> | SourceKeys<S3> | SourceKeys<S4>
119
+ >,
120
+ ]
121
+ : Sources extends readonly [
122
+ infer S2 extends KeySource,
123
+ infer S3 extends KeySource,
124
+ infer S4 extends KeySource,
125
+ infer S5 extends KeySource,
126
+ ]
127
+ ? [
128
+ SourceResult<T, S1>,
129
+ SourceResult<T, S2>,
130
+ SourceResult<T, S3>,
131
+ SourceResult<T, S4>,
132
+ SourceResult<T, S5>,
133
+ Omit<
134
+ T,
135
+ | SourceKeys<S1>
136
+ | SourceKeys<S2>
137
+ | SourceKeys<S3>
138
+ | SourceKeys<S4>
139
+ | SourceKeys<S5>
140
+ >,
141
+ ]
142
+ : Sources extends readonly [
143
+ infer S2 extends KeySource,
144
+ infer S3 extends KeySource,
145
+ infer S4 extends KeySource,
146
+ infer S5 extends KeySource,
147
+ infer S6 extends KeySource,
148
+ ]
149
+ ? [
150
+ SourceResult<T, S1>,
151
+ SourceResult<T, S2>,
152
+ SourceResult<T, S3>,
153
+ SourceResult<T, S4>,
154
+ SourceResult<T, S5>,
155
+ SourceResult<T, S6>,
156
+ Omit<
157
+ T,
158
+ | SourceKeys<S1>
159
+ | SourceKeys<S2>
160
+ | SourceKeys<S3>
161
+ | SourceKeys<S4>
162
+ | SourceKeys<S5>
163
+ | SourceKeys<S6>
164
+ >,
165
+ ]
166
+ : Sources extends readonly [
167
+ infer S2 extends KeySource,
168
+ infer S3 extends KeySource,
169
+ infer S4 extends KeySource,
170
+ infer S5 extends KeySource,
171
+ infer S6 extends KeySource,
172
+ infer S7 extends KeySource,
173
+ ]
174
+ ? [
175
+ SourceResult<T, S1>,
176
+ SourceResult<T, S2>,
177
+ SourceResult<T, S3>,
178
+ SourceResult<T, S4>,
179
+ SourceResult<T, S5>,
180
+ SourceResult<T, S6>,
181
+ SourceResult<T, S7>,
182
+ Omit<
183
+ T,
184
+ | SourceKeys<S1>
185
+ | SourceKeys<S2>
186
+ | SourceKeys<S3>
187
+ | SourceKeys<S4>
188
+ | SourceKeys<S5>
189
+ | SourceKeys<S6>
190
+ | SourceKeys<S7>
191
+ >,
192
+ ]
193
+ : Sources extends readonly [
194
+ infer S2 extends KeySource,
195
+ infer S3 extends KeySource,
196
+ infer S4 extends KeySource,
197
+ infer S5 extends KeySource,
198
+ infer S6 extends KeySource,
199
+ infer S7 extends KeySource,
200
+ infer S8 extends KeySource,
201
+ ]
202
+ ? [
203
+ SourceResult<T, S1>,
204
+ SourceResult<T, S2>,
205
+ SourceResult<T, S3>,
206
+ SourceResult<T, S4>,
207
+ SourceResult<T, S5>,
208
+ SourceResult<T, S6>,
209
+ SourceResult<T, S7>,
210
+ SourceResult<T, S8>,
211
+ Omit<
212
+ T,
213
+ | SourceKeys<S1>
214
+ | SourceKeys<S2>
215
+ | SourceKeys<S3>
216
+ | SourceKeys<S4>
217
+ | SourceKeys<S5>
218
+ | SourceKeys<S6>
219
+ | SourceKeys<S7>
220
+ | SourceKeys<S8>
221
+ >,
222
+ ]
223
+ : unknown[];
224
+
225
+ export interface OnlyVariantsComponent<V> {
226
+ getVariants: GetVariants<V>;
227
+ keys: (keyof V)[];
228
+ }
229
+
230
+ export interface ModalComponent<V, R extends ComponentResult> {
231
+ (props?: ComponentProps<V>): R;
232
+ class: (props?: ComponentProps<V>) => string;
233
+ style: (props?: ComponentProps<V>) => R["style"];
234
+ getVariants: GetVariants<V>;
235
+ keys: (keyof V | keyof R)[];
236
+ onlyVariants: OnlyVariantsComponent<V>;
237
+ /** @internal Base class without variants */
238
+ _baseClass: string;
239
+ }
240
+
241
+ export interface Component<
242
+ V extends Variants = {},
243
+ CV extends ComputedVariants = {},
244
+ E extends AnyComponent[] = [],
245
+ R extends ComponentResult = ComponentResult,
246
+ > extends ModalComponent<MergeVariants<V, CV, E>, R> {
247
+ jsx: ModalComponent<MergeVariants<V, CV, E>, JSXProps>;
248
+ html: ModalComponent<MergeVariants<V, CV, E>, HTMLProps>;
249
+ htmlObj: ModalComponent<MergeVariants<V, CV, E>, HTMLObjProps>;
250
+ }
251
+
252
+ export type AnyComponent =
253
+ | Component<any, any, any, any>
254
+ | ModalComponent<any, any>;
255
+
256
+ type MergeExtendedVariants<T> = T extends readonly [infer First, ...infer Rest]
257
+ ? ExtractVariants<First> & MergeExtendedVariants<Rest>
258
+ : {};
259
+
260
+ type MergeExtendedComputedVariants<T> = T extends readonly [
261
+ infer First,
262
+ ...infer Rest,
263
+ ]
264
+ ? ExtractComputedVariants<First> & MergeExtendedComputedVariants<Rest>
265
+ : {};
266
+
267
+ type ExtractVariants<T> =
268
+ T extends Component<infer V, any, infer E, any>
269
+ ? V & MergeExtendedVariants<E>
270
+ : {};
271
+
272
+ type ExtractComputedVariants<T> =
273
+ T extends Component<any, infer CV, infer E, any>
274
+ ? CV & Omit<MergeExtendedComputedVariants<E>, keyof CV>
275
+ : {};
276
+
277
+ export type MergeVariants<V, CV, E extends AnyComponent[]> = NoInfer<CV> &
278
+ Omit<NoInfer<V>, keyof CV> &
279
+ Omit<MergeExtendedVariants<E>, keyof CV> &
280
+ Omit<MergeExtendedComputedVariants<E>, keyof CV>;
281
+
282
+ type StringToBoolean<T> = T extends "true" | "false" ? boolean : T;
283
+
284
+ type VariantValue = ClassValue | StyleClassValue;
285
+
286
+ type ExtractVariantValue<T> = T extends (value: infer V) => any
287
+ ? V
288
+ : T extends ClassValue
289
+ ? boolean
290
+ : T extends Record<infer K, any>
291
+ ? StringToBoolean<K>
292
+ : never;
293
+
294
+ export type VariantValues<V> = {
295
+ [K in keyof V]?: ExtractVariantValue<V[K]>;
296
+ };
297
+
298
+ export type StyleValue = CSS.Properties & {
299
+ [key: `--${string}`]: string;
300
+ };
301
+
302
+ export type StyleClassValue = StyleValue & { class?: ClassValue };
303
+
304
+ export interface ComputedContext<V> {
305
+ variants: VariantValues<V>;
306
+ setVariants: (variants: VariantValues<V>) => void;
307
+ setDefaultVariants: (variants: VariantValues<V>) => void;
308
+ }
309
+
310
+ export type Computed<V> = (context: ComputedContext<V>) => VariantValue;
311
+
312
+ export type ComputedVariant = (value: any) => VariantValue;
313
+ export type ComputedVariants = Record<string, ComputedVariant>;
314
+ export type Variant = ClassValue | Record<string, VariantValue>;
315
+ export type Variants = Record<string, Variant>;
316
+
317
+ type ExtendedVariants<E extends AnyComponent[]> = MergeExtendedVariants<E> &
318
+ MergeExtendedComputedVariants<E>;
319
+
320
+ export type ExtendableVariants<
321
+ V extends Variants,
322
+ E extends AnyComponent[],
323
+ > = V & {
324
+ [K in keyof ExtendedVariants<E>]?: Partial<ExtendedVariants<E>[K]> | Variant;
325
+ };
package/src/utils.ts ADDED
@@ -0,0 +1,164 @@
1
+ import type * as CSS from "csstype";
2
+
3
+ import type {
4
+ StyleValue,
5
+ JSXCSSProperties,
6
+ HTMLCSSProperties,
7
+ } from "./types.ts";
8
+
9
+ /**
10
+ * Converts a hyphenated CSS property name to camelCase.
11
+ * @example
12
+ * hyphenToCamel("background-color") // "backgroundColor"
13
+ * hyphenToCamel("--custom-var") // "--custom-var" (CSS variables are preserved)
14
+ */
15
+ export function hyphenToCamel(str: string) {
16
+ // CSS custom properties (variables) should not be converted
17
+ if (str.startsWith("--")) {
18
+ return str;
19
+ }
20
+ return str.replace(/-([a-z])/gi, (_, letter) => letter.toUpperCase());
21
+ }
22
+
23
+ /**
24
+ * Converts a camelCase CSS property name to hyphenated form.
25
+ * @example
26
+ * camelToHyphen("backgroundColor") // "background-color"
27
+ * camelToHyphen("--customVar") // "--customVar" (CSS variables are preserved)
28
+ */
29
+ export function camelToHyphen(str: string) {
30
+ // CSS custom properties (variables) should not be converted
31
+ if (str.startsWith("--")) {
32
+ return str;
33
+ }
34
+ return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
35
+ }
36
+
37
+ /**
38
+ * Parses a length value, adding "px" if it's a number.
39
+ * @example
40
+ * parseLengthValue(16); // "16px"
41
+ * parseLengthValue("2em"); // "2em"
42
+ */
43
+ export function parseLengthValue(value: string | number) {
44
+ if (typeof value === "string") return value;
45
+ return `${value}px`;
46
+ }
47
+
48
+ /**
49
+ * Parses a CSS style string into a StyleValue object.
50
+ * @example
51
+ * htmlStyleToStyleValue("background-color: red; font-size: 16px;");
52
+ * // { backgroundColor: "red", fontSize: "16px" }
53
+ */
54
+ export function htmlStyleToStyleValue(styleString: string) {
55
+ if (!styleString) return {};
56
+
57
+ const result: StyleValue = {};
58
+ const declarations = styleString.split(";");
59
+
60
+ for (const declaration of declarations) {
61
+ const trimmed = declaration.trim();
62
+ if (!trimmed) continue;
63
+
64
+ const colonIndex = trimmed.indexOf(":");
65
+ if (colonIndex === -1) continue;
66
+
67
+ const property = trimmed.slice(0, colonIndex).trim();
68
+ const value = trimmed.slice(colonIndex + 1).trim();
69
+ if (!property) continue;
70
+ if (!value) continue;
71
+
72
+ const camelProperty = hyphenToCamel(property) as any;
73
+ result[camelProperty] = value;
74
+ }
75
+
76
+ return result;
77
+ }
78
+
79
+ /**
80
+ * Converts a hyphenated style object to a camelCase StyleValue object.
81
+ * @example
82
+ * htmlObjStyleToStyleValue({ "background-color": "red", "font-size": "16px" });
83
+ * // { backgroundColor: "red", fontSize: "16px" }
84
+ */
85
+ export function htmlObjStyleToStyleValue(style: HTMLCSSProperties) {
86
+ const result: StyleValue = {};
87
+ for (const [key, value] of Object.entries(style)) {
88
+ if (value == null) continue;
89
+ const property = hyphenToCamel(key) as any;
90
+ result[property] = parseLengthValue(value);
91
+ }
92
+ return result;
93
+ }
94
+
95
+ /**
96
+ * Converts a camelCase style object to a StyleValue object.
97
+ * @example
98
+ * jsxStyleToStyleValue({ backgroundColor: "red", fontSize: 16 });
99
+ * // { backgroundColor: "red", fontSize: "16px" }
100
+ */
101
+ export function jsxStyleToStyleValue(style: JSXCSSProperties) {
102
+ const result: StyleValue = {};
103
+ for (const [key, value] of Object.entries(style)) {
104
+ if (value == null) continue;
105
+ result[key as any] = parseLengthValue(value);
106
+ }
107
+ return result;
108
+ }
109
+
110
+ /**
111
+ * Converts a StyleValue object to a CSS style string.
112
+ * @example
113
+ * styleValueToHTMLStyle({ backgroundColor: "red", fontSize: "16px" });
114
+ * // "background-color: red; font-size: 16px;"
115
+ */
116
+ export function styleValueToHTMLStyle(style: StyleValue): string {
117
+ const parts: string[] = [];
118
+ for (const [key, value] of Object.entries(style)) {
119
+ if (value == null) continue;
120
+ parts.push(`${camelToHyphen(key)}: ${value}`);
121
+ }
122
+ if (!parts.length) return "";
123
+ return `${parts.join("; ")};`;
124
+ }
125
+
126
+ /**
127
+ * Converts a StyleValue object to a hyphenated style object.
128
+ * @example
129
+ * styleValueToHTMLObjStyle({ backgroundColor: "red", fontSize: "16px" });
130
+ * // { "background-color": "red", "font-size": "16px" }
131
+ */
132
+ export function styleValueToHTMLObjStyle(style: StyleValue) {
133
+ const result: CSS.PropertiesHyphen = {};
134
+ for (const [key, value] of Object.entries(style)) {
135
+ if (value == null) continue;
136
+ const property = camelToHyphen(key) as keyof HTMLCSSProperties;
137
+ result[property] = value;
138
+ }
139
+ return result;
140
+ }
141
+
142
+ /**
143
+ * Converts a StyleValue object to a camelCase style object.
144
+ * @example
145
+ * styleValueToJSXStyle({ backgroundColor: "red", fontSize: "16px" });
146
+ * // { backgroundColor: "red", fontSize: "16px" }
147
+ */
148
+ export function styleValueToJSXStyle(style: StyleValue) {
149
+ return style as JSXCSSProperties;
150
+ }
151
+
152
+ /**
153
+ * Type guard to check if a style object has hyphenated keys.
154
+ * @example
155
+ * isHTMLObjStyle({ "background-color": "red" }); // true
156
+ * isHTMLObjStyle({ backgroundColor: "red" }); // false
157
+ */
158
+ export function isHTMLObjStyle(
159
+ style: CSS.Properties<any> | CSS.PropertiesHyphen<any>,
160
+ ): style is CSS.PropertiesHyphen {
161
+ return Object.keys(style).some(
162
+ (key) => key.includes("-") && !key.startsWith("--"),
163
+ );
164
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./.tsc"
5
+ },
6
+ "include": ["src"]
7
+ }