juxscript 1.0.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 (61) hide show
  1. package/README.md +292 -0
  2. package/bin/cli.js +149 -0
  3. package/lib/adapters/base-adapter.js +35 -0
  4. package/lib/adapters/index.js +33 -0
  5. package/lib/adapters/mysql-adapter.js +65 -0
  6. package/lib/adapters/postgres-adapter.js +70 -0
  7. package/lib/adapters/sqlite-adapter.js +56 -0
  8. package/lib/components/app.ts +124 -0
  9. package/lib/components/button.ts +136 -0
  10. package/lib/components/card.ts +205 -0
  11. package/lib/components/chart.ts +125 -0
  12. package/lib/components/code.ts +242 -0
  13. package/lib/components/container.ts +282 -0
  14. package/lib/components/data.ts +105 -0
  15. package/lib/components/docs-data.json +1211 -0
  16. package/lib/components/error-handler.ts +285 -0
  17. package/lib/components/footer.ts +146 -0
  18. package/lib/components/header.ts +167 -0
  19. package/lib/components/hero.ts +170 -0
  20. package/lib/components/import.ts +430 -0
  21. package/lib/components/input.ts +175 -0
  22. package/lib/components/layout.ts +113 -0
  23. package/lib/components/list.ts +392 -0
  24. package/lib/components/main.ts +111 -0
  25. package/lib/components/menu.ts +170 -0
  26. package/lib/components/modal.ts +216 -0
  27. package/lib/components/nav.ts +136 -0
  28. package/lib/components/node.ts +200 -0
  29. package/lib/components/reactivity.js +104 -0
  30. package/lib/components/script.ts +152 -0
  31. package/lib/components/sidebar.ts +168 -0
  32. package/lib/components/style.ts +129 -0
  33. package/lib/components/table.ts +279 -0
  34. package/lib/components/tabs.ts +191 -0
  35. package/lib/components/theme.ts +97 -0
  36. package/lib/components/view.ts +174 -0
  37. package/lib/jux.ts +203 -0
  38. package/lib/layouts/default.css +260 -0
  39. package/lib/layouts/default.jux +8 -0
  40. package/lib/layouts/figma.css +334 -0
  41. package/lib/layouts/figma.jux +0 -0
  42. package/lib/layouts/notion.css +258 -0
  43. package/lib/styles/base-theme.css +186 -0
  44. package/lib/styles/dark-theme.css +144 -0
  45. package/lib/styles/global.css +1131 -0
  46. package/lib/styles/light-theme.css +144 -0
  47. package/lib/styles/tokens/dark.css +86 -0
  48. package/lib/styles/tokens/light.css +86 -0
  49. package/lib/themes/dark.css +86 -0
  50. package/lib/themes/light.css +86 -0
  51. package/lib/utils/path-resolver.js +23 -0
  52. package/machinery/compiler.js +262 -0
  53. package/machinery/doc-generator.js +160 -0
  54. package/machinery/generators/css.js +128 -0
  55. package/machinery/generators/html.js +108 -0
  56. package/machinery/imports.js +155 -0
  57. package/machinery/server.js +185 -0
  58. package/machinery/validators/file-validator.js +123 -0
  59. package/machinery/watcher.js +148 -0
  60. package/package.json +58 -0
  61. package/types/globals.d.ts +16 -0
