@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.
Files changed (70) hide show
  1. package/README.md +13 -0
  2. package/dist/BaseStyles.d.ts +44 -0
  3. package/dist/BaseStyles.js +166 -0
  4. package/dist/Button.d.ts +69 -0
  5. package/dist/Button.js +56 -0
  6. package/dist/CssColor.d.ts +44 -0
  7. package/dist/CssColor.js +196 -0
  8. package/dist/CssColor.test.d.ts +1 -0
  9. package/dist/CssColor.test.js +68 -0
  10. package/dist/CssExpr.d.ts +9 -0
  11. package/dist/CssExpr.js +35 -0
  12. package/dist/CssFilter.d.ts +18 -0
  13. package/dist/CssFilter.js +40 -0
  14. package/dist/CssNum.d.ts +30 -0
  15. package/dist/CssNum.js +106 -0
  16. package/dist/CssNum.test.d.ts +1 -0
  17. package/dist/CssNum.test.js +30 -0
  18. package/dist/CssReset.d.ts +1 -0
  19. package/dist/CssReset.js +15 -0
  20. package/dist/CssShadow.d.ts +19 -0
  21. package/dist/CssShadow.js +42 -0
  22. package/dist/DefaultTheme.d.ts +14 -0
  23. package/dist/DefaultTheme.js +6 -0
  24. package/dist/EmptyTheme.d.ts +14 -0
  25. package/dist/EmptyTheme.js +2 -0
  26. package/dist/HTML.d.ts +9 -0
  27. package/dist/HTML.jsx +7 -0
  28. package/dist/ScopeStyles.d.ts +24 -0
  29. package/dist/ScopeStyles.js +67 -0
  30. package/dist/State.d.ts +36 -0
  31. package/dist/State.js +107 -0
  32. package/dist/State.test.d.ts +1 -0
  33. package/dist/State.test.js +41 -0
  34. package/dist/StyleWriter.d.ts +25 -0
  35. package/dist/StyleWriter.js +156 -0
  36. package/dist/Theme.d.ts +56 -0
  37. package/dist/Theme.js +29 -0
  38. package/dist/Tone.d.ts +1 -0
  39. package/dist/Tone.js +1 -0
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js +1 -0
  42. package/dist/index.test.d.ts +1 -0
  43. package/dist/index.test.js +9 -0
  44. package/dist/props.d.ts +7 -0
  45. package/dist/props.js +104 -0
  46. package/dist/types.d.ts +239 -0
  47. package/dist/types.js +1 -0
  48. package/package.json +36 -0
  49. package/src/BaseStyles.ts +192 -0
  50. package/src/Button.ts +68 -0
  51. package/src/CssColor.test.ts +82 -0
  52. package/src/CssColor.ts +175 -0
  53. package/src/CssExpr.ts +25 -0
  54. package/src/CssFilter.ts +44 -0
  55. package/src/CssNum.test.ts +37 -0
  56. package/src/CssNum.ts +93 -0
  57. package/src/CssReset.ts +17 -0
  58. package/src/CssShadow.ts +46 -0
  59. package/src/DefaultTheme.ts +8 -0
  60. package/src/HTML.tsx +17 -0
  61. package/src/ScopeStyles.ts +100 -0
  62. package/src/State.test.ts +47 -0
  63. package/src/State.ts +164 -0
  64. package/src/StyleWriter.ts +163 -0
  65. package/src/Theme.ts +95 -0
  66. package/src/Tone.ts +1 -0
  67. package/src/index.test.ts +10 -0
  68. package/src/index.ts +1 -0
  69. package/src/props.ts +118 -0
  70. package/src/types.ts +311 -0
