Skip to content

Commit 0fcc79b

Browse files
committed
Feat: defineRules - funtional properties and shortcuts
1 parent 7396e4f commit 0fcc79b

File tree

4 files changed

+225
-26
lines changed

4 files changed

+225
-26
lines changed

.changeset/six-coins-ask.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@mincho-js/css": minor
3+
---
4+
5+
- `defineRules` - functional properties and shorcuts

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"vue"
2929
],
3030
"cSpell.words": [
31+
"bivariance",
3132
"Classable",
3233
"codegen",
3334
"elif",

packages/css/src/defineRules/index.ts

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,17 @@ function applyProperty<
125125
) {
126126
const propDef = ctx.properties?.[prop as keyof Properties];
127127

128+
if (typeof propDef === "function") {
129+
const result = propDef(value);
130+
if (isPlainObject(result)) {
131+
Object.assign(out, result);
132+
return;
133+
} else {
134+
(out as Record<string, unknown>)[prop] = result;
135+
return;
136+
}
137+
}
138+
128139
// just assign => last one wins
129140
if (isPlainObject(propDef) === false) {
130141
(out as Record<string, unknown>)[prop] = value;
@@ -178,13 +189,12 @@ function applyShortcut<
178189
return;
179190
}
180191

181-
// TODO: fn shortcut support
182-
// if (typeof def === "function") {
183-
// // fn shortcut
184-
// const produced = def(value);
185-
// applyInput(ctx, out, produced, nextStack);
186-
// return;
187-
// }
192+
if (typeof def === "function") {
193+
// fn shortcut
194+
const produced = def(value);
195+
applyInput(ctx, out, produced, nextStack);
196+
return;
197+
}
188198

189199
if (isPlainObject(def)) {
190200
// fixed style shortcut
@@ -295,6 +305,42 @@ if (import.meta.vitest) {
295305
expect(css.raw({ margin: 8 })).toEqual({ margin: 8 });
296306
});
297307

308+
it("Function values for CSS properties", () => {
309+
const { css } = defineRules({
310+
properties: {
311+
color(arg: "primary" | "secondary") {
312+
if (arg === "primary") {
313+
return "blue";
314+
} else {
315+
return "gray";
316+
}
317+
},
318+
otherColor(arg: "primary" | "secondary") {
319+
if (arg === "primary") {
320+
return { color: "red" } as const;
321+
} else {
322+
return { color: "green" } as const;
323+
}
324+
}
325+
}
326+
});
327+
328+
expect(
329+
css.raw({
330+
color: "primary"
331+
})
332+
).toEqual({
333+
color: "blue"
334+
});
335+
expect(
336+
css.raw({
337+
otherColor: "secondary"
338+
})
339+
).toEqual({
340+
color: "green"
341+
});
342+
});
343+
298344
it("Last one wins for properties", () => {
299345
const { css } = defineRules({
300346
properties: {
@@ -462,6 +508,31 @@ if (import.meta.vitest) {
462508
display: "inline"
463509
});
464510
});
511+
512+
it("Function shortcut", () => {
513+
const { css } = defineRules({
514+
properties: {
515+
display: ["none", "inline", "block"],
516+
paddingLeft: [0, 4, 8],
517+
paddingRight: [0, 4, 8]
518+
},
519+
shortcuts: {
520+
px: ["paddingLeft", "paddingRight"],
521+
center(arg: "none" | "inline" | "block") {
522+
return {
523+
display: arg,
524+
px: 4
525+
};
526+
}
527+
}
528+
});
529+
530+
expect(css.raw({ center: "inline" })).toEqual({
531+
display: "inline",
532+
paddingLeft: 4,
533+
paddingRight: 4
534+
});
535+
});
465536
});
466537
});
467538
}

packages/css/src/defineRules/types.ts

