juxscript 1.0.3 → 1.0.5

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 (73) hide show
  1. package/README.md +37 -92
  2. package/bin/cli.js +57 -56
  3. package/lib/components/alert.ts +240 -0
  4. package/lib/components/app.ts +216 -82
  5. package/lib/components/badge.ts +164 -0
  6. package/lib/components/barchart.ts +1248 -0
  7. package/lib/components/button.ts +188 -53
  8. package/lib/components/card.ts +75 -61
  9. package/lib/components/chart.ts +17 -15
  10. package/lib/components/checkbox.ts +199 -0
  11. package/lib/components/code.ts +66 -152
  12. package/lib/components/container.ts +104 -208
  13. package/lib/components/data.ts +1 -3
  14. package/lib/components/datepicker.ts +226 -0
  15. package/lib/components/dialog.ts +258 -0
  16. package/lib/components/docs-data.json +1969 -423
  17. package/lib/components/dropdown.ts +244 -0
  18. package/lib/components/element.ts +271 -0
  19. package/lib/components/fileupload.ts +319 -0
  20. package/lib/components/footer.ts +37 -18
  21. package/lib/components/header.ts +53 -33
  22. package/lib/components/heading.ts +119 -0
  23. package/lib/components/helpers.ts +34 -0
  24. package/lib/components/hero.ts +57 -31
  25. package/lib/components/include.ts +292 -0
  26. package/lib/components/input.ts +508 -77
  27. package/lib/components/layout.ts +144 -18
  28. package/lib/components/list.ts +83 -74
  29. package/lib/components/loading.ts +263 -0
  30. package/lib/components/main.ts +43 -17
  31. package/lib/components/menu.ts +108 -24
  32. package/lib/components/modal.ts +50 -21
  33. package/lib/components/nav.ts +60 -18
  34. package/lib/components/paragraph.ts +111 -0
  35. package/lib/components/progress.ts +276 -0
  36. package/lib/components/radio.ts +236 -0
  37. package/lib/components/req.ts +300 -0
  38. package/lib/components/script.ts +33 -74
  39. package/lib/components/select.ts +280 -0
  40. package/lib/components/sidebar.ts +87 -37
  41. package/lib/components/style.ts +47 -70
  42. package/lib/components/switch.ts +261 -0
  43. package/lib/components/table.ts +47 -24
  44. package/lib/components/tabs.ts +105 -63
  45. package/lib/components/theme-toggle.ts +361 -0
  46. package/lib/components/token-calculator.ts +380 -0
  47. package/lib/components/tooltip.ts +244 -0
  48. package/lib/components/view.ts +36 -20
  49. package/lib/components/write.ts +284 -0
  50. package/lib/globals.d.ts +21 -0
  51. package/lib/jux.ts +178 -68
  52. package/lib/presets/notion.css +521 -0
  53. package/lib/presets/notion.jux +27 -0
  54. package/lib/reactivity/state.ts +364 -0
  55. package/lib/themes/charts.js +126 -0
  56. package/machinery/compiler.js +126 -38
  57. package/machinery/generators/html.js +2 -3
  58. package/machinery/server.js +2 -2
  59. package/package.json +29 -3
  60. package/lib/components/import.ts +0 -430
  61. package/lib/components/node.ts +0 -200
  62. package/lib/components/reactivity.js +0 -104
  63. package/lib/components/theme.ts +0 -97
  64. package/lib/layouts/notion.css +0 -258
  65. package/lib/styles/base-theme.css +0 -186
  66. package/lib/styles/dark-theme.css +0 -144
  67. package/lib/styles/light-theme.css +0 -144
  68. package/lib/styles/tokens/dark.css +0 -86
  69. package/lib/styles/tokens/light.css +0 -86
  70. package/lib/templates/index.juxt +0 -33
  71. package/lib/themes/dark.css +0 -86
  72. package/lib/themes/light.css +0 -86
  73. /package/lib/{styles → presets}/global.css +0 -0
@@ -1,184 +1,124 @@
1
- import { Reactive, getOrCreateContainer } from './reactivity.js';
1
+ import { getOrCreateContainer } from './helpers.js';
2
2
 
3
3
  /**
4
4
  * Container options
5
5
  */
6
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[];
7
+ class?: string;
8
+ style?: string;
9
+ direction?: 'row' | 'column';
10
+ gap?: string | number;
11
+ align?: 'start' | 'center' | 'end' | 'stretch';
12
+ justify?: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly';
15
13
  }
16
14
 