@@ -0,0 +1,41 @@
1
+ import { describe, it } from 'vitest';
2
+ // TODO: Update imports once State is fully integrated
3
+ // import { computed, createState } from './State';
4
+ describe('State', () => {
5
+ it.skip('returns initial value and emits on set', async () => {
6
+ // const s = createState<string>("A");
7
+ // expect(await s.value).toBe("A");
8
+ // let calls = 0;
9
+ // let expectedValue = "A";
10
+ // s.subscribe((v) => {
11
+ // calls++;
12
+ // expect(v).toBe(expectedValue);
13
+ // });
14
+ // expectedValue = "B";
15
+ // s.setValue("B");
16
+ // expect(calls).toBe(2);
17
+ });
18
+ it.skip('emits when initialized with a resolving promise', async () => {
19
+ // const s = createState<number>();
20
+ // s.setValue(5);
21
+ // expect(s.value).toBe(5);
22
+ });
23
+ it.skip('recomputes when dependency changes', () => {
24
+ // const a = createState(100);
25
+ // const c = computed([a], () => {
26
+ // return (a.value) + 1;
27
+ // }, 0);
28
+ // expect(c.value).toBe(101);
29
+ // a.setValue(200);
30
+ // expect(c.value).toBe(201);
31
+ });
32
+ it.skip('recomputes when dependency changes II', () => {
33
+ // const a = createState(100);
34
+ // const c = computed([a], () => {
35
+ // return (a.value) + 1;
36
+ // });
37
+ // expect(c.value).toBe(101);
38
+ // a.setValue(200);
39
+ // expect(c.value).toBe(201);
40
+ });
41
+ });
@@ -0,0 +1,25 @@
1
+ import { CssProperties, ITheme } from "./types";
2
+ export declare function toKebabCase(str: string): string;
3
+ export declare class StyleWriter {
4
+ readonly name: string;
5
+ readonly element: HTMLStyleElement;
6
+ readonly lines: string[];
7
+ static readonly builders: Map<string, StyleWriter>;
8
+ private indentString;
9
+ private _indentLevel;
10
+ get indentLevel(): number;
11
+ set indentLevel(value: number);
12
+ private constructor();
13
+ static byName(name: string): StyleWriter;
14
+ render(): void;
15
+ clear(): void;
16
+ addLines(lines: string[]): void;
17
+ addLine(line: string | undefined): void;
18
+ addRule(selector: string, styles: CssProperties | string): void;
19
+ protected addCssProperties(properties: CssProperties): void;
20
+ static stringifyStyle(key: string, value: CssProperties, theme?: ITheme): string | undefined;
21
+ static spacingProps: Set<string>;
22
+ static unitlessProps: Set<string>;
23
+ static convertNumberToCssValue(prop: string, value: number): string;
24
+ }
25
+ export declare function addStyles(section: string, callback: (builder: StyleWriter) => void, thisArg?: any): void;
@@ -0,0 +1,156 @@
1
+ import { CssExpr } from "./CssExpr";
2
+ export function toKebabCase(str) {
3
+ if (str.startsWith('--'))
4
+ return str;
5
+ return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
6
+ }
7
+ export class StyleWriter {
8
+ name;
9
+ element;
10
+ lines = [];
11
+ static builders = new Map;
12
+ indentString = '';
13
+ _indentLevel = 0;
14
+ get indentLevel() {
15
+ return this._indentLevel;
16
+ }
17
+ set indentLevel(value) {
18
+ this._indentLevel = value;
19
+ this.indentString = ' '.repeat(this._indentLevel);
20
+ }
21
+ constructor(name) {
22
+ this.name = name;
23
+ this.element = document.createElement('style');
24
+ this.element.setAttribute("data-section", name);
25
+ document.head.appendChild(this.element);
26
+ }
27
+ static byName(name) {
28
+ let existing = StyleWriter.builders.get(name);
29
+ if (existing)
30
+ return existing;
31
+ let newBuilder = new StyleWriter(name);
32
+ StyleWriter.builders.set(name, newBuilder);
33
+ return newBuilder;
34
+ }
35
+ render() {
36
+ this.element.innerHTML = this.lines.join("\n");
37
+ this.clear();
38
+ }
39
+ clear() {
40
+ this.lines.length = 0;
41
+ }
42
+ addLines(lines) {
43
+ lines.forEach(s => this.addLine(s));
44
+ }
45
+ addLine(line) {
46
+ if (line === undefined)
47
+ return;
48
+ this.lines.push(this.indentString + line);
49
+ }
50
+ addRule(selector, styles) {
51
+ this.addLine(`${selector} {`);
52
+ this.indentLevel += 1;
53
+ if (typeof styles === 'string') {
54
+ this.addLine(styles);
55
+ }
56
+ else if (Array.isArray(styles)) {
57
+ this.addLines(styles);
58
+ }
59
+ else {
60
+ this.addCssProperties(styles);
61
+ }
62
+ this.indentLevel -= 1;
63
+ this.addLine(`}`);
64
+ }
65
+ addCssProperties(properties) {
66
+ if (properties == null)
67
+ return;
68
+ let entries = Object.entries(properties);
69
+ if (entries.length === 0)
70
+ return;
71
+ let stringifyValue = (key, v) => {
72
+ if (v === null || v === undefined)
73
+ return undefined;
74
+ if (typeof v === 'string' || typeof v === 'number') {
75
+ return StyleWriter.stringifyStyle(key, v);
76
+ }
77
+ else if (v instanceof CssExpr) {
78
+ let s = v.toString();
79
+ return s;
80
+ }
81
+ else if (Array.isArray(v)) {
82
+ let s = v.map(i => stringifyValue(key, i)).join(', ');
83
+ return s;
84
+ }
85
+ else {
86
+ throw Error("Unexpected type in style value");
87
+ }
88
+ };
89
+ for (let [k, v] of entries) {
90
+ if (!k.startsWith('--')) {
91
+ k = toKebabCase(k);
92
+ }
93
+ v = stringifyValue(k, v);
94
+ if (v === undefined || v === null)
95
+ continue;
96
+ this.addLine(`${k}: ${v};`);
97
+ }
98
+ }
99
+ static stringifyStyle(key, value, theme) {
100
+ if (value === null || value === undefined)
101
+ return undefined;
102
+ if (key === 'content' && typeof value === 'string') {
103
+ return `content: ${value};`;
104
+ }
105
+ let result;
106
+ switch (typeof value) {
107
+ case "number":
108
+ result = StyleWriter.convertNumberToCssValue(key, value);
109
+ break;
110
+ case "function":
111
+ result = value(theme);
112
+ break;
113
+ default:
114
+ result = String(value);
115
+ break;
116
+ }
117
+ return result;
118
+ }
119
+ static spacingProps = new Set([
120
+ 'width', 'height', 'max-width', 'min-width', 'max-height', 'min-height',
121
+ 'padding', 'padding-top', 'padding-bottom', 'padding-left', 'padding-right',
122
+ 'margin', 'margin-top', 'margin-bottom', 'margin-left', 'margin-right',
123
+ 'gap', 'row-gap', 'column-gap',
124
+ 'top', 'right', 'bottom', 'left',
125
+ 'inset', 'inset-top', 'inset-right', 'inset-bottom', 'inset-left',
126
+ 'border', 'border-radius', 'border-top-left-radius', 'border-top-right-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'
127
+ ]);
128
+ // Properties that are unitless in CSS
129
+ static unitlessProps = new Set([
130
+ 'line-height', 'z-index', 'opacity', 'font-weight', 'order', 'flex', 'flex-grow', 'flex-shrink', 'flex-basis'
131
+ ]);
132
+ static convertNumberToCssValue(prop, value) {
133
+ // normalize prop name to kebab-case as used in spacingPropsSet
134
+ const p = prop.toLowerCase();
135
+ if (StyleWriter.unitlessProps.has(p))
136
+ return String(value);
137
+ if (StyleWriter.spacingProps && StyleWriter.spacingProps.has(p)) {
138
+ // default conversion: treat number as rem with one decimal (value * 0.25 -> 0.25 increments?)
139
+ // original behaviour used Math.round(value * 2.5) / 10 to produce tenths
140
+ return `${Math.round(value * 2.5) / 10}rem`;
141
+ }
142
+ // fallback: return numeric as-is
143
+ return String(value);
144
+ }
145
+ }
146
+ export function addStyles(section, callback, thisArg) {
147
+ let builder = StyleWriter.byName(section);
148
+ try {
149
+ callback.call(thisArg, builder);
150
+ }
151
+ finally {
152
+ if (typeof document === 'undefined')
153
+ return;
154
+ builder.render();
155
+ }
156
+ }
@@ -0,0 +1,56 @@
1
+ import { ITheme, IThemeParts, IScopeStylesParts, MergeThemeArgs, ThemeArgs } from "./types";
2
+ import { ScopeStylesBuilder } from "./ScopeStyles";
3
+ import { BaseStyles } from "./BaseStyles";
4
+ export type ThemeCSSClass = keyof ITheme['classes'];
5
+ export type ThemeClassList = Partial<Record<ThemeCSSClass, boolean | undefined>>;
6
+ export declare function createTheme<Colors extends string = never, Sizes extends string = never, Paddings extends string = never, Margins extends string = never, Borders extends string = never, Layouts extends string = never, Backgrounds extends string = never, Typography extends string = never, Shadows extends string = never, Vars extends string = never, Classes extends string = never, Variants extends Record<string, Record<string, any>> = Record<string, never>>(themeParts: IThemeParts<{
7
+ colors: Colors;
8
+ sizes: Sizes;
9
+ paddings: Paddings;
10
+ margins: Margins;
11
+ borders: Borders;
12
+ backgrounds: Backgrounds;
13
+ layouts: Layouts;
14
+ typography: Typography;
15
+ shadows: Shadows;
16
+ vars: Vars;
17
+ classes: Classes;
18
+ variants: Variants;
19
+ }>): ThemeBuilder<{
20
+ colors: Colors;
21
+ sizes: Sizes;
22
+ paddings: Paddings;
23
+ margins: Margins;
24
+ borders: Borders;
25
+ backgrounds: Backgrounds;
26
+ layouts: Layouts;
27
+ typography: Typography;
28
+ shadows: Shadows;
29
+ vars: Vars;
30
+ classes: Classes;
31
+ variants: Variants;
32
+ }>;
33
+ export declare class ThemeBuilder<A extends ThemeArgs> extends BaseStyles<A> {
34
+ constructor(themeParts: IThemeParts<A>);
35
+ declareScope<P extends IScopeStylesParts<any> & {
36
+ class: string;
37
+ scopeStart?: string;
38
+ scopeEnd?: string;
39
+ }>(parts: P): ScopeStylesBuilder<MergeThemeArgs<A, P>, MergeThemeArgs<A, P>['variants']>;
40
+ declareScope<P extends (theme: ThemeBuilder<A>) => IScopeStylesParts<any> & {
41
+ scopeStart: string;
42
+ scopeEnd?: string;
43
+ }>(parts: P): ScopeStylesBuilder<MergeThemeArgs<A, ReturnType<P>>, MergeThemeArgs<A, ReturnType<P>>['variants']>;
44
+ add<P extends IThemeParts<any>>(parts: P): ThemeBuilder<MergeThemeArgs<A, P>>;
45
+ add<P extends (theme: ThemeBuilder<A>) => IThemeParts<any>>(parts: P): ThemeBuilder<MergeThemeArgs<A, ReturnType<P>>>;
46
+ render(): ThemePublic<A>;
47
+ }
48
+ export type ThemePublic<A extends ThemeArgs> = {
49
+ declareScope: ThemeBuilder<A>['declareScope'];
50
+ colors: A['colors'] extends string ? Record<A['colors'], string> : Record<string, string>;
51
+ vars: A['vars'] extends string ? Record<A['vars'], string> : Record<string, string>;
52
+ classes: A['classes'] extends string ? Record<A['classes'], string> : Record<string, string>;
53
+ };
54
+ export declare class Theme<A extends ThemeArgs> extends BaseStyles<A> {
55
+ declareScope: ThemeBuilder<A>['declareScope'];
56
+ }
package/dist/Theme.js ADDED
@@ -0,0 +1,29 @@
1
+ import { ScopeStylesBuilder } from "./ScopeStyles";
2
+ import { BaseStyles } from "./BaseStyles";
3
+ export function createTheme(themeParts) {
4
+ let theme = new ThemeBuilder(themeParts);
5
+ return theme;
6
+ }
7
+ export class ThemeBuilder extends BaseStyles {
8
+ constructor(themeParts) {
9
+ super({ ...themeParts });
10
+ }
11
+ declareScope(partsOrLambda) {
12
+ const resolved = (typeof partsOrLambda === 'function') ? partsOrLambda(this) : partsOrLambda;
13
+ return new ScopeStylesBuilder(this, { ...resolved });
14
+ }
15
+ add(parts) {
16
+ if (typeof parts === 'function')
17
+ parts = parts(this);
18
+ super.add(parts);
19
+ return this;
20
+ }
21
+ render() {
22
+ super.render();
23
+ let result = this;
24
+ return result;
25
+ }
26
+ }
27
+ export class Theme extends BaseStyles {
28
+ declareScope;
29
+ }
package/dist/Tone.d.ts ADDED
@@ -0,0 +1 @@
1
+ export type Tone = 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'danger' | 'info';
package/dist/Tone.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export * from "./Theme";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from "./Theme";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createTheme } from './Theme';
3
+ describe('createTheme', () => {
4
+ it('returns a theme object', () => {
5
+ const theme = createTheme({}).render();
6
+ expect(theme).toBeDefined();
7
+ expect(theme.classes).toStrictEqual({});
8
+ });
9
+ });
@@ -0,0 +1,7 @@
1
+ import { IDivProps } from "./HTML";
2
+ export declare function prepareProps<T extends IDivProps = IDivProps>(propsIn: T, styles?: {
3
+ variants?: Record<string, any>;
4
+ class?: string;
5
+ }): Record<string, any>;
6
+ export declare function completeProps(propsIn: Record<string, any>): Record<string, any>;
7
+ export declare function Elt(tag: string, props: Record<string, any>, children?: any[]): string;
package/dist/props.js ADDED
@@ -0,0 +1,104 @@
1
+ function classListToRecord(classList) {
2
+ const result = {};
3
+ if (!classList)
4
+ return result;
5
+ if (Array.isArray(classList)) {
6
+ for (const item of classList) {
7
+ if (!item)
8
+ continue;
9
+ if (typeof item === 'string') {
10
+ result[item] = true;
11
+ }
12
+ else if (typeof item === 'object') {
13
+ Object.assign(result, item);
14
+ }
15
+ }
16
+ }
17
+ else if (typeof classList === 'string') {
18
+ result[classList] = true;
19
+ }
20
+ else {
21
+ Object.assign(result, classList);
22
+ }
23
+ return result;
24
+ }
25
+ // Normalizes classes and variants. User code can intercept between prepareProps/completeProps to add classes.
26
+ export function prepareProps(propsIn, styles) {
27
+ const classRecord = classListToRecord(propsIn.class);
28
+ const dataAttrs = {};
29
+ if (styles?.class) {
30
+ classRecord[styles.class] = true;
31
+ }
32
+ // Apply variants as attributes, use 'default' value if not provided
33
+ if (styles?.variants) {
34
+ for (const variantKey of Object.keys(styles.variants)) {
35
+ let variantValue = propsIn[variantKey];
36
+ if (variantValue === undefined) {
37
+ const variantGroup = styles.variants[variantKey];
38
+ if (variantGroup && 'default' in variantGroup) {
39
+ variantValue = 'default';
40
+ }
41
+ }
42
+ if (variantValue !== undefined) {
43
+ dataAttrs[variantKey] = variantValue;
44
+ }
45
+ }
46
+ }
47
+ return {
48
+ __prepared: true,
49
+ style: styleToString(propsIn.style),
50
+ class: classRecord,
51
+ ...dataAttrs,
52
+ };
53
+ }
54
+ // Converts classRecord to space-separated string
55
+ export function completeProps(propsIn) {
56
+ if (!('__prepared' in propsIn)) {
57
+ propsIn = prepareProps(propsIn);
58
+ }
59
+ delete propsIn.__prepared;
60
+ if (typeof propsIn.class === 'object') {
61
+ propsIn.class = Object.entries(propsIn.class)
62
+ .filter(([_, value]) => value)
63
+ .map(([key]) => key)
64
+ .join(' ');
65
+ }
66
+ return propsIn;
67
+ }
68
+ function styleToString(styles) {
69
+ if (!styles)
70
+ return undefined;
71
+ let result = '';
72
+ for (const [key, value] of Object.entries(styles)) {
73
+ if (value !== undefined) {
74
+ const cssKey = key.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
75
+ result += `${cssKey}: ${value}; `;
76
+ }
77
+ }
78
+ return result || undefined;
79
+ }
80
+ export function Elt(tag, props, children) {
81
+ const finalProps = completeProps(props);
82
+ const escapeAttr = (s) => s.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
83
+ const attrs = Object.entries(finalProps)
84
+ .map(([k, v]) => {
85
+ if (v === undefined || v === null)
86
+ return '';
87
+ if (k === 'children')
88
+ return '';
89
+ if (typeof v === 'boolean')
90
+ return v ? `${k}` : '';
91
+ return `${k}="${escapeAttr(String(v))}"`;
92
+ })
93
+ .filter(Boolean)
94
+ .join(' ');
95
+ const renderChildren = (c) => {
96
+ if (c === undefined || c === null)
97
+ return '';
98
+ if (Array.isArray(c))
99
+ return c.map(renderChildren).join('');
100
+ return String(c);
101
+ };
102
+ const childrenStr = renderChildren(children);
103
+ return `<${tag}${attrs ? ' ' + attrs : ''}>${childrenStr}</${tag}>`;
104
+ }