@@ -0,0 +1,242 @@
1
+ import { Reactive, getOrCreateContainer } from './reactivity.js';
2
+
3
+ /**
4
+ * Code component options
5
+ */
6
+ export interface CodeOptions {
7
+ code?: string;
8
+ language?: string;
9
+ title?: string | null;
10
+ showLineNumbers?: boolean;
11
+ highlightLines?: number[];
12
+ maxHeight?: string | null;
13
+ wrap?: boolean;
14
+ }
15
+
16
+ /**
17
+ * Code component state
18
+ */
19
+ type CodeState = {
20
+ code: string;
21
+ language: string;
22
+ title: string | null;
23
+ showLineNumbers: boolean;
24
+ highlightLines: number[];
25
+ maxHeight: string | null;
26
+ wrap: boolean;
27
+ };
28
+
29
+ /**
30
+ * Code component - displays formatted code blocks with syntax highlighting
31
+ *
32
+ * Usage:
33
+ * const codeBlock = jux.code('myCode', {
34
+ * code: 'const x = 42;',
35
+ * language: 'javascript',
36
+ * title: 'Example Code',
37
+ * showLineNumbers: true
38
+ * });
39
+ * codeBlock.render();
40
+ */
41
+ export class Code extends Reactive {
42
+ state!: CodeState;
43
+ container: HTMLElement | null = null;
44
+
45
+ constructor(componentId: string, options: CodeOptions = {}) {
46
+ super();
47
+ this._setComponentId(componentId);
48
+
49
+ // Map 'jux' language to 'javascript' for syntax highlighting
50
+ let language = options.language || 'javascript';
51
+ if (language === 'jux') {
52
+ language = 'javascript';
53
+ }
54
+
55
+ this.state = this._createReactiveState({
56
+ code: options.code ?? '',
57
+ language: language,
58
+ title: options.title ?? null,
59
+ showLineNumbers: options.showLineNumbers ?? false,
60
+ highlightLines: options.highlightLines ?? [],
61
+ maxHeight: options.maxHeight ?? null,
62
+ wrap: options.wrap ?? false
63
+ }) as CodeState;
64
+ }
65
+
66
+ /* -------------------------
67
+ * Fluent API
68
+ * ------------------------- */
69
+
70
+ code(value: string): this {
71
+ this.state.code = value;
72
+ return this;
73
+ }
74
+
75
+ language(value: string): this {
76
+ this.state.language = value === 'jux' ? 'javascript' : value;
77
+ return this;
78
+ }
79
+
80
+ title(value: string): this {
81
+ this.state.title = value;
82
+ return this;
83
+ }
84
+
85
+ showLineNumbers(value: boolean): this {
86
+ this.state.showLineNumbers = value;
87
+ return this;
88
+ }
89
+
90
+ highlightLines(value: number[]): this {
91
+ this.state.highlightLines = value;
92
+ return this;
93
+ }
94
+
95
+ maxHeight(value: string): this {
96
+ this.state.maxHeight = value;
97
+ return this;
98
+ }
99
+
100
+ wrap(value: boolean): this {
101
+ this.state.wrap = value;
102
+ return this;
103
+ }
104
+
105
+ /* -------------------------
106
+ * Helpers
107
+ * ------------------------- */
108
+
109
+ private _escapeHtml(text: string): string {
110
+ const div = document.createElement('div');
111
+ div.textContent = text;
112
+ return div.innerHTML;
113
+ }
114
+
115
+ private _formatCode(): string {
116
+ const { code, showLineNumbers, highlightLines } = this.state;
117
+ const lines = code.split('\n');
118
+
119
+ if (!showLineNumbers) {
120
+ return `<code class="jux-code-block">${this._escapeHtml(code)}</code>`;
121
+ }
122
+
123
+ const formattedLines = lines
124
+ .map((line, index) => {
125
+ const lineNum = index + 1;
126
+ const isHighlighted = highlightLines.includes(lineNum);
127
+ const highlightClass = isHighlighted ? ' jux-code-line-highlighted' : '';
128
+
129
+ return `<div class="jux-code-line${highlightClass}">
130
+ <span class="jux-code-line-number">${lineNum}</span>
131
+ <span class="jux-code-line-content">${this._escapeHtml(line) || ' '}</span>
132
+ </div>`;
133
+ })
134
+ .join('');
135
+
136
+ return `<code class="jux-code-block jux-code-numbered">${formattedLines}</code>`;
137
+ }
138
+
139
+ /* -------------------------
140
+ * Render
141
+ * ------------------------- */
142
+
143
+ render(targetId?: string): this {
144
+ let container: HTMLElement;
145
+
146
+ if (targetId) {
147
+ const target = document.querySelector(targetId);
148
+ if (!target || !(target instanceof HTMLElement)) {
149
+ throw new Error(`Code: Target element "${targetId}" not found`);
150
+ }
151
+ container = target;
152
+ } else {
153
+ container = getOrCreateContainer(this._componentId) as HTMLElement;
154
+ }
155
+
156
+ this.container = container;
157
+ const { language, title, maxHeight, wrap } = this.state;
158
+
159
+ container.innerHTML = '';
160
+ container.className = 'jux-code-container';
161
+ container.id = this._componentId;
162
+
163
+ // Title bar
164
+ if (title) {
165
+ const titleBar = document.createElement('div');
166
+ titleBar.className = 'jux-code-title-bar';
167
+
168
+ const titleEl = document.createElement('div');
169
+ titleEl.className = 'jux-code-title';
170
+ titleEl.textContent = title;
171
+
172
+ const langBadge = document.createElement('div');
173
+ langBadge.className = 'jux-code-language-badge';
174
+ langBadge.textContent = language;
175
+
176
+ titleBar.appendChild(titleEl);
177
+ titleBar.appendChild(langBadge);
178
+ container.appendChild(titleBar);
179
+ }
180
+
181
+ // Code block wrapper
182
+ const codeWrapper = document.createElement('div');
183
+ codeWrapper.className = 'jux-code-wrapper';
184
+ if (maxHeight) {
185
+ codeWrapper.style.maxHeight = maxHeight;
186
+ codeWrapper.style.overflowY = 'auto';
187
+ }
188
+
189
+ // Pre element
190
+ const preEl = document.createElement('pre');
191
+ preEl.className = `jux-code-pre language-${language}`;
192
+ if (wrap) {
193
+ preEl.classList.add('jux-code-wrap');
194
+ }
195
+
196
+ preEl.innerHTML = this._formatCode();
197
+
198
+ codeWrapper.appendChild(preEl);
199
+ container.appendChild(codeWrapper);
200
+
201
+ // Copy button
202
+ const copyBtn = document.createElement('button');
203
+ copyBtn.className = 'jux-code-copy-btn';
204
+ copyBtn.textContent = 'Copy';
205
+ container.appendChild(copyBtn);
206
+
207
+ // Event binding - copy functionality
208
+ copyBtn.addEventListener('click', () => {
209
+ navigator.clipboard.writeText(this.state.code).then(() => {
210
+ copyBtn.textContent = 'Copied!';
211
+ setTimeout(() => {
212
+ copyBtn.textContent = 'Copy';
213
+ }, 2000);
214
+ });
215
+ });
216
+
217
+ this.emit('rendered');
218
+ return this;
219
+ }
220
+
221
+ /**
222
+ * Render to another Jux component's container
223
+ */
224
+ renderTo(juxComponent: any): this {
225
+ if (!juxComponent || typeof juxComponent !== 'object') {
226
+ throw new Error('Code.renderTo: Invalid component - not an object');
227
+ }
228
+
229
+ if (!juxComponent._componentId || typeof juxComponent._componentId !== 'string') {
230
+ throw new Error('Code.renderTo: Invalid component - missing _componentId (not a Jux component)');
231
+ }
232
+
233
+ return this.render(`#${juxComponent._componentId}`);
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Factory helper
239
+ */
240
+ export function code(componentId: string, options: CodeOptions = {}): Code {
241
+ return new Code(componentId, options);
242
+ }
@@ -0,0 +1,282 @@
1
+ import { Reactive, getOrCreateContainer } from './reactivity.js';
2
+
3
+ /**
4
+ * Container options
5
+ */
6
+ export interface ContainerOptions {
7
+ direction?: 'horizontal' | 'vertical';
8
+ layout?: 'flex' | 'grid' | 'responsive';
9
+ minWidth?: string;
10
+ gap?: string;
11
+ justify?: 'start' | 'end' | 'center' | 'space-between' | 'space-around' | 'space-evenly';
12
+ align?: 'start' | 'end' | 'center' | 'stretch' | 'baseline';
13
+ wrap?: boolean;
14
+ children?: any[];
15
+ }
16
+
17
+ /**
18
+ * Container state
19
+ */
20
+ type ContainerState = {
21
+ direction: string;
22
+ layout: string;
23
+ minWidth: string;
24
+ gap: string;
25
+ justify: string;
26
+ align: string;
27
+ wrap: boolean;
28
+ children: any[];
29
+ };
30
+
31
+ /**
32
+ * Container component - creates a flex or grid container for organizing child components
33
+ *
34
+ * Usage:
35
+ * const container = jux.container('myContainer', {
36
+ * layout: 'flex',
37
+ * direction: 'vertical',
38
+ * justify: 'space-between',
39
+ * align: 'center'
40
+ * });
41
+ * container.add(component1);
42
+ * container.add([component2, component3]);
43
+ * await container.render();
44
+ *
45
+ * FLEX Layout Options:
46
+ * - layout: 'flex' (default)
47
+ * - direction: 'vertical' | 'horizontal' (default: 'vertical')
48
+ * - justify: 'start' | 'end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' (default: 'start')
49
+ * - align: 'start' | 'end' | 'center' | 'stretch' | 'baseline' (default: 'stretch')
50
+ * - wrap: boolean (default: false)
51
+ * - gap: string (default: '16px')
52
+ *
53
+ * GRID Layout Options:
54
+ * - layout: 'grid'
55
+ * - minWidth: string - for auto-fit (default: '250px')
56
+ * - gap: string (default: '16px')
57
+ * - align: 'start' | 'end' | 'center' | 'stretch' (default: 'stretch')
58
+ * - justify: 'start' | 'end' | 'center' | 'stretch' (default: 'stretch')
59
+ */
60
+ export class Container extends Reactive {
61
+ state!: ContainerState;
62
+ container: HTMLElement | null = null;
63
+
64
+ constructor(componentId: string, options: ContainerOptions = {}) {
65
+ super();
66
+ this._setComponentId(componentId);
67
+
68
+ this.state = this._createReactiveState({
69
+ direction: options.direction ?? 'vertical',
70
+ layout: options.layout ?? 'flex',
71
+ minWidth: options.minWidth ?? '250px',
72
+ gap: options.gap ?? '16px',
73
+ justify: options.justify ?? 'start',
74
+ align: options.align ?? 'stretch',
75
+ wrap: options.wrap ?? false,
76
+ children: Array.isArray(options.children) ? [...options.children] : []
77
+ }) as ContainerState;
78
+ }
79
+
80
+ /* -------------------------
81
+ * Fluent API
82
+ * ------------------------- */
83
+
84
+ direction(value: 'horizontal' | 'vertical'): this {
85
+ this.state.direction = value;
86
+ return this;
87
+ }
88
+
89
+ layout(value: 'flex' | 'grid' | 'responsive'): this {
90
+ this.state.layout = value;
91
+ return this;
92
+ }
93
+
94
+ minWidth(value: string): this {
95
+ this.state.minWidth = value;
96
+ return this;
97
+ }
98
+
99
+ gap(value: string): this {
100
+ this.state.gap = value;
101
+ return this;
102
+ }
103
+
104
+ justify(value: 'start' | 'end' | 'center' | 'space-between' | 'space-around' | 'space-evenly'): this {
105
+ this.state.justify = value;
106
+ return this;
107
+ }
108
+
109
+ align(value: 'start' | 'end' | 'center' | 'stretch' | 'baseline'): this {
110
+ this.state.align = value;
111
+ return this;
112
+ }
113
+
114
+ wrap(value: boolean): this {
115
+ this.state.wrap = value;
116
+ return this;
117
+ }
118
+
119
+ /* -------------------------
120
+ * Add children
121
+ * ------------------------- */
122
+
123
+ add(component: any | any[]): this {
124
+ if (Array.isArray(component)) {
125
+ this.state.children.push(...component);
126
+ } else {
127
+ this.state.children.push(component);
128
+ }
129
+ return this;
130
+ }
131
+
132
+ /* -------------------------
133
+ * Layout styles
134
+ * ------------------------- */
135
+
136
+ private _applyFlexStyles(containerEl: HTMLElement): void {
137
+ const { direction, gap, justify, align, wrap } = this.state;
138
+ containerEl.style.cssText = `
139
+ display: flex;
140
+ flex-direction: ${direction === 'horizontal' ? 'row' : 'column'};
141
+ justify-content: ${justify};
142
+ align-items: ${align};
143
+ flex-wrap: ${wrap ? 'wrap' : 'nowrap'};
144
+ gap: ${gap};
145
+ `;
146
+ }
147
+
148
+ private _applyGridStyles(containerEl: HTMLElement): void {
149
+ const { minWidth, gap, direction } = this.state;
150
+
151
+ if (direction === 'horizontal') {
152
+ // For horizontal grid, use auto-fit columns
153
+ containerEl.style.cssText = `
154
+ display: grid;
155
+ grid-template-columns: repeat(auto-fit, minmax(${minWidth}, 1fr));
156
+ gap: ${gap};
157
+ `;
158
+ } else {
159
+ // For vertical grid, stack rows
160
+ containerEl.style.cssText = `
161
+ display: grid;
162
+ grid-template-columns: 1fr;
163
+ grid-auto-rows: auto;
164
+ gap: ${gap};
165
+ `;
166
+ }
167
+ }
168
+
169
+ /* -------------------------
170
+ * Render
171
+ * ------------------------- */
172
+
173
+ async render(targetId?: string | HTMLElement): Promise<this> {
174
+ let container: HTMLElement;
175
+
176
+ if (targetId) {
177
+ // If targetId is an HTMLElement (passed from parent container), use it directly
178
+ if (targetId instanceof HTMLElement) {
179
+ container = targetId;
180
+ } else {
181
+ // Otherwise query for it
182
+ const target = document.querySelector(targetId);
183
+ if (!target || !(target instanceof HTMLElement)) {
184
+ throw new Error(`Container: Target element "${targetId}" not found`);
185
+ }
186
+ container = target;
187
+ }
188
+ } else {
189
+ container = getOrCreateContainer(this._componentId) as HTMLElement;
190
+ }
191
+
192
+ this.container = container;
193
+ const { layout, children } = this.state;
194
+
195
+ container.innerHTML = '';
196
+ container.className = 'jux-container';
197
+ container.id = this._componentId;
198
+
199
+ // Apply layout-specific styles
200
+ if (layout === 'grid') {
201
+ this._applyGridStyles(container);
202
+ } else {
203
+ this._applyFlexStyles(container);
204
+ }
205
+
206
+ // Render children
207
+ for (let i = 0; i < children.length; i++) {
208
+ const child = children[i];
209
+ if (!child) continue;
210
+
211
+ // Handle string children
212
+ if (typeof child === 'string') {
213
+ const textWrapper = document.createElement('div');
214
+ textWrapper.className = 'jux-container-item';
215
+ textWrapper.style.cssText = 'padding: 1rem; display: flex; align-items: center; justify-content: center; font-weight: bold;';
216
+ textWrapper.textContent = child;
217
+ container.appendChild(textWrapper);
218
+ continue;
219
+ }
220
+
221
+ // Get child ID
222
+ let childId: string | null = null;
223
+ if ((child as any).id) {
224
+ childId = (child as any).id;
225
+ } else if ((child as any)._componentId) {
226
+ childId = (child as any)._componentId;
227
+ } else {
228
+ childId = `${this._componentId}-child-${i}`;
229
+ }
230
+
231
+ // Create wrapper for child
232
+ const childWrapper = document.createElement('div');
233
+ childWrapper.className = 'jux-container-item';
234
+ childWrapper.id = `${childId}-wrapper`;
235
+ container.appendChild(childWrapper);
236
+
237
+ // Render child INTO the wrapper (not to its own container)
238
+ if (typeof (child as any).render === 'function') {
239
+ try {
240
+ // Pass the wrapper element directly, not a selector
241
+ const result = (child as any).render(childWrapper);
242
+ if (result && typeof (result as any).then === 'function') {
243
+ await result;
244
+ }
245
+ } catch (err) {
246
+ console.error(`Container: Error rendering child ${i}:`, err);
247
+ childWrapper.innerHTML = `<div style="color: #ff6b6b; padding: 1rem;">Error: ${(err as Error).message}</div>`;
248
+ }
249
+ } else {
250
+ // If no render method, try to append directly
251
+ if (child instanceof HTMLElement) {
252
+ childWrapper.appendChild(child);
253
+ }
254
+ }
255
+ }
256
+
257
+ this.emit('rendered');
258
+ return this;
259
+ }
260
+
261
+ /**
262
+ * Render to another Jux component's container
263
+ */
264
+ async renderTo(juxComponent: any): Promise<this> {
265
+ if (!juxComponent || typeof juxComponent !== 'object') {
266
+ throw new Error('Container.renderTo: Invalid component - not an object');
267
+ }
268
+
269
+ if (!juxComponent._componentId || typeof juxComponent._componentId !== 'string') {
270
+ throw new Error('Container.renderTo: Invalid component - missing _componentId (not a Jux component)');
271
+ }
272
+
273
+ return this.render(`#${juxComponent._componentId}`);
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Factory helper
279
+ */
280
+ export function container(componentId: string, options: ContainerOptions = {}): Container {
281
+ return new Container(componentId, options);
282
+ }
@@ -0,0 +1,105 @@
1
+ import { Reactive, getOrCreateContainer } from './reactivity.js';
2
+
3
+ /**
4
+ * Data component - SQL query execution
5
+ * Note: No componentId needed - this is a data-only component
6
+ *
7
+ * Usage:
8
+ * const posts = jux.data('SELECT * FROM posts WHERE id = ?', [1]);
9
+ * await posts.execute();
10
+ * console.log(posts.data);
11
+ */
12
+
13
+ export interface DataOptions {
14
+ sql: string;
15
+ params?: any[];
16
+ apiUrl?: string;
17
+ }
18
+
19
+ export class Data {
20
+ private _data: any[] = [];
21
+ private _executed: boolean = false;
22
+ private _loading: boolean = false;
23
+ private _sql: string;
24
+ private _params: any[];
25
+ private _apiUrl: string;
26
+
27
+ constructor(sql: string, params: any[] = [], apiUrl: string = '/api/query') {
28
+ this._sql = sql;
29
+ this._params = params;
30
+ this._apiUrl = apiUrl;
31
+ }
32
+
33
+ /* -------------------------
34
+ * Getters
35
+ * ------------------------- */
36
+
37
+ get data(): any[] {
38
+ return this._data;
39
+ }
40
+
41
+ get executed(): boolean {
42
+ return this._executed;
43
+ }
44
+
45
+ get loading(): boolean {
46
+ return this._loading;
47
+ }
48
+
49
+ get sql(): string {
50
+ return this._sql;
51
+ }
52
+
53
+ set sql(value: string) {
54
+ this._sql = value;
55
+ }
56
+
57
+ /* -------------------------
58
+ * Methods
59
+ * ------------------------- */
60
+
61
+ async execute(): Promise<void> {
62
+ if (this._executed) return;
63
+ this._loading = true;
64
+
65
+ try {
66
+ const response = await fetch(this._apiUrl, {
67
+ method: 'POST',
68
+ headers: {
69
+ 'Content-Type': 'application/json'
70
+ },
71
+ body: JSON.stringify({
72
+ sql: this._sql,
73
+ params: this._params
74
+ })
75
+ });
76
+
77
+ if (!response.ok) {
78
+ throw new Error(`API error: ${response.statusText}`);
79
+ }
80
+
81
+ const result = await response.json();
82
+ this._data = result.data || [];
83
+ this._executed = true;
84
+ } catch (error) {
85
+ console.error('Data execution error:', error);
86
+ this._data = [];
87
+ throw error;
88
+ } finally {
89
+ this._loading = false;
90
+ }
91
+ }
92
+
93
+ reset(): void {
94
+ this._executed = false;
95
+ this._data = [];
96
+ this._loading = false;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Factory helper
102
+ */
103
+ export function data(sql: string, params: any[] = [], apiUrl: string = '/api/query'): Data {
104
+ return new Data(sql, params, apiUrl);
105
+ }