chaincss 2.1.30 → 2.1.31

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.
@@ -0,0 +1,87 @@
1
+ // src/compiler/component-generator.ts
2
+ // Auto-generates React/Vue/Svelte/Solid components from style definitions
3
+
4
+ interface ComponentInfo {
5
+ name: string;
6
+ selector: string;
7
+ styles: Record<string, any>;
8
+ propsDefinition?: Record<string, any>;
9
+ framework: 'react' | 'vue' | 'svelte' | 'solid' | 'auto';
10
+ }
11
+
12
+ export function detectFramework(): 'react' | 'vue' | 'svelte' | 'solid' {
13
+ try { require.resolve('react/package.json'); return 'react'; } catch {}
14
+ try { require.resolve('vue/package.json'); return 'vue'; } catch {}
15
+ try { require.resolve('svelte/package.json'); return 'svelte'; } catch {}
16
+ try { require.resolve('solid-js/package.json'); return 'solid'; } catch {}
17
+ return 'react';
18
+ }
19
+
20
+ export function generateComponentCode(info: ComponentInfo): string {
21
+ const framework = info.framework === 'auto' ? detectFramework() : info.framework;
22
+ const cleanSelector = info.selector.replace(/^\./, '');
23
+
24
+ switch (framework) {
25
+ case 'react': {
26
+ const props = info.propsDefinition
27
+ ? Object.entries(info.propsDefinition).map(([k, t]) => ` ${k}?: ${t};`).join('\n')
28
+ : ' [key: string]: any;';
29
+ return `// Auto-generated by ChainCSS
30
+ import React from 'react';
31
+ import styles from './${info.name}.class.js';
32
+ import './${info.name}.css';
33
+
34
+ export interface ${info.name}Props {
35
+ className?: string;
36
+ children?: React.ReactNode;
37
+ ${props}
38
+ }
39
+
40
+ export const ${info.name}: React.FC<${info.name}Props> = ({ className, children, ...props }) => {
41
+ const cn = [styles.${cleanSelector}, className].filter(Boolean).join(' ');
42
+ return <div className={cn} {...props}>{children}</div>;
43
+ };
44
+ ${info.name}.displayName = 'ChainCSS${info.name}';
45
+ export default ${info.name};
46
+ `;
47
+ }
48
+ case 'vue':
49
+ return `<!-- Auto-generated by ChainCSS -->
50
+ <template>
51
+ <component :is="tag" :class="combinedClass" v-bind="$attrs"><slot /></component>
52
+ </template>
53
+ <script>
54
+ import styles from './${info.name}.class.js';
55
+ import './${info.name}.css';
56
+ export default {
57
+ name: 'ChainCSS${info.name}',
58
+ props: { tag: { type: String, default: 'div' }, className: { type: String, default: '' } },
59
+ computed: { combinedClass() { return [styles.${cleanSelector}, this.className].filter(Boolean).join(' '); } }
60
+ };
61
+ </script>`;
62
+ case 'svelte':
63
+ return `<!-- Auto-generated by ChainCSS -->
64
+ <script>
65
+ import styles from './${info.name}.class.js';
66
+ import './${info.name}.css';
67
+ export let className = '';
68
+ export let tag = 'div';
69
+ $: combinedClass = [styles.${cleanSelector}, className].filter(Boolean).join(' ');
70
+ </script>
71
+ <svelte:element this={tag} class={combinedClass}><slot /></svelte:element>`;
72
+ case 'solid':
73
+ return `// Auto-generated by ChainCSS
74
+ import { splitProps } from 'solid-js';
75
+ import styles from './${info.name}.class.js';
76
+ import './${info.name}.css';
77
+ export function ${info.name}(props: any) {
78
+ const [local, others] = splitProps(props, ['class', 'children']);
79
+ const cn = () => [styles.${cleanSelector}, local.class].filter(Boolean).join(' ');
80
+ return <div class={cn()} {...others}>{local.children}</div>;
81
+ }`;
82
+ default:
83
+ return generateComponentCode({ ...info, framework: 'react' });
84
+ }
85
+ }
86
+
87
+ export type { ComponentInfo };
@@ -0,0 +1,145 @@
1
+ // src/compiler/recipe.ts
2
+ /**
3
+ * Recipe System - Type-safe component variants
4
+ */
5
+ import { chain } from './Chain.js';
6
+ // Local type to avoid circular import with btt.ts
7
+ interface StyleDefinition {
8
+ selectors: string[];
9
+ hover?: Record<string, string | number>;
10
+ [key: string]: any;
11
+ }
12
+ import { run } from './btt.js';
13
+
14
+ export interface RecipeOptions<TVariants extends Record<string, Record<string, any>>> {
15
+ base?: StyleDefinition | (() => StyleDefinition);
16
+ variants?: TVariants;
17
+ defaultVariants?: Partial<{ [K in keyof TVariants]: keyof TVariants[K] }>;
18
+ compoundVariants?: Array<{
19
+ variants: Partial<{ [K in keyof TVariants]: keyof TVariants[K] }>;
20
+ style: StyleDefinition | (() => StyleDefinition);
21
+ }>;
22
+ }
23
+
24
+ export type Recipe<TVariants extends Record<string, Record<string, any>>> = {
25
+ (selection?: Partial<{ [K in keyof TVariants]: keyof TVariants[K] }>): StyleDefinition;
26
+ variants: TVariants;
27
+ defaultVariants: Partial<{ [K in keyof TVariants]: keyof TVariants[K] }>;
28
+ base: StyleDefinition;
29
+ getAllVariants: () => Array<Partial<{ [K in keyof TVariants]: keyof TVariants[K] }>>;
30
+ compileAll: () => string;
31
+ getVariantClassNames: () => Record<string, string>;
32
+ };
33
+
34
+ export function recipe<TVariants extends Record<string, Record<string, any>>>(
35
+ options: RecipeOptions<TVariants>
36
+ ): Recipe<TVariants> {
37
+ const { base, variants = {} as TVariants, defaultVariants = {}, compoundVariants = [] } = options;
38
+
39
+ const baseStyle = typeof base === 'function' ? (base as () => StyleDefinition)() : base;
40
+ const variantStyles: Record<string, Record<string, StyleDefinition>> = {};
41
+
42
+ for (const [variantName, variantMap] of Object.entries(variants)) {
43
+ variantStyles[variantName] = {};
44
+ for (const [variantKey, variantStyle] of Object.entries(variantMap as Record<string, any>)) {
45
+ variantStyles[variantName][variantKey] = typeof variantStyle === 'function'
46
+ ? (variantStyle as () => StyleDefinition)()
47
+ : variantStyle;
48
+ }
49
+ }
50
+
51
+ const compoundStyles = compoundVariants.map(cv => ({
52
+ condition: cv.variants || cv,
53
+ style: typeof cv.style === 'function' ? (cv.style as () => StyleDefinition)() : cv.style
54
+ }));
55
+
56
+ function mergeStyles(...styles: (StyleDefinition | undefined)[]): StyleDefinition {
57
+ const merged: StyleDefinition = { selectors: [] } as StyleDefinition;
58
+ for (const style of styles) {
59
+ if (!style) continue;
60
+ for (const [key, value] of Object.entries(style)) {
61
+ if (key === 'selectors') {
62
+ const newSelectors = Array.isArray(value) ? value : [value];
63
+ merged.selectors = [...new Set([...(merged.selectors || []), ...newSelectors])];
64
+ } else if (key === 'hover' && typeof value === 'object') {
65
+ if (!merged.hover) merged.hover = {};
66
+ Object.assign(merged.hover, value);
67
+ } else if (key !== 'selectors') {
68
+ (merged as any)[key] = value;
69
+ }
70
+ }
71
+ }
72
+ return merged;
73
+ }
74
+
75
+ function pick(variantSelection: Partial<Record<keyof TVariants, any>> = {}): StyleDefinition {
76
+ const selected = { ...defaultVariants, ...variantSelection } as Record<string, any>;
77
+ const stylesToMerge: StyleDefinition[] = [];
78
+
79
+ if (baseStyle) stylesToMerge.push(baseStyle);
80
+ for (const [variantName, variantValue] of Object.entries(selected)) {
81
+ const variantStyle = variantStyles[variantName]?.[variantValue];
82
+ if (variantStyle) stylesToMerge.push(variantStyle);
83
+ }
84
+ for (const cv of compoundStyles) {
85
+ if (Object.entries(cv.condition).every(([key, value]) => selected[key] === value) && cv.style) {
86
+ stylesToMerge.push(cv.style);
87
+ }
88
+ }
89
+
90
+ const merged = mergeStyles(...stylesToMerge);
91
+ let styleBuilder: any = chain();
92
+
93
+ for (const [prop, value] of Object.entries(merged)) {
94
+ if (prop === 'selectors' || prop === 'hover') continue;
95
+ if (styleBuilder[prop]) styleBuilder = styleBuilder[prop](value);
96
+ }
97
+ if (merged.hover) {
98
+ styleBuilder = styleBuilder.hover();
99
+ for (const [hoverProp, hoverValue] of Object.entries(merged.hover)) {
100
+ if (styleBuilder[hoverProp]) styleBuilder = styleBuilder[hoverProp](hoverValue);
101
+ }
102
+ styleBuilder = styleBuilder.end();
103
+ }
104
+
105
+ return styleBuilder.$el(...(merged.selectors || []));
106
+ }
107
+
108
+ (pick as any).variants = variants;
109
+ (pick as any).defaultVariants = defaultVariants;
110
+ (pick as any).base = baseStyle;
111
+
112
+ (pick as any).getAllVariants = () => {
113
+ const result: Array<Partial<Record<keyof TVariants, any>>> = [];
114
+ const variantKeys = Object.keys(variants) as (keyof TVariants)[];
115
+ function generate(current: Partial<Record<keyof TVariants, any>>, index: number): void {
116
+ if (index === variantKeys.length) { result.push({ ...current }); return; }
117
+ for (const v of Object.keys(variants[variantKeys[index]] as Record<string, any>)) {
118
+ current[variantKeys[index]] = v as any;
119
+ generate(current, index + 1);
120
+ }
121
+ }
122
+ generate({}, 0);
123
+ return result;
124
+ };
125
+
126
+ (pick as any).getVariantClassNames = () => {
127
+ const classNames: Record<string, string> = {};
128
+ for (const variant of (pick as any).getAllVariants()) {
129
+ const key = Object.entries(variant).map(([k, v]) => `${k}-${v}`).join('_');
130
+ const def = pick(variant);
131
+ if (def.selectors?.[0]) classNames[key] = def.selectors[0].replace(/^\./, '');
132
+ }
133
+ return classNames;
134
+ };
135
+
136
+ (pick as any).compileAll = () => {
137
+ const all = (pick as any).getAllVariants();
138
+ const styles: StyleDefinition[] = [];
139
+ if (baseStyle?.selectors) styles.push(baseStyle);
140
+ for (const v of all) { const d = pick(v); if (d?.selectors) styles.push(d); }
141
+ return run(...styles);
142
+ };
143
+
144
+ return pick as Recipe<TVariants>;
145
+ }
@@ -0,0 +1,46 @@
1
+ // src/compiler/scanner.ts
2
+ /**
3
+ * Content Scanner - Extracts ChainCSS calls from source files
4
+ */
5
+ import fs from 'fs';
6
+ import chalk from 'chalk';
7
+
8
+ export const scanContent = (text: string): string[] => {
9
+ const regex = /(?:chain|smartChain|\$t?)\(((?:[^()]|\([^()]*\))*)\)(?:\s*\.\s*[a-zA-Z0-9]+\s*\([^)]*\))*/g;
10
+ const matches = text.match(regex) || [];
11
+ return matches.map(m => m.replace(/\s+/g, ''));
12
+ };
13
+
14
+ export function scanFileForStyles(
15
+ filePath: string,
16
+ optimizer: any,
17
+ source: string | null = null
18
+ ): { foundCount: number; errors: Error[] } {
19
+ const errors: Error[] = [];
20
+ let foundCount = 0;
21
+
22
+ try {
23
+ const content = source !== null ? source : fs.readFileSync(filePath, 'utf8');
24
+ if (!content || content.trim().length === 0) return { foundCount: 0, errors };
25
+
26
+ const styleRegex = /(?:chain|smartChain|\$)\(((?:[^()]|\([^()]*\))*)\)/g;
27
+ let match;
28
+
29
+ while ((match = styleRegex.exec(content)) !== null) {
30
+ try {
31
+ const styleBody = match[1].trim();
32
+ const cleanBody = styleBody.replace(/^['"\`]|['"\`]$/g, '');
33
+ if (cleanBody && optimizer && typeof optimizer.trackStyles === 'function') {
34
+ optimizer.trackStyles([{ selectors: { '&': cleanBody } }]);
35
+ foundCount++;
36
+ }
37
+ } catch (parseError) {
38
+ errors.push(parseError as Error);
39
+ }
40
+ }
41
+ } catch (err) {
42
+ errors.push(err as Error);
43
+ }
44
+
45
+ return { foundCount, errors };
46
+ }
@@ -0,0 +1,128 @@
1
+ // src/compiler/timeline.ts
2
+ /**
3
+ * Style Timeline & Diff Viewer
4
+ * Tracks every style change, lets you diff versions
5
+ */
6
+
7
+ export interface StyleSnapshot {
8
+ id: string;
9
+ timestamp: number;
10
+ selector: string;
11
+ styles: Record<string, any>;
12
+ source: string;
13
+ hash: string;
14
+ }
15
+
16
+ export interface StyleChange {
17
+ id: string;
18
+ timestamp: number;
19
+ selector: string;
20
+ property: string;
21
+ oldValue: any;
22
+ newValue: any;
23
+ type: 'add' | 'remove' | 'modify';
24
+ }
25
+
26
+ let styleHistory: StyleSnapshot[] = [];
27
+ let styleChanges: StyleChange[] = [];
28
+ let timelineEnabled = false;
29
+ let currentSnapshotId = 0;
30
+
31
+ export function enableTimeline(enable: boolean = true): void {
32
+ timelineEnabled = enable;
33
+ if (!enable) {
34
+ styleHistory = [];
35
+ styleChanges = [];
36
+ }
37
+ }
38
+
39
+ export function getStyleHistory(): StyleSnapshot[] {
40
+ return [...styleHistory];
41
+ }
42
+
43
+ export function getStyleChanges(): StyleChange[] {
44
+ return [...styleChanges];
45
+ }
46
+
47
+ export function getStyleDiff(snapshotId1: string, snapshotId2: string): Record<string, any> {
48
+ const snapshot1 = styleHistory.find(s => s.id === snapshotId1);
49
+ const snapshot2 = styleHistory.find(s => s.id === snapshotId2);
50
+
51
+ if (!snapshot1 || !snapshot2) {
52
+ return { error: 'Snapshot not found' };
53
+ }
54
+
55
+ const diff: Record<string, any> = { added: {}, removed: {}, modified: {} };
56
+
57
+ for (const [key, value] of Object.entries(snapshot2.styles)) {
58
+ if (!(key in snapshot1.styles)) {
59
+ diff.added[key] = value;
60
+ } else if (JSON.stringify(snapshot1.styles[key]) !== JSON.stringify(value)) {
61
+ diff.modified[key] = { old: snapshot1.styles[key], new: value };
62
+ }
63
+ }
64
+
65
+ for (const [key, value] of Object.entries(snapshot1.styles)) {
66
+ if (!(key in snapshot2.styles)) {
67
+ diff.removed[key] = value;
68
+ }
69
+ }
70
+
71
+ return diff;
72
+ }
73
+
74
+ export function takeSnapshot(selector: string, styles: Record<string, any>, source: string): string {
75
+ if (!timelineEnabled) return '';
76
+
77
+ const hash = JSON.stringify(styles);
78
+ const existing = styleHistory.find(s => s.selector === selector && s.hash === hash);
79
+ if (existing) return existing.id;
80
+
81
+ const id = `snapshot_${currentSnapshotId++}`;
82
+ styleHistory.push({ id, timestamp: Date.now(), selector, styles: { ...styles }, source, hash });
83
+
84
+ // Track changes from previous snapshot
85
+ const previous = styleHistory[styleHistory.length - 2];
86
+ if (previous && previous.selector === selector) {
87
+ for (const [key, value] of Object.entries(styles)) {
88
+ if (!(key in previous.styles)) {
89
+ styleChanges.push({
90
+ id: `change_${Date.now()}_${Math.random().toString(36).slice(2)}`,
91
+ timestamp: Date.now(), selector, property: key,
92
+ oldValue: undefined, newValue: value, type: 'add'
93
+ });
94
+ } else if (JSON.stringify(previous.styles[key]) !== JSON.stringify(value)) {
95
+ styleChanges.push({
96
+ id: `change_${Date.now()}_${Math.random().toString(36).slice(2)}`,
97
+ timestamp: Date.now(), selector, property: key,
98
+ oldValue: previous.styles[key], newValue: value, type: 'modify'
99
+ });
100
+ }
101
+ }
102
+ for (const [key] of Object.entries(previous.styles)) {
103
+ if (!(key in styles)) {
104
+ styleChanges.push({
105
+ id: `change_${Date.now()}_${Math.random().toString(36).slice(2)}`,
106
+ timestamp: Date.now(), selector, property: key,
107
+ oldValue: previous.styles[key], newValue: undefined, type: 'remove'
108
+ });
109
+ }
110
+ }
111
+ }
112
+
113
+ return id;
114
+ }
115
+
116
+ export function exportTimeline(): string {
117
+ return JSON.stringify({ history: styleHistory, changes: styleChanges, exportedAt: Date.now() }, null, 2);
118
+ }
119
+
120
+ export function clearTimeline(): void {
121
+ styleHistory = [];
122
+ styleChanges = [];
123
+ currentSnapshotId = 0;
124
+ }
125
+
126
+ export function isTimelineEnabled(): boolean {
127
+ return timelineEnabled;
128
+ }