Lines changed: 141 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,29 @@ import type {
66

77
type CSSPropertiesKeys = keyof CSSProperties;
88

9+
type DefineRulesResolver<
10+
Out = CSSPropertiesWithVars | undefined,
11+
Arg = unknown
12+
> = {
13+
bivarianceHack(arg: Arg): Out;
14+
}["bivarianceHack"];
15+
916
type DefineRulesCssProperties = {
10-
[Property in CSSPropertiesKeys]?:
11-
| ReadonlyArray<CSSProperties[Property]>
12-
| Record<string, CSSProperties[Property] | CSSPropertiesWithVars>
13-
| true
14-
| false;
17+
[Property in CSSPropertiesKeys]?: DefineRulesCssPropertiesValue<
18+
CSSProperties[Property]
19+
>;
1520
};
21+
type DefineRulesCssPropertiesValue<Value> =
22+
| ReadonlyArray<Value>
23+
| Record<string, Value | CSSPropertiesWithVars>
24+
| DefineRulesResolver<Value>
25+
| true
26+
| false;
27+
1628
type DefineRulesCustomProperties = Partial<
1729
Record<
1830
Exclude<NonNullableString, CSSPropertiesKeys>,
19-
Record<string, CSSPropertiesWithVars>
31+
Record<string, CSSPropertiesWithVars> | DefineRulesResolver
2032
>
2133
>;
2234
export type DefineRulesProperties =
@@ -31,7 +43,8 @@ type ShortcutValue<
3143
| keyof Properties
3244
| Exclude<keyof Shortcuts, ShortcutsKey>
3345
| ReadonlyArray<keyof Properties | Exclude<keyof Shortcuts, ShortcutsKey>>
34-
| DefineRulesCssInput<Properties, Shortcuts>;
46+
| DefineRulesCssInput<Properties, Shortcuts>
47+
| DefineRulesResolver<DefineRulesCssInput<Properties, Shortcuts>>;
3548

3649
export type DefineRulesShortcuts<
3750
Properties extends DefineRulesProperties,
@@ -59,15 +72,17 @@ type PropertiesInput<Properties extends DefineRulesProperties> = {
5972
type ResolvePropertiesValue<Key, Value> =
6073
Value extends ReadonlyArray<infer Item>
6174
? Item
62-
: true extends Value
63-
? Key extends CSSPropertiesKeys
64-
? CSSProperties[Key]
65-
: never
66-
: false extends Value
67-
? never
68-
: Value extends Record<infer StyleObjectKey, unknown>
69-
? StyleObjectKey
70-
: never;
75+
: Value extends (arg: infer Arg) => unknown
76+
? Arg
77+
: true extends Value
78+
? Key extends CSSPropertiesKeys
79+
? CSSProperties[Key]
80+
: never
81+
: false extends Value
82+
? never
83+
: Value extends Record<infer StyleObjectKey, unknown>
84+
? StyleObjectKey
85+
: never;
7186

7287
type ShortcutsInput<
7388
Properties extends Record<string, unknown>,
@@ -86,9 +101,11 @@ type ResolveShortcutValue<
86101
Value
87102
> = Value extends readonly unknown[]
88103
? ResolveShortcutArrayRef<Properties, Shortcuts, Value>
89-
: Value extends Record<string, unknown>
90-
? boolean
91-
: ResolveShortcutRef<Properties, Shortcuts, Value>;
104+
: Value extends (arg: infer Arg) => unknown
105+
? Arg
106+
: Value extends Record<string, unknown>
107+
? boolean
108+
: ResolveShortcutRef<Properties, Shortcuts, Value>;
92109

93110
type ResolveShortcutArrayRef<
94111
Properties extends Record<string, unknown>,
@@ -295,6 +312,68 @@ if (import.meta.vitest) {
295312
backgroundOpacity: "quarter"
296313
});
297314
});
315+
316+
it("Function values return CSS properties", () => {
317+
const { rules, _props } = resolveDefineRules({
318+
properties: {
319+
color(arg: "primary" | "secondary") {
320+
if (arg === "primary") {
321+
return { color: "blue" } as const;
322+
} else {
323+
return { color: "gray" } as const;
324+
}
325+
}
326+
}
327+
});
328+
assertType<{
329+
properties?: {
330+
color: (arg: "primary" | "secondary") =>
331+
| {
332+
color: string;
333+
}
334+
| undefined;
335+
};
336+
}>(rules);
337+
338+
assertType<typeof _props>({
339+
color: "primary"
340+
});
341+
});
342+
343+
it("Function values return Style objects", () => {
344+
const { rules, _props } = resolveDefineRules({
345+
properties: {
346+
color(arg: "primary" | "secondary") {
347+
if (arg === "primary") {
348+
return "blue";
349+
} else {
350+
return "gray";
351+
}
352+
},
353+
otherColor(arg: "primary" | "secondary") {
354+
if (arg === "primary") {
355+
return { color: "red" } as const;
356+
} else {
357+
return { color: "green" } as const;
358+
}
359+
}
360+
}
361+
});
362+
assertType<{
363+
properties?: {
364+
color: (arg: "primary" | "secondary") => "blue" | "gray";
365+
otherColor: (arg: "primary" | "secondary") =>
366+
| {
367+
color: string;
368+
}
369+
| undefined;
370+
};
371+
}>(rules);
372+
373+
assertType<typeof _props>({
374+
color: "primary"
375+
});
376+
});
298377
});
299378

300379
describe.concurrent("DefineRulesShortcuts Type", () => {
@@ -462,6 +541,49 @@ if (import.meta.vitest) {
462541
});
463542
assertType<typeof _props>(["inline"]);
464543
});
544+
545+
it("Function shortcut", () => {
546+
const { rules, _props } = resolveDefineRules({
547+
properties: {
548+
display: ["none", "inline", "block"],
549+
paddingLeft: [0, 4, 8],
550+
paddingRight: [0, 4, 8]
551+
},
552+
shortcuts: {
553+
px: ["paddingLeft", "paddingRight"],
554+
center(arg: "none" | "inline" | "block") {
555+
return {
556+
display: arg,
557+
px: 4
558+
};
559+
}
560+
}
561+
});
562+
assertType<{
563+
properties?: {
564+
display: readonly ["none", "inline", "block"];
565+
paddingLeft: readonly [0, 4, 8];
566+
paddingRight: readonly [0, 4, 8];
567+
};
568+
shortcuts?: {
569+
px: readonly ["paddingLeft", "paddingRight"];
570+
center: (arg: "none" | "inline" | "block") =>
571+
| {
572+
display: "none" | "inline" | "block";
573+
px: number;
574+
}
575+
| undefined;
576+
};
577+
}>(rules);
578+
579+
assertType<typeof _props>({
580+
center: "inline"
581+
});
582+
assertType<typeof _props>({
583+
// @ts-expect-error: invalid value
584+
center: "flex"
585+
});
586+
});
465587
});
466588

467589
describe.concurrent("Invalid Type Cases", () => {

0 commit comments

Comments
 (0)