@paganaye/stylets 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/README.md +13 -0
- package/dist/BaseStyles.d.ts +44 -0
- package/dist/BaseStyles.js +166 -0
- package/dist/Button.d.ts +69 -0
- package/dist/Button.js +56 -0
- package/dist/CssColor.d.ts +44 -0
- package/dist/CssColor.js +196 -0
- package/dist/CssColor.test.d.ts +1 -0
- package/dist/CssColor.test.js +68 -0
- package/dist/CssExpr.d.ts +9 -0
- package/dist/CssExpr.js +35 -0
- package/dist/CssFilter.d.ts +18 -0
- package/dist/CssFilter.js +40 -0
- package/dist/CssNum.d.ts +30 -0
- package/dist/CssNum.js +106 -0
- package/dist/CssNum.test.d.ts +1 -0
- package/dist/CssNum.test.js +30 -0
- package/dist/CssReset.d.ts +1 -0
- package/dist/CssReset.js +15 -0
- package/dist/CssShadow.d.ts +19 -0
- package/dist/CssShadow.js +42 -0
- package/dist/DefaultTheme.d.ts +14 -0
- package/dist/DefaultTheme.js +6 -0
- package/dist/EmptyTheme.d.ts +14 -0
- package/dist/EmptyTheme.js +2 -0
- package/dist/HTML.d.ts +9 -0
- package/dist/HTML.jsx +7 -0
- package/dist/ScopeStyles.d.ts +24 -0
- package/dist/ScopeStyles.js +67 -0
- package/dist/State.d.ts +36 -0
- package/dist/State.js +107 -0
- package/dist/State.test.d.ts +1 -0
- package/dist/State.test.js +41 -0
- package/dist/StyleWriter.d.ts +25 -0
- package/dist/StyleWriter.js +156 -0
- package/dist/Theme.d.ts +56 -0
- package/dist/Theme.js +29 -0
- package/dist/Tone.d.ts +1 -0
- package/dist/Tone.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +9 -0
- package/dist/props.d.ts +7 -0
- package/dist/props.js +104 -0
- package/dist/types.d.ts +239 -0
- package/dist/types.js +1 -0
- package/package.json +36 -0
- package/src/BaseStyles.ts +192 -0
- package/src/Button.ts +68 -0
- package/src/CssColor.test.ts +82 -0
- package/src/CssColor.ts +175 -0
- package/src/CssExpr.ts +25 -0
- package/src/CssFilter.ts +44 -0
- package/src/CssNum.test.ts +37 -0
- package/src/CssNum.ts +93 -0
- package/src/CssReset.ts +17 -0
- package/src/CssShadow.ts +46 -0
- package/src/DefaultTheme.ts +8 -0
- package/src/HTML.tsx +17 -0
- package/src/ScopeStyles.ts +100 -0
- package/src/State.test.ts +47 -0
- package/src/State.ts +164 -0
- package/src/StyleWriter.ts +163 -0
- package/src/Theme.ts +95 -0
- package/src/Tone.ts +1 -0
- package/src/index.test.ts +10 -0
- package/src/index.ts +1 -0
- package/src/props.ts +118 -0
- package/src/types.ts +311 -0
package/src/CssColor.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { type Num } from "./CssNum";
|
|
2
|
+
import { CssExpr, customCss } from "./CssExpr";
|
|
3
|
+
|
|
4
|
+
type IPropAlteration = Num | { expr?: Num, mul?: Num, add?: Num, min?: Num, max?: Num };
|
|
5
|
+
type IColorAlterations = { l?: IPropAlteration, c?: IPropAlteration, h?: IPropAlteration };
|
|
6
|
+
|
|
7
|
+
export abstract class CssColor extends CssExpr {
|
|
8
|
+
abstract toString(): string;
|
|
9
|
+
static alpha = alpha
|
|
10
|
+
static contrast = contrast
|
|
11
|
+
static complement = complement
|
|
12
|
+
static triadic = triadic
|
|
13
|
+
static analogous = analogous
|
|
14
|
+
static alter = alter
|
|
15
|
+
static lch = lch
|
|
16
|
+
static mix = mix
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class CssColorVar extends CssColor {
|
|
20
|
+
constructor(public name: string) { super('none', 'color'); }
|
|
21
|
+
toString(): string { return `var(--${this.name})`; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class CssCustomColorExpr extends CssColor {
|
|
25
|
+
constructor(public expr: string) { super('none', 'color'); }
|
|
26
|
+
toString(): string { return this.expr; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class LchColor extends CssColor {
|
|
30
|
+
constructor(public readonly l: number, public readonly c: number, public readonly h: number) { super('none', 'color') }
|
|
31
|
+
toString(): string { return `oklch(${this.l} ${this.c} ${this.h})`; }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class PropAlteration extends CssColor {
|
|
35
|
+
constructor(readonly prop: string, readonly alt?: IPropAlteration) { super('none', 'color'); }
|
|
36
|
+
|
|
37
|
+
toString(): string {
|
|
38
|
+
let alt = this.alt;
|
|
39
|
+
if (!alt) return this.prop;
|
|
40
|
+
|
|
41
|
+
if (typeof alt === 'number') return String(alt);
|
|
42
|
+
if (alt instanceof CssExpr || alt instanceof CssColor) return alt.toString();
|
|
43
|
+
|
|
44
|
+
let p = (val?: Num) => {
|
|
45
|
+
if (val === undefined) return undefined;
|
|
46
|
+
if (typeof val == 'number') return String(val);
|
|
47
|
+
return val.toString();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let expr = p((alt as any).expr) ?? this.prop;
|
|
51
|
+
|
|
52
|
+
const mul = p((alt as any).mul);
|
|
53
|
+
const add = p((alt as any).add);
|
|
54
|
+
const min = p((alt as any).min);
|
|
55
|
+
const max = p((alt as any).max);
|
|
56
|
+
// Handle multiplication and addition
|
|
57
|
+
if (mul && add) {
|
|
58
|
+
expr = `calc((${expr} * ${mul}) + ${add})`;
|
|
59
|
+
} else if (mul) {
|
|
60
|
+
expr = `calc(${expr} * ${mul})`;
|
|
61
|
+
} else if (add) {
|
|
62
|
+
expr = `calc(${expr} + ${add})`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Handle clamping
|
|
66
|
+
if ('min' in (alt as any) && 'max' in (alt as any)) {
|
|
67
|
+
expr = `clamp(${min}, ${expr}, ${max})`;
|
|
68
|
+
}
|
|
69
|
+
else if ('min' in (alt as any)) {
|
|
70
|
+
expr = `max(${min}, ${expr})`;
|
|
71
|
+
}
|
|
72
|
+
else if ('max' in (alt as any)) {
|
|
73
|
+
expr = `min(${expr}, ${max})`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return expr;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class AlterColor extends CssColor {
|
|
81
|
+
l: PropAlteration;
|
|
82
|
+
c: PropAlteration;
|
|
83
|
+
h: PropAlteration;
|
|
84
|
+
|
|
85
|
+
constructor(public base: CssColor, alterations: IColorAlterations) {
|
|
86
|
+
super('none', 'color');
|
|
87
|
+
this.l = new PropAlteration('l', alterations.l);
|
|
88
|
+
this.c = new PropAlteration('c', alterations.c);
|
|
89
|
+
this.h = new PropAlteration('h', alterations.h);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
toString(): string {
|
|
93
|
+
const l = this.l.toString();
|
|
94
|
+
const c = this.c.toString();
|
|
95
|
+
const h = this.h.toString();
|
|
96
|
+
return `oklch(from ${this.base.toString()} ${l} ${c} ${h})`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class MixColor extends CssColor {
|
|
101
|
+
constructor(public base: CssColor, public other: CssColor, public ratio: number | CssExpr) { super('none', 'color'); }
|
|
102
|
+
|
|
103
|
+
toString(): string {
|
|
104
|
+
let percentA: string;
|
|
105
|
+
let percentB: string;
|
|
106
|
+
if (typeof this.ratio === 'number') {
|
|
107
|
+
if (this.ratio <= 0) return this.base.toString();
|
|
108
|
+
if (this.ratio >= 1) return this.other.toString();
|
|
109
|
+
percentA = `${Math.round(100 - this.ratio * 100)}%`;
|
|
110
|
+
percentB = `${Math.round(this.ratio * 100)}%`;
|
|
111
|
+
} else {
|
|
112
|
+
percentA = `calc(100% - ${this.ratio})`;
|
|
113
|
+
percentB = this.ratio.toString();
|
|
114
|
+
}
|
|
115
|
+
return `mix(${this.base.toString()} ${percentA}, ${this.other.toString()}, ${percentB})`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
class AlphaColor extends CssColor {
|
|
120
|
+
color: CssColor;
|
|
121
|
+
opacity: number | CssExpr;
|
|
122
|
+
constructor(color: CssColor, opacity: number | CssExpr) { super('none', 'color'); this.color = color; this.opacity = opacity; }
|
|
123
|
+
toString(): string { const op = typeof this.opacity === 'number' ? String(this.opacity) : this.opacity.toString(); return `color(${this.color.toString()} / ${op})`; }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
class ContrastColor extends CssColor {
|
|
127
|
+
color: CssColor;
|
|
128
|
+
constructor(color: CssColor) { super('none', 'color'); this.color = color; }
|
|
129
|
+
toString(): string { if (this.color instanceof LchColor) { return this.color.l > 0.5 ? 'black' : 'white'; } return `color-contrast(${this.color.toString()} vs(black, white))`; }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
class ComplementColor extends CssColor {
|
|
133
|
+
color: CssColor;
|
|
134
|
+
constructor(color: CssColor) { super('none', 'color'); this.color = color; }
|
|
135
|
+
toString(): string { if (this.color instanceof LchColor) { const newH = (this.color.h + 180) % 360; return lch(this.color.l, this.color.c, newH).toString(); } return `oklch(from ${this.color.toString()} l c calc((h + 180) % 360))`; }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
class TriadicColor extends CssColor {
|
|
139
|
+
color: CssColor;
|
|
140
|
+
index: 0 | 1 | 2;
|
|
141
|
+
constructor(color: CssColor, index: 0 | 1 | 2 = 0) { super('none', 'color'); this.color = color; this.index = index; }
|
|
142
|
+
toString(): string { const offset = this.index === 0 ? 0 : (this.index === 1 ? 120 : 240); if (this.color instanceof LchColor) { const newH = (this.color.h + offset) % 360; return lch(this.color.l, this.color.c, newH).toString(); } return `oklch(from ${this.color.toString()} l c calc((h + ${offset}) % 360))`; }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
class AnalogousColor extends CssExpr {
|
|
146
|
+
color: CssColor;
|
|
147
|
+
angle: number | CssExpr;
|
|
148
|
+
constructor(color: CssColor, angle: number | CssExpr = 30) { super('none', 'color'); this.color = color; this.angle = angle; }
|
|
149
|
+
toString(): string {
|
|
150
|
+
if (this.color instanceof LchColor) {
|
|
151
|
+
if (typeof this.angle === 'number') {
|
|
152
|
+
const h1 = (this.color.h + this.angle) % 360;
|
|
153
|
+
const h2 = (this.color.h + 360 - this.angle) % 360;
|
|
154
|
+
return `${lch(this.color.l, this.color.c, h1).toString()}, ${lch(this.color.l, this.color.c, h2).toString()}`;
|
|
155
|
+
}
|
|
156
|
+
const aStr = this.angle.toString();
|
|
157
|
+
return `${this.color.toString()}, oklch(l c calc((h + ${aStr}) % 360)), oklch(l c calc((h - ${aStr} + 360) % 360))`;
|
|
158
|
+
}
|
|
159
|
+
const aStr = typeof this.angle === 'number' ? String(this.angle) : this.angle.toString();
|
|
160
|
+
return `${this.color.toString()}, oklch(l c calc((h + ${aStr}) % 360)), oklch(l c calc((h - ${aStr} + 360) % 360))`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function alpha(color: CssExpr, opacity: number | CssExpr): string { return new AlphaColor(color, opacity).toString(); }
|
|
165
|
+
export function contrast(color: CssExpr): string { return new ContrastColor(color).toString(); }
|
|
166
|
+
export function complement(color: CssExpr): string { return new ComplementColor(color).toString(); }
|
|
167
|
+
export function triadic(color: CssExpr, index: 0 | 1 | 2 = 0): string { return new TriadicColor(color, index).toString(); }
|
|
168
|
+
export function analogous(color: CssExpr, angle: number = 30): string { return new AnalogousColor(color, angle).toString(); }
|
|
169
|
+
export function alter(base: CssExpr, alterations: IColorAlterations): string { return new AlterColor(base, alterations).toString(); }
|
|
170
|
+
export function lch(l: number, c: number, h: number): LchColor { return new LchColor(l, c, h); }
|
|
171
|
+
export function mix(base: CssExpr, other: CssExpr, ratio: number | CssExpr): string { return new MixColor(base, other, ratio).toString(); }
|
|
172
|
+
|
|
173
|
+
export const l = customCss('l', 'none', 'length');
|
|
174
|
+
export const c = customCss('c', 'none', 'length');
|
|
175
|
+
export const h = customCss('h', 'none', 'angle');
|
package/src/CssExpr.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CssUnit, CssType } from "./CssNum";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export abstract class CssExpr {
|
|
5
|
+
constructor(readonly unit: CssUnit, readonly type: CssType) { }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
class CssVar extends CssExpr {
|
|
9
|
+
constructor(readonly varName: string, unit: CssUnit, type: CssType) { super('%', 'unknown'); }
|
|
10
|
+
toString(): string { return `var(--${this.varName})`; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class CssCustomExpr extends CssExpr {
|
|
14
|
+
constructor(public expr: string, unit: CssUnit, type: CssType) { super(unit, type); }
|
|
15
|
+
toString(): string { return this.expr; }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class CssArg extends CssExpr {
|
|
19
|
+
constructor(readonly argName: string, unit: CssUnit, type: CssType) { super(unit, type); }
|
|
20
|
+
toString(): string { return this.argName; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function customCss(expr: string, unit: CssUnit = 'unknown', type: CssType = 'unknown'): CssExpr { return new CssCustomExpr(expr, unit, type); }
|
|
24
|
+
export function arg(name: string, unit: CssUnit = 'unknown', type: CssType = 'unknown'): CssExpr { return new CssArg(name, unit, type); }
|
|
25
|
+
export function v(name: string, unit: CssUnit = 'unknown', type: CssType = 'unknown'): CssExpr { return new CssVar(name, unit, type); }
|
package/src/CssFilter.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CssExpr } from "./CssExpr";
|
|
2
|
+
import { Num,numToStr } from "./CssNum";
|
|
3
|
+
|
|
4
|
+
export class CssFilter extends CssExpr {
|
|
5
|
+
|
|
6
|
+
constructor(readonly options: FilterOptions | FilterOptions[]) {
|
|
7
|
+
super('none', 'filter');
|
|
8
|
+
}
|
|
9
|
+
toString(): string {
|
|
10
|
+
const serializeOne = (opt: FilterOptions) => {
|
|
11
|
+
const result: string[] = [];
|
|
12
|
+
if (opt.blur !== undefined) result.push(`blur(${numToStr(opt.blur)})`);
|
|
13
|
+
if (opt.brightness !== undefined) result.push(`brightness(${numToStr(opt.brightness)})`);
|
|
14
|
+
if (opt.contrast !== undefined) result.push(`contrast(${numToStr(opt.contrast)})`);
|
|
15
|
+
if (opt.hueRotate !== undefined) result.push(`hue-rotate(${numToStr(opt.hueRotate)})`);
|
|
16
|
+
if (opt.saturate !== undefined) result.push(`saturate(${numToStr(opt.saturate)})`);
|
|
17
|
+
if (opt.invert !== undefined) result.push(`invert(${numToStr(opt.invert)})`);
|
|
18
|
+
if (opt.grayscale !== undefined) result.push(`grayscale(${numToStr(opt.grayscale)})`);
|
|
19
|
+
if (opt.sepia !== undefined) result.push(`sepia(${numToStr(opt.sepia)})`);
|
|
20
|
+
if (opt.opacity !== undefined) result.push(`opacity(${numToStr(opt.opacity)})`);
|
|
21
|
+
return result.join(' ');
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(this.options)) {
|
|
24
|
+
const parts = this.options.map(o => serializeOne(o));
|
|
25
|
+
return parts.join(' ');
|
|
26
|
+
} else {
|
|
27
|
+
return serializeOne(this.options);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type FilterOptions = {
|
|
33
|
+
blur?: Num;
|
|
34
|
+
brightness?: Num;
|
|
35
|
+
contrast?: Num;
|
|
36
|
+
hueRotate?: Num;
|
|
37
|
+
saturate?: Num;
|
|
38
|
+
invert?: Num;
|
|
39
|
+
grayscale?: Num;
|
|
40
|
+
sepia?: Num;
|
|
41
|
+
opacity?: Num;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
// TODO: Update imports once CssNum is fully integrated
|
|
3
|
+
// import { mul, add, clamp, min, max, num } from './CssNum';
|
|
4
|
+
// import { l, h, c } from './CssColor';
|
|
5
|
+
// import { v, customCss } from './CssExpr';
|
|
6
|
+
|
|
7
|
+
describe('CssNumUtils', () => {
|
|
8
|
+
it.skip('numVar creates CSS var string', () => {
|
|
9
|
+
// expect(v('x').toString()).toBe('var(--x)');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it.skip('clamp/min/max produce expected strings', () => {
|
|
13
|
+
// expect(clamp(0, l, 1).toString()).toBe('clamp(0, l, 1)');
|
|
14
|
+
// expect(min(0, l, 2).toString()).toBe('min(0, l, 2)');
|
|
15
|
+
// expect(max(0, l, 2).toString()).toBe('max(0, l, 2)');
|
|
16
|
+
// expect(max(num(0, 'rem'), l, num(2, 'px')).toString()).toBe('max(0rem, l, 2px)');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it.skip('customCssExpr returns custom expression', () => {
|
|
20
|
+
// expect(customCss('calc(var(--a) + 1)').toString()).toBe('calc(var(--a) + 1)');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it.skip('num returns expected string', () => {
|
|
24
|
+
// expect(num(5, 'px').toString()).toBe('5px');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it.skip('min/max accept many args and nested expressions', () => {
|
|
28
|
+
// const nested = mul(l, 0.5);
|
|
29
|
+
// expect(clamp(0, nested, 100).toString()).toBe('clamp(0, calc(l * 0.5), 100)');
|
|
30
|
+
|
|
31
|
+
// expect(min(0, l, 2, mul(c, 3)).toString())
|
|
32
|
+
// .toBe('min(0, l, 2, calc(c * 3))');
|
|
33
|
+
|
|
34
|
+
// expect(max(0, l, 2, add(c, 1)).toString())
|
|
35
|
+
// .toBe('max(0, l, 2, calc(c + 1))');
|
|
36
|
+
});
|
|
37
|
+
});
|
package/src/CssNum.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { CssExpr } from "./CssExpr";
|
|
2
|
+
|
|
3
|
+
export type Num = number | string | CssExpr;
|
|
4
|
+
|
|
5
|
+
export type ArgVariable = 'l' | 'c' | 'h';
|
|
6
|
+
export type NumberType = 'length' | 'angle' | 'time' | 'frequency' | 'flex' | 'resolution';
|
|
7
|
+
export type LengthUnit = 'px' | 'rem' | 'em' | 'vw' | 'vh' | '%';
|
|
8
|
+
export type AngleUnit = 'deg' | 'rad';
|
|
9
|
+
export type TimeUnit = 's' | 'ms';
|
|
10
|
+
export type FrequencyUnit = 'hz' | 'khz';
|
|
11
|
+
export type FlexUnit = 'fr';
|
|
12
|
+
export type ResolutionUnit = 'dpi' | 'dpcm' | 'dppx';
|
|
13
|
+
export type CssUnit = LengthUnit | AngleUnit | TimeUnit | FrequencyUnit | FlexUnit | ResolutionUnit | 'none' | 'unknown';
|
|
14
|
+
export type CssType = NumberType | 'color' | 'shadow' | 'filter' | 'unknown';
|
|
15
|
+
|
|
16
|
+
export function numToStr(num: Num): string {
|
|
17
|
+
if (typeof num === 'number') return String(num);
|
|
18
|
+
return `${num.toString()}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
// ==== Num ===== //
|
|
23
|
+
|
|
24
|
+
abstract class NumExpr extends CssExpr {
|
|
25
|
+
static arg = numArg;
|
|
26
|
+
static mul = mul;
|
|
27
|
+
static add = add;
|
|
28
|
+
static clamp = clamp;
|
|
29
|
+
static min = min;
|
|
30
|
+
static max = max;
|
|
31
|
+
static new = num;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class LitteralNumExpr extends NumExpr {
|
|
35
|
+
constructor(readonly value: number, readonly unit: CssUnit, type: CssType) { super(unit, type); }
|
|
36
|
+
toString(): string { return `${this.value}${this.unit ?? ''}`; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class CssNumArg extends NumExpr {
|
|
40
|
+
constructor(readonly argName: string, unit: CssUnit, type: CssType) { super(unit, type); }
|
|
41
|
+
toString(): string { return this.argName; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class MulExpr extends NumExpr {
|
|
46
|
+
constructor(public expr: NumExpr, public factor: number | NumExpr) { super('unknown', 'unknown'); }
|
|
47
|
+
toString(): string {
|
|
48
|
+
const f = numToStr(this.factor);
|
|
49
|
+
return `calc(${numToStr(this.expr)} * ${f})`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class AddExpr extends NumExpr {
|
|
54
|
+
constructor(public expr: NumExpr, public addend: number | NumExpr) { super('unknown', 'unknown'); }
|
|
55
|
+
toString(): string {
|
|
56
|
+
const a = numToStr(this.addend);
|
|
57
|
+
return `calc(${numToStr(this.expr)} + ${a})`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class ClampExpr extends NumExpr {
|
|
62
|
+
constructor(public min: number | NumExpr, public expr: NumExpr, public max: number | NumExpr) { super('unknown', 'unknown'); }
|
|
63
|
+
toString(): string {
|
|
64
|
+
const mn = numToStr(this.min);
|
|
65
|
+
const ex = numToStr(this.expr);
|
|
66
|
+
const mx = numToStr(this.max);
|
|
67
|
+
return `clamp(${mn}, ${ex}, ${mx})`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class MinExpr extends NumExpr {
|
|
72
|
+
constructor(public args: Array<number | NumExpr>) { super('unknown', 'unknown'); }
|
|
73
|
+
toString(): string {
|
|
74
|
+
const parts = this.args.map(a => numToStr(a));
|
|
75
|
+
return `min(${parts.join(', ')})`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class MaxExpr extends NumExpr {
|
|
80
|
+
constructor(public args: Array<number | NumExpr>) { super('unknown', 'unknown'); }
|
|
81
|
+
toString(): string {
|
|
82
|
+
const parts = this.args.map(a => numToStr(a));
|
|
83
|
+
return `max(${parts.join(', ')})`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function numArg(name: string, unit: CssUnit = 'none', type: CssType = 'length'): CssExpr { return new CssNumArg(name, unit, type); }
|
|
88
|
+
export function mul(expr: NumExpr, factor: number | NumExpr): NumExpr { return new MulExpr(expr, factor); }
|
|
89
|
+
export function add(expr: NumExpr, addend: number | NumExpr): NumExpr { return new AddExpr(expr, addend); }
|
|
90
|
+
export function clamp(min: number | NumExpr, expr: NumExpr, max: number | NumExpr): NumExpr { return new ClampExpr(min, expr, max); }
|
|
91
|
+
export function min(...args: Array<number | NumExpr>): NumExpr { return new MinExpr(args); }
|
|
92
|
+
export function max(...args: Array<number | NumExpr>): NumExpr { return new MaxExpr(args); }
|
|
93
|
+
export function num(value: number, unit: CssUnit = 'unknown', type: CssType = 'unknown'): NumExpr { return new LitteralNumExpr(value, unit, type); }
|
package/src/CssReset.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { addStyles } from "./StyleWriter";
|
|
2
|
+
|
|
3
|
+
addStyles("css-reset", (builder) => {
|
|
4
|
+
builder.addLines([`/* John W Comeau Modern CSS Reset */`,
|
|
5
|
+
`*, *::before, *::after { box-sizing: border-box; }`,
|
|
6
|
+
`*:not(dialog) { margin: 0; }`,
|
|
7
|
+
`@media (prefers-reduced-motion: no-preference) { html { interpolate-size: allow-keywords; } }`,
|
|
8
|
+
`body { line-height: 1.5; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; }`,
|
|
9
|
+
`img, picture, video, canvas, svg { display: block; max-width: 100%; }`,
|
|
10
|
+
`input, button, textarea, select { font: inherit; }`,
|
|
11
|
+
`p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }`,
|
|
12
|
+
`p { text-wrap: pretty; }`,
|
|
13
|
+
`h1, h2, h3, h4, h5, h6 { text-wrap: balance; }`,
|
|
14
|
+
`#root, #__next { isolation: isolate; }`,
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
17
|
+
|
package/src/CssShadow.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { CssColor } from "./CssColor";
|
|
2
|
+
import { CssExpr } from "./CssExpr";
|
|
3
|
+
import { Num } from "./CssNum";
|
|
4
|
+
|
|
5
|
+
export type BoxShadowOptions = {
|
|
6
|
+
offsetX: Num;
|
|
7
|
+
offsetY: Num;
|
|
8
|
+
blur?: Num;
|
|
9
|
+
spread?: Num;
|
|
10
|
+
color?: string | CssColor;
|
|
11
|
+
inset?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class CssShadow extends CssExpr {
|
|
15
|
+
constructor(readonly kind: 'box' | 'text', public opts: BoxShadowOptions | BoxShadowOptions[]) { super('none', 'shadow'); }
|
|
16
|
+
toString(): string {
|
|
17
|
+
let serializeOne = (opt: BoxShadowOptions) => {
|
|
18
|
+
const { offsetX, offsetY, blur, spread, color, inset } = opt;
|
|
19
|
+
const parts: string[] = [];
|
|
20
|
+
// For text shadows, 'inset' and 'spread' are not valid
|
|
21
|
+
if (inset && this.kind === 'box') parts.push('inset');
|
|
22
|
+
const sx = typeof offsetX === 'string' ? offsetX : offsetX.toString();
|
|
23
|
+
const sy = typeof offsetY === 'string' ? offsetY : offsetY.toString();
|
|
24
|
+
parts.push(sx, sy);
|
|
25
|
+
if (blur !== undefined) parts.push(typeof blur === 'string' ? blur : blur.toString());
|
|
26
|
+
if (spread !== undefined && this.kind === 'box') parts.push(typeof spread === 'string' ? spread : spread.toString());
|
|
27
|
+
if (color) parts.push(typeof color === 'string' ? color : color.toString());
|
|
28
|
+
return parts.join(' ');
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (Array.isArray(this.opts)) {
|
|
32
|
+
const parts = this.opts.map(o => serializeOne(o));
|
|
33
|
+
return parts.join(', ');
|
|
34
|
+
} else {
|
|
35
|
+
return serializeOne(this.opts);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function textShadow(options: BoxShadowOptions | BoxShadowOptions[]): CssShadow {
|
|
41
|
+
return new CssShadow('text', options);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function boxShadow(options: BoxShadowOptions | BoxShadowOptions[]): CssShadow {
|
|
45
|
+
return new CssShadow('box', options);
|
|
46
|
+
}
|
package/src/HTML.tsx
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { prepareProps, completeProps, Elt } from "./props";
|
|
2
|
+
import { CssProperties, ThemeClass, ElementPropsRecord } from "./types";
|
|
3
|
+
import { theme } from "./DefaultTheme";
|
|
4
|
+
|
|
5
|
+
export interface IDivProps {
|
|
6
|
+
class?: ThemeClass<typeof theme>;
|
|
7
|
+
style?: CssProperties;
|
|
8
|
+
gap?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Div(props: IDivProps, children: any[]) {
|
|
12
|
+
return Elt('div', props, children);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function P(props: IDivProps, children: any[]) {
|
|
16
|
+
return Elt('p', props, children);
|
|
17
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ThemeBuilder } from "./Theme";
|
|
2
|
+
import { BaseStyles } from "./BaseStyles";
|
|
3
|
+
import { IThemeParts, IScopeStylesParts, MergeThemeArgs, ThemeArgs, Identity, Simplify } from "./types";
|
|
4
|
+
|
|
5
|
+
export class ScopeStylesBuilder<
|
|
6
|
+
A extends ThemeArgs,
|
|
7
|
+
V extends A['variants'] = A['variants']
|
|
8
|
+
> extends BaseStyles<A> {
|
|
9
|
+
readonly class: string;
|
|
10
|
+
scopeStart: string;
|
|
11
|
+
scopeEnd: string | undefined;
|
|
12
|
+
|
|
13
|
+
parent: ThemeBuilder<any>;
|
|
14
|
+
constructor(parent: ThemeBuilder<any>, themeParts: IScopeStylesParts<A> & {
|
|
15
|
+
class: string;
|
|
16
|
+
scopeStart: string;
|
|
17
|
+
scopeEnd?: string;
|
|
18
|
+
}) {
|
|
19
|
+
// Merge all parent properties into scope
|
|
20
|
+
const mergedParts = {
|
|
21
|
+
...themeParts,
|
|
22
|
+
name: `@scope ${themeParts.class}`,
|
|
23
|
+
colors: { ...parent.colors, ...themeParts.colors },
|
|
24
|
+
vars: { ...parent.vars, ...themeParts.vars },
|
|
25
|
+
paddings: { ...parent.paddings, ...themeParts.paddings },
|
|
26
|
+
margins: { ...parent.margins, ...themeParts.margins },
|
|
27
|
+
borders: { ...parent.borders, ...themeParts.borders },
|
|
28
|
+
backgrounds: { ...parent.backgrounds, ...themeParts.backgrounds },
|
|
29
|
+
typography: { ...parent.typography, ...themeParts.typography },
|
|
30
|
+
layouts: { ...parent.layouts, ...themeParts.layouts },
|
|
31
|
+
shadows: { ...parent.shadows, ...themeParts.shadows },
|
|
32
|
+
classes: { ...parent.classes, ...themeParts.classes },
|
|
33
|
+
variants: { ...parent.variants, ...themeParts.variants }
|
|
34
|
+
};
|
|
35
|
+
super(mergedParts as any);
|
|
36
|
+
this.parent = parent;
|
|
37
|
+
this.class = themeParts.class ?? '';
|
|
38
|
+
this.scopeStart = themeParts.scopeStart ?? `.${themeParts.class}`;
|
|
39
|
+
this.scopeEnd = themeParts.scopeEnd;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// overloads to infer Added from literal or callback
|
|
43
|
+
add<P extends IScopeStylesParts<any>>(parts: P): ScopeStylesBuilder<MergeThemeArgs<A, P>, MergeThemeArgs<A, P>['variants']>;
|
|
44
|
+
add<P extends (theme: ThemeBuilder<A>) => IScopeStylesParts<any>>(parts: P): ScopeStylesBuilder<MergeThemeArgs<A, ReturnType<P>>, MergeThemeArgs<A, ReturnType<P>>['variants']>;
|
|
45
|
+
|
|
46
|
+
add(parts: any): any {
|
|
47
|
+
if (typeof parts === 'function') parts = parts(this as any);
|
|
48
|
+
super.add(parts as any);
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
protected override renderHeader(): void {
|
|
53
|
+
super.renderHeader();
|
|
54
|
+
this.builder.addLine(`@scope (${this.scopeStart})${this.scopeEnd ? ` to (${this.scopeEnd})` : ''} {`);
|
|
55
|
+
this.builder.indentLevel += 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected renderVariants(): void {
|
|
59
|
+
if (Object.keys(this._variants).length === 0) return;
|
|
60
|
+
|
|
61
|
+
this.builder.addLine(`/* variants */`);
|
|
62
|
+
|
|
63
|
+
for (const [variantKey, variantValues] of Object.entries(this._variants)) {
|
|
64
|
+
for (const [variantName, styles] of Object.entries(variantValues as Record<string, any>)) {
|
|
65
|
+
const selector = `&[${variantKey}="${variantName}"]`;
|
|
66
|
+
this.builder.addRule(selector, styles);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override renderFooter(): void {
|
|
72
|
+
this.renderVariants();
|
|
73
|
+
this.builder.indentLevel -= 1;
|
|
74
|
+
this.builder.addLine(`}`);
|
|
75
|
+
super.renderFooter();
|
|
76
|
+
}
|
|
77
|
+
public render() {
|
|
78
|
+
super.render();
|
|
79
|
+
let result = this as unknown as ScopeStylesPublic<A, V>;
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// only for the IDE never instantiated,
|
|
85
|
+
export type ScopeStylesPublic<
|
|
86
|
+
A extends ThemeArgs,
|
|
87
|
+
V extends A['variants'] = A['variants']
|
|
88
|
+
> = {
|
|
89
|
+
class: string;
|
|
90
|
+
variants: V;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// only for the IDE never instantiated, kept internal
|
|
94
|
+
class ScopeStyles<
|
|
95
|
+
A extends ThemeArgs,
|
|
96
|
+
V extends A['variants'] = A['variants']
|
|
97
|
+
> extends BaseStyles<A> {
|
|
98
|
+
class!: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
// TODO: Update imports once State is fully integrated
|
|
3
|
+
// import { computed, createState } from './State';
|
|
4
|
+
|
|
5
|
+
describe('State', () => {
|
|
6
|
+
it.skip('returns initial value and emits on set', async () => {
|
|
7
|
+
// const s = createState<string>("A");
|
|
8
|
+
// expect(await s.value).toBe("A");
|
|
9
|
+
|
|
10
|
+
// let calls = 0;
|
|
11
|
+
// let expectedValue = "A";
|
|
12
|
+
// s.subscribe((v) => {
|
|
13
|
+
// calls++;
|
|
14
|
+
// expect(v).toBe(expectedValue);
|
|
15
|
+
// });
|
|
16
|
+
|
|
17
|
+
// expectedValue = "B";
|
|
18
|
+
// s.setValue("B");
|
|
19
|
+
// expect(calls).toBe(2);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it.skip('emits when initialized with a resolving promise', async () => {
|
|
23
|
+
// const s = createState<number>();
|
|
24
|
+
// s.setValue(5);
|
|
25
|
+
// expect(s.value).toBe(5);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it.skip('recomputes when dependency changes', () => {
|
|
29
|
+
// const a = createState(100);
|
|
30
|
+
// const c = computed([a], () => {
|
|
31
|
+
// return (a.value) + 1;
|
|
32
|
+
// }, 0);
|
|
33
|
+
// expect(c.value).toBe(101);
|
|
34
|
+
// a.setValue(200);
|
|
35
|
+
// expect(c.value).toBe(201);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it.skip('recomputes when dependency changes II', () => {
|
|
39
|
+
// const a = createState(100);
|
|
40
|
+
// const c = computed([a], () => {
|
|
41
|
+
// return (a.value) + 1;
|
|
42
|
+
// });
|
|
43
|
+
// expect(c.value).toBe(101);
|
|
44
|
+
// a.setValue(200);
|
|
45
|
+
// expect(c.value).toBe(201);
|
|
46
|
+
});
|
|
47
|
+
});
|