17
15
  /**
18
16
  * Container state
19
17
  */
20
18
  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[];
19
+ class: string;
20
+ style: string;
21
+ direction?: 'row' | 'column';
22
+ gap?: string | number;
23
+ align?: string;
24
+ justify?: string;
29
25
  };
30
26
 
31
27
  /**
32
- * Container component - creates a flex or grid container for organizing child components
28
+ * Container component - a simple div container for grouping elements
33
29
  *
34
30
  * 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();
31
+ * // Plain container
32
+ * jux.container('wrapper').render('#app');
44
33
  *
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')
34
+ * // Flex container with layout
35
+ * jux.container('toolbar')
36
+ * .direction('row')
37
+ * .gap(16)
38
+ * .render('#app');
59
39
  */
60
- export class Container extends Reactive {
61
- state!: ContainerState;
40
+ export class Container {
41
+ state: ContainerState;
62
42
  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;
43
+ _id: string;
44
+ id: string;
45
+
46
+ constructor(id: string, options: ContainerOptions = {}) {
47
+ this._id = id;
48
+ this.id = id;
49
+
50
+ this.state = {
51
+ class: options.class ?? '',
52
+ style: options.style ?? '',
53
+ direction: options.direction,
54
+ gap: options.gap,
55
+ align: options.align,
56
+ justify: options.justify
57
+ };
78
58
  }
79
59
 
80
60
  /* -------------------------
81
61
  * Fluent API
82
62
  * ------------------------- */
83
63
 
84
- direction(value: 'horizontal' | 'vertical'): this {
85
- this.state.direction = value;
64
+ class(value: string): this {
65
+ this.state.class = value;
86
66
  return this;
87
67
  }
88
68
 
89
- layout(value: 'flex' | 'grid' | 'responsive'): this {
90
- this.state.layout = value;
69
+ style(value: string): this {
70
+ this.state.style = value;
91
71
  return this;
92
72
  }
93
73
 
94
- minWidth(value: string): this {
95
- this.state.minWidth = value;
74
+ /**
75
+ * Set flex direction (row = horizontal, column = vertical)
76
+ * Automatically enables flexbox when called
77
+ */
78
+ direction(value: 'row' | 'column'): this {
79
+ this.state.direction = value;
96
80
  return this;
97
81
  }
98
82
 
99
- gap(value: string): this {
83
+ /**
84
+ * Set gap between children
85
+ * Automatically enables flexbox when called
86
+ * @param value - Gap size (number = px, string = any CSS unit)
87
+ */
88
+ gap(value: string | number): this {
100
89
  this.state.gap = value;
101
90
  return this;
102
91
  }
103
92
 
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 {
93
+ /**
94
+ * Set alignment of children along cross axis
95
+ * Automatically enables flexbox when called
96
+ */
97
+ align(value: 'start' | 'center' | 'end' | 'stretch'): this {
110
98
  this.state.align = value;
111
99
  return this;
112
100
  }
113
101
 
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
- }
102
+ /**
103
+ * Set justification of children along main axis
104
+ * Automatically enables flexbox when called
105
+ */
106
+ justify(value: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly'): this {
107
+ this.state.justify = value;
129
108
  return this;
130
109
  }
131
110
 
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
111
  /* -------------------------
170
112
  * Render
171
113
  * ------------------------- */
172
114
 
173
- async render(targetId?: string | HTMLElement): Promise<this> {
115
+ render(targetId?: string | HTMLElement): this {
174
116
  let container: HTMLElement;
175
-
117
+
176
118
  if (targetId) {
177
- // If targetId is an HTMLElement (passed from parent container), use it directly
178
119
  if (targetId instanceof HTMLElement) {
179
120
  container = targetId;
180
121
  } else {
181
- // Otherwise query for it
182
122
  const target = document.querySelector(targetId);
183
123
  if (!target || !(target instanceof HTMLElement)) {
184
124
  throw new Error(`Container: Target element "${targetId}" not found`);
@@ -186,97 +126,53 @@ export class Container extends Reactive {
186
126
  container = target;
187
127
  }
188
128
  } else {
189
- container = getOrCreateContainer(this._componentId) as HTMLElement;
129
+ container = getOrCreateContainer(this._id);
190
130
  }
191
-
131
+
192
132
  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
- }
133
+ const { class: className, style, direction, gap, align, justify } = this.state;
134
+
135
+ const div = document.createElement('div');
136
+ div.id = this._id;
137
+
138
+ // Always include jux-container class, append custom classes
139
+ div.className = className ? `jux-container ${className}` : 'jux-container';
140
+
141
+ // Only apply flex styles if any flex properties are set
142
+ const usesFlexbox = direction || gap !== undefined || align || justify;
143
+
144
+ let computedStyle = style;
145
+
146
+ if (usesFlexbox) {
147
+ const flexStyles: string[] = ['display: flex', 'width: max-content'];
148
+
149
+ if (direction) flexStyles.push(`flex-direction: ${direction}`);
150
+
151
+ if (gap !== undefined) {
152
+ const gapValue = typeof gap === 'number' ? `${gap}px` : gap;
153
+ flexStyles.push(`gap: ${gapValue}`);
254
154
  }
255
- }
256
-
257
- this.emit('rendered');
258
- return this;
259
- }
155
+ // Only set align-items if explicitly specified
156
+ if (align) flexStyles.push(`align-items: ${align}`);
157
+ // Only set justify-content if explicitly specified
158
+ if (justify) flexStyles.push(`justify-content: ${justify}`);
260
159
 
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');
160
+ computedStyle = `${flexStyles.join('; ')}; ${style}`.trim();
267
161
  }
268
-
269
- if (!juxComponent._componentId || typeof juxComponent._componentId !== 'string') {
270
- throw new Error('Container.renderTo: Invalid component - missing _componentId (not a Jux component)');
162
+
163
+ if (computedStyle) {
164
+ div.setAttribute('style', computedStyle);
271
165
  }
272
-
273
- return this.render(`#${juxComponent._componentId}`);
166
+
167
+ container.appendChild(div);
168
+
169
+ return this;
274
170
  }
275
171
  }
276
172
 
277
173
  /**
278
174
  * Factory helper
279
175
  */
280
- export function container(componentId: string, options: ContainerOptions = {}): Container {
281
- return new Container(componentId, options);
176
+ export function container(id: string, options: ContainerOptions = {}): Container {
177
+ return new Container(id, options);
282
178
  }
@@ -1,8 +1,6 @@
1
- import { Reactive, getOrCreateContainer } from './reactivity.js';
2
-
3
1
  /**
4
2
  * Data component - SQL query execution
5
- * Note: No componentId needed - this is a data-only component
3
+ * Note: No id needed - this is a data-only component
6
4
  *
7
5
  * Usage:
8
6
  * const posts = jux.data('SELECT * FROM posts WHERE id = ?', [1]);
@@ -0,0 +1,226 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+ import { State } from '../reactivity/state.js';
3
+
4
+ /**
5
+ * DatePicker component options
6
+ */
7
+ export interface DatePickerOptions {
8
+ value?: string;
9
+ min?: string;
10
+ max?: string;
11
+ placeholder?: string;
12
+ disabled?: boolean;
13
+ name?: string;
14
+ onChange?: (value: string) => void;
15
+ style?: string;
16
+ class?: string;
17
+ }
18
+
19
+ /**
20
+ * DatePicker component state
21
+ */
22
+ type DatePickerState = {
23
+ value: string;
24
+ min: string;
25
+ max: string;
26
+ placeholder: string;
27
+ disabled: boolean;
28
+ name: string;
29
+ style: string;
30
+ class: string;
31
+ };
32
+
33
+ /**
34
+ * DatePicker component - Calendar input
35
+ *
36
+ * Usage:
37
+ * jux.datepicker('start-date', {
38
+ * placeholder: 'Select date',
39
+ * value: '2024-01-15',
40
+ * onChange: (date) => console.log(date)
41
+ * }).render('#form');
42
+ *
43
+ * // Two-way binding
44
+ * const dateState = state('2024-01-15');
45
+ * jux.datepicker('date').bind(dateState).render('#form');
46
+ */
47
+ export class DatePicker {
48
+ state: DatePickerState;
49
+ container: HTMLElement | null = null;
50
+ _id: string;
51
+ id: string;
52
+ private _onChange?: (value: string) => void;
53
+ private _boundState?: State<string>;
54
+
55
+ constructor(id: string, options: DatePickerOptions = {}) {
56
+ this._id = id;
57
+ this.id = id;
58
+ this._onChange = options.onChange;
59
+
60
+ this.state = {
61
+ value: options.value ?? '',
62
+ min: options.min ?? '',
63
+ max: options.max ?? '',
64
+ placeholder: options.placeholder ?? 'Select date',
65
+ disabled: options.disabled ?? false,
66
+ name: options.name ?? id,
67
+ style: options.style ?? '',
68
+ class: options.class ?? ''
69
+ };
70
+ }
71
+
72
+ /* -------------------------
73
+ * Fluent API
74
+ * ------------------------- */
75
+
76
+ value(value: string): this {
77
+ this.state.value = value;
78
+ this._updateElement();
79
+ return this;
80
+ }
81
+
82
+ min(value: string): this {
83
+ this.state.min = value;
84
+ return this;
85
+ }
86
+
87
+ max(value: string): this {
88
+ this.state.max = value;
89
+ return this;
90
+ }
91
+
92
+ placeholder(value: string): this {
93
+ this.state.placeholder = value;
94
+ return this;
95
+ }
96
+
97
+ disabled(value: boolean): this {
98
+ this.state.disabled = value;
99
+ this._updateElement();
100
+ return this;
101
+ }
102
+
103
+ name(value: string): this {
104
+ this.state.name = value;
105
+ return this;
106
+ }
107
+
108
+ style(value: string): this {
109
+ this.state.style = value;
110
+ return this;
111
+ }
112
+
113
+ class(value: string): this {
114
+ this.state.class = value;
115
+ return this;
116
+ }
117
+
118
+ onChange(handler: (value: string) => void): this {
119
+ this._onChange = handler;
120
+ return this;
121
+ }
122
+
123
+ /**
124
+ * Two-way binding to state
125
+ */
126
+ bind(stateObj: State<string>): this {
127
+ this._boundState = stateObj;
128
+
129
+ stateObj.subscribe((val) => {
130
+ this.state.value = val;
131
+ this._updateElement();
132
+ });
133
+
134
+ this.onChange((value) => stateObj.set(value));
135
+
136
+ return this;
137
+ }
138
+
139
+ /* -------------------------
140
+ * Helpers
141
+ * ------------------------- */
142
+
143
+ private _updateElement(): void {
144
+ const input = document.getElementById(`${this._id}-input`) as HTMLInputElement;
145
+ if (input) {
146
+ input.value = this.state.value;
147
+ input.disabled = this.state.disabled;
148
+ }
149
+ }
150
+
151
+ getValue(): string {
152
+ return this.state.value;
153
+ }
154
+
155
+ getDate(): Date | null {
156
+ return this.state.value ? new Date(this.state.value) : null;
157
+ }
158
+
159
+ /* -------------------------
160
+ * Render
161
+ * ------------------------- */
162
+
163
+ render(targetId?: string): this {
164
+ let container: HTMLElement;
165
+
166
+ if (targetId) {
167
+ const target = document.querySelector(targetId);
168
+ if (!target || !(target instanceof HTMLElement)) {
169
+ throw new Error(`DatePicker: Target element "${targetId}" not found`);
170
+ }
171
+ container = target;
172
+ } else {
173
+ container = getOrCreateContainer(this._id);
174
+ }
175
+
176
+ this.container = container;
177
+ const { value, min, max, placeholder, disabled, name, style, class: className } = this.state;
178
+
179
+ const wrapper = document.createElement('div');
180
+ wrapper.className = 'jux-datepicker';
181
+ wrapper.id = this._id;
182
+
183
+ if (className) {
184
+ wrapper.className += ` ${className}`;
185
+ }
186
+
187
+ if (style) {
188
+ wrapper.setAttribute('style', style);
189
+ }
190
+
191
+ const input = document.createElement('input');
192
+ input.type = 'date';
193
+ input.className = 'jux-datepicker-input';
194
+ input.id = `${this._id}-input`;
195
+ input.name = name;
196
+ input.value = value;
197
+ input.disabled = disabled;
198
+
199
+ if (min) input.min = min;
200
+ if (max) input.max = max;
201
+ if (placeholder) input.setAttribute('placeholder', placeholder);
202
+
203
+ input.addEventListener('change', (e) => {
204
+ const target = e.target as HTMLInputElement;
205
+ this.state.value = target.value;
206
+ if (this._onChange) {
207
+ this._onChange(target.value);
208
+ }
209
+ });
210
+
211
+ wrapper.appendChild(input);
212
+ container.appendChild(wrapper);
213
+ return this;
214
+ }
215
+
216
+ renderTo(juxComponent: any): this {
217
+ if (!juxComponent?._id) {
218
+ throw new Error('DatePicker.renderTo: Invalid component');
219
+ }
220
+ return this.render(`#${juxComponent._id}`);
221
+ }
222
+ }
223
+
224
+ export function datepicker(id: string, options: DatePickerOptions = {}): DatePicker {
225
+ return new DatePicker(id, options);
226
+ }