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,19 +1,45 @@
1
1
  import { ErrorHandler } from './error-handler.js';
2
2
 
3
3
  /**
4
- * Layout - Load a JUX layout file
5
- * Auto-loads when file is set
4
+ * Layout component interface
5
+ * Represents any Jux component that can be rendered
6
+ */
7
+ interface LayoutComponent {
8
+ render: (target?: string) => any;
9
+ _componentId?: string;
10
+ _id?: string;
11
+ }
12
+
13
+ /**
14
+ * Layout - Compose and render multiple components
15
+ *
16
+ * Usage:
17
+ * // As a composition function
18
+ * jux.layout(
19
+ * jux.include('/lib/presets/notion/notion.css'),
20
+ * jux.header('appheader').render('#app'),
21
+ * jux.sidebar('appsidebar').render('#app'),
22
+ * jux.main('appmain').render('#app')
23
+ * );
24
+ *
25
+ * // Or load from a .jux file
26
+ * jux.layout('/lib/presets/notion/main.jux');
6
27
  */
7
28
  export class Layout {
8
29
  private _juxFile: string;
9
30
  private _loaded: boolean = false;
31
+ private _components: any[] = [];
10
32
 
11
- constructor(juxFile: string = '') {
12
- this._juxFile = juxFile;
13
-
14
- // Auto-load if file provided
15
- if (juxFile) {
33
+ constructor(...args: any[]) {
34
+ // If first arg is string, it's a file path
35
+ if (args.length === 1 && typeof args[0] === 'string' && args[0].endsWith('.jux')) {
36
+ this._juxFile = args[0];
16
37
  this.load();
38
+ } else {
39
+ // Otherwise, it's a list of components to compose
40
+ this._juxFile = '';
41
+ this._components = args;
42
+ this._compose();
17
43
  }
18
44
  }
19
45
 
@@ -34,6 +60,41 @@ export class Layout {
34
60
  return this._juxFile;
35
61
  }
36
62
 
63
+ /**
64
+ * Compose components immediately
65
+ * This handles the case where components are passed directly
66
+ */
67
+ private _compose(): this {
68
+ try {
69
+ // Components are already rendered via chaining
70
+ // This just ensures they're tracked
71
+ this._components.forEach((component, index) => {
72
+ // Components that return themselves from .render() are already in DOM
73
+ // Just log for debugging
74
+ if (component && typeof component === 'object') {
75
+ const id = component._componentId || component._id || component.id || `component-${index}`;
76
+ console.log(`✓ Layout component composed: ${id}`);
77
+ }
78
+ });
79
+
80
+ this._loaded = true;
81
+ } catch (error: any) {
82
+ ErrorHandler.captureError({
83
+ component: 'Layout',
84
+ method: '_compose',
85
+ message: `Failed to compose layout: ${error.message}`,
86
+ stack: error.stack,
87
+ timestamp: new Date(),
88
+ context: {
89
+ componentCount: this._components.length,
90
+ error: 'composition_failed'
91
+ }
92
+ });
93
+ }
94
+
95
+ return this;
96
+ }
97
+
37
98
  /**
38
99
  * Normalize path to absolute URL from site root
39
100
  */
@@ -42,22 +103,22 @@ export class Layout {
42
103
  if (path.startsWith('http://') || path.startsWith('https://')) {
43
104
  return path;
44
105
  }
45
-
106
+
46
107
  // Convert relative path to absolute
47
108
  // Remove leading './' if present
48
109
  let cleanPath = path.replace(/^\.\//, '');
49
-
110
+
50
111
  // Ensure it starts with /
51
112
  if (!cleanPath.startsWith('/')) {
52
113
  cleanPath = '/' + cleanPath;
53
114
  }
54
-
115
+
55
116
  // Return absolute URL with origin
56
117
  return new URL(cleanPath, window.location.origin).href;
57
118
  }
58
119
 
59
120
  /**
60
- * Load the layout
121
+ * Load the layout from a .jux file
61
122
  * This will dynamically import the compiled JS file
62
123
  */
63
124
  async load(): Promise<this> {
@@ -65,25 +126,33 @@ export class Layout {
65
126
  return this;
66
127
  }
67
128
 
129
+ if (!this._juxFile) {
130
+ console.warn('Layout: No file specified to load');
131
+ return this;
132
+ }
133
+
68
134
  try {
69
135
  // Convert .jux to .js for the compiled output
70
136
  let jsFile = this._juxFile.replace(/\.jux$/, '.js');
71
-
137
+
72
138
  // Normalize to absolute URL for browser import
73
139
  jsFile = this.normalizePath(jsFile);
74
-
140
+
75
141
  console.log(`Loading layout: ${jsFile}`);
76
-
142
+
77
143
  // Dynamic import of the layout module
78
144
  const layoutModule = await import(jsFile);
79
-
145
+
80
146
  // If the module has an init or default export, call it
81
147
  if (typeof layoutModule.default === 'function') {
82
148
  await layoutModule.default();
83
149
  } else if (typeof layoutModule.init === 'function') {
84
150
  await layoutModule.init();
151
+ } else if (layoutModule.default && typeof layoutModule.default === 'object') {
152
+ // If default export is the layout object itself
153
+ console.log('✓ Layout module loaded (object export)');
85
154
  }
86
-
155
+
87
156
  this._loaded = true;
88
157
  console.log(`✓ Layout loaded: ${this._juxFile}`);
89
158
  } catch (error: any) {
@@ -93,7 +162,7 @@ export class Layout {
93
162
  message: `Failed to load layout: ${error.message}`,
94
163
  stack: error.stack,
95
164
  timestamp: new Date(),
96
- context: {
165
+ context: {
97
166
  juxFile: this._juxFile,
98
167
  jsFile: this._juxFile.replace(/\.jux$/, '.js'),
99
168
  errorCode: error.code
@@ -110,4 +179,61 @@ export class Layout {
110
179
  isLoaded(): boolean {
111
180
  return this._loaded;
112
181
  }
113
- }
182
+
183
+ /**
184
+ * Get all composed components
185
+ */
186
+ getComponents(): any[] {
187
+ return this._components;
188
+ }
189
+
190
+ /**
191
+ * Add a component to the layout
192
+ */
193
+ add(component: any): this {
194
+ this._components.push(component);
195
+ return this;
196
+ }
197
+
198
+ /**
199
+ * Render all components
200
+ * Useful if components weren't already rendered
201
+ */
202
+ render(target?: string): this {
203
+ this._components.forEach((component) => {
204
+ if (component && typeof component.render === 'function') {
205
+ try {
206
+ component.render(target);
207
+ } catch (error: any) {
208
+ ErrorHandler.captureError({
209
+ component: 'Layout',
210
+ method: 'render',
211
+ message: `Failed to render component: ${error.message}`,
212
+ stack: error.stack,
213
+ timestamp: new Date(),
214
+ context: {
215
+ componentId: component._componentId || component._id || 'unknown',
216
+ target
217
+ }
218
+ });
219
+ }
220
+ }
221
+ });
222
+
223
+ return this;
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Factory helper
229
+ * Accepts either:
230
+ * - A file path: layout('/lib/presets/notion/main.jux')
231
+ * - A list of components: layout(header, sidebar, main)
232
+ */
233
+ export function layout(...args: any[]): Layout {
234
+ return new Layout(...args);
235
+ }
236
+
237
+ /* -------------------------
238
+ * Module Exports
239
+ * ------------------------- */
@@ -1,4 +1,4 @@
1
- import { Reactive, getOrCreateContainer } from './reactivity.js';
1
+ import { getOrCreateContainer } from './helpers.js';
2
2
 
3
3
  /**
4
4
  * List item interface
@@ -23,6 +23,8 @@ export interface ListOptions {
23
23
  selectedIndex?: number | null;
24
24
  onItemClick?: (item: ListItem, index: number, e: Event) => void;
25
25
  onItemDoubleClick?: (item: ListItem, index: number, e: Event) => void;
26
+ style?: string;
27
+ class?: string;
26
28
  }
27
29
 
28
30
  /**
@@ -35,6 +37,8 @@ type ListState = {
35
37
  direction: string;
36
38
  selectable: boolean;
37
39
  selectedIndex: number | null;
40
+ style: string;
41
+ class: string;
38
42
  };
39
43
 
40
44
  /**
@@ -62,25 +66,29 @@ type ListState = {
62
66
  * // Move item from index 0 to index 2
63
67
  * myList.move(0, 2);
64
68
  */
65
- export class List extends Reactive {
66
- state!: ListState;
69
+ export class List {
70
+ state: ListState;
67
71
  container: HTMLElement | null = null;
72
+ _id: string;
73
+ id: string;
68
74
  private _onItemClick: ((item: ListItem, index: number, e: Event) => void) | null;
69
75
  private _onItemDoubleClick: ((item: ListItem, index: number, e: Event) => void) | null;
70
76
 
71
- constructor(componentId: string, options: ListOptions = {}) {
72
- super();
73
- this._setComponentId(componentId);
74
-
75
- this.state = this._createReactiveState({
77
+ constructor(id: string, options: ListOptions = {}) {
78
+ this._id = id;
79
+ this.id = id;
80
+
81
+ this.state = {
76
82
  items: options.items ?? [],
77
83
  header: options.header ?? '',
78
84
  gap: options.gap ?? '0.5rem',
79
85
  direction: options.direction ?? 'vertical',
80
86
  selectable: options.selectable ?? false,
81
- selectedIndex: options.selectedIndex ?? null
82
- }) as ListState;
83
-
87
+ selectedIndex: options.selectedIndex ?? null,
88
+ style: options.style ?? '',
89
+ class: options.class ?? ''
90
+ };
91
+
84
92
  this._onItemClick = options.onItemClick ?? null;
85
93
  this._onItemDoubleClick = options.onItemDoubleClick ?? null;
86
94
  }
@@ -114,22 +122,31 @@ export class List extends Reactive {
114
122
  return this;
115
123
  }
116
124
 
125
+ style(value: string): this {
126
+ this.state.style = value;
127
+ return this;
128
+ }
129
+
130
+ class(value: string): this {
131
+ this.state.class = value;
132
+ return this;
133
+ }
134
+
117
135
  /* -------------------------
118
136
  * List operations
119
137
  * ------------------------- */
120
138
 
121
139
  add(item: ListItem, index?: number): this {
122
140
  const items = [...this.state.items];
123
-
141
+
124
142
  if (typeof index === 'number' && index >= 0 && index <= items.length) {
125
143
  items.splice(index, 0, item);
126
144
  } else {
127
145
  index = items.length;
128
146
  items.push(item);
129
147
  }
130
-
148
+
131
149
  this.state.items = items;
132
- this.emit('itemAdded', { item, index });
133
150
  this._updateDOM();
134
151
  return this;
135
152
  }
@@ -139,10 +156,10 @@ export class List extends Reactive {
139
156
  console.error(`List: Invalid index ${index} for remove`);
140
157
  return this;
141
158
  }
142
-
159
+
143
160
  const items = [...this.state.items];
144
- const removed = items.splice(index, 1)[0];
145
-
161
+ items.splice(index, 1);
162
+
146
163
  // Adjust selected index
147
164
  if (this.state.selectedIndex !== null) {
148
165
  if (this.state.selectedIndex === index) {
@@ -151,33 +168,32 @@ export class List extends Reactive {
151
168
  this.state.selectedIndex--;
152
169
  }
153
170
  }
154
-
171
+
155
172
  this.state.items = items;
156
- this.emit('itemRemoved', { item: removed, index });
157
173
  this._updateDOM();
158
174
  return this;
159
175
  }
160
176
 
161
177
  move(fromIndex: number, toIndex: number): this {
162
178
  const items = [...this.state.items];
163
-
179
+
164
180
  if (fromIndex < 0 || fromIndex >= items.length) {
165
181
  console.error(`List: Invalid fromIndex ${fromIndex}`);
166
182
  return this;
167
183
  }
168
-
184
+
169
185
  if (toIndex < 0 || toIndex >= items.length) {
170
186
  console.error(`List: Invalid toIndex ${toIndex}`);
171
187
  return this;
172
188
  }
173
-
189
+
174
190
  if (fromIndex === toIndex) {
175
191
  return this;
176
192
  }
177
-
193
+
178
194
  const [movedItem] = items.splice(fromIndex, 1);
179
195
  items.splice(toIndex, 0, movedItem);
180
-
196
+
181
197
  // Adjust selected index
182
198
  if (this.state.selectedIndex !== null) {
183
199
  if (this.state.selectedIndex === fromIndex) {
@@ -188,9 +204,8 @@ export class List extends Reactive {
188
204
  this.state.selectedIndex++;
189
205
  }
190
206
  }
191
-
207
+
192
208
  this.state.items = items;
193
- this.emit('itemMoved', { item: movedItem, fromIndex, toIndex });
194
209
  this._updateDOM();
195
210
  return this;
196
211
  }
@@ -200,16 +215,8 @@ export class List extends Reactive {
200
215
  console.error(`List: Invalid index ${index} for select`);
201
216
  return this;
202
217
  }
203
-
204
- const previousIndex = this.state.selectedIndex;
218
+
205
219
  this.state.selectedIndex = index;
206
-
207
- this.emit('itemSelect', {
208
- item: this.state.items[index],
209
- index,
210
- previousIndex
211
- });
212
-
213
220
  this._updateDOM();
214
221
  return this;
215
222
  }
@@ -218,11 +225,8 @@ export class List extends Reactive {
218
225
  if (this.state.selectedIndex === null) {
219
226
  return this;
220
227
  }
221
-
222
- const previousIndex = this.state.selectedIndex;
228
+
223
229
  this.state.selectedIndex = null;
224
-
225
- this.emit('itemDeselect', { previousIndex });
226
230
  this._updateDOM();
227
231
  return this;
228
232
  }
@@ -243,7 +247,7 @@ export class List extends Reactive {
243
247
 
244
248
  private _updateDOM(): void {
245
249
  if (!this.container) return;
246
-
250
+
247
251
  // Clear and re-render
248
252
  this.container.innerHTML = '';
249
253
  this._renderContent();
@@ -251,13 +255,21 @@ export class List extends Reactive {
251
255
 
252
256
  private _renderContent(): void {
253
257
  if (!this.container) return;
254
-
255
- const { items, header, gap, direction, selectable, selectedIndex } = this.state;
256
-
258
+
259
+ const { items, header, gap, direction, selectable, selectedIndex, style, class: className } = this.state;
260
+
257
261
  const wrapper = document.createElement('div');
258
262
  wrapper.className = 'jux-list-wrapper';
259
- wrapper.id = this._componentId;
260
-
263
+ wrapper.id = this._id;
264
+
265
+ if (className) {
266
+ wrapper.className += ` ${className}`;
267
+ }
268
+
269
+ if (style) {
270
+ wrapper.setAttribute('style', style);
271
+ }
272
+
261
273
  // Header
262
274
  if (header) {
263
275
  const headerEl = document.createElement('div');
@@ -265,21 +277,21 @@ export class List extends Reactive {
265
277
  headerEl.textContent = header;
266
278
  wrapper.appendChild(headerEl);
267
279
  }
268
-
280
+
269
281
  // List container
270
282
  const listContainer = document.createElement('div');
271
283
  listContainer.className = `jux-list jux-list-${direction}`;
272
284
  listContainer.style.gap = gap;
273
-
285
+
274
286
  // Render items
275
287
  items.forEach((item, index) => {
276
288
  const itemEl = document.createElement('div');
277
289
  itemEl.className = `jux-list-item jux-list-item-${item.type || 'default'}`;
278
-
290
+
279
291
  if (selectable && selectedIndex === index) {
280
292
  itemEl.classList.add('jux-list-item-selected');
281
293
  }
282
-
294
+
283
295
  // Icon
284
296
  if (item.icon) {
285
297
  const iconEl = document.createElement('span');
@@ -287,27 +299,27 @@ export class List extends Reactive {
287
299
  iconEl.textContent = item.icon;
288
300
  itemEl.appendChild(iconEl);
289
301
  }
290
-
302
+
291
303
  // Content
292
304
  const contentEl = document.createElement('div');
293
305
  contentEl.className = 'jux-list-item-content';
294
-
306
+
295
307
  if (item.title) {
296
308
  const titleEl = document.createElement('div');
297
309
  titleEl.className = 'jux-list-item-title';
298
310
  titleEl.textContent = item.title;
299
311
  contentEl.appendChild(titleEl);
300
312
  }
301
-
313
+
302
314
  if (item.body) {
303
315
  const bodyEl = document.createElement('div');
304
316
  bodyEl.className = 'jux-list-item-body';
305
317
  bodyEl.textContent = item.body;
306
318
  contentEl.appendChild(bodyEl);
307
319
  }
308
-
320
+
309
321
  itemEl.appendChild(contentEl);
310
-
322
+
311
323
  // Metadata
312
324
  if (item.metadata) {
313
325
  const metadataEl = document.createElement('span');
@@ -315,30 +327,27 @@ export class List extends Reactive {
315
327
  metadataEl.textContent = item.metadata;
316
328
  itemEl.appendChild(metadataEl);
317
329
  }
318
-
330
+
319
331
  listContainer.appendChild(itemEl);
320
-
332
+
321
333
  // Event binding - click handlers
322
334
  itemEl.addEventListener('click', (e) => {
323
335
  if (selectable) {
324
336
  this.select(index);
325
337
  }
326
-
327
- this.emit('itemClick', { item, index, event: e });
328
-
338
+
329
339
  if (this._onItemClick) {
330
340
  this._onItemClick(item, index, e);
331
341
  }
332
342
  });
333
-
343
+
334
344
  if (this._onItemDoubleClick) {
335
345
  itemEl.addEventListener('dblclick', (e) => {
336
- this.emit('itemDoubleClick', { item, index, event: e });
337
346
  this._onItemDoubleClick!(item, index, e);
338
347
  });
339
348
  }
340
349
  });
341
-
350
+
342
351
  wrapper.appendChild(listContainer);
343
352
  this.container.appendChild(wrapper);
344
353
  }
@@ -349,7 +358,7 @@ export class List extends Reactive {
349
358
 
350
359
  render(targetId?: string): this {
351
360
  let container: HTMLElement;
352
-
361
+
353
362
  if (targetId) {
354
363
  const target = document.querySelector(targetId);
355
364
  if (!target || !(target instanceof HTMLElement)) {
@@ -357,14 +366,14 @@ export class List extends Reactive {
357
366
  }
358
367
  container = target;
359
368
  } else {
360
- container = getOrCreateContainer(this._componentId) as HTMLElement;
369
+ container = getOrCreateContainer(this._id);
361
370
  }
362
-
371
+
363
372
  this.container = container;
364
373
  this.container.innerHTML = '';
365
-
374
+
366
375
  this._renderContent();
367
-
376
+
368
377
  return this;
369
378
  }
370
379
 
@@ -375,18 +384,18 @@ export class List extends Reactive {
375
384
  if (!juxComponent || typeof juxComponent !== 'object') {
376
385
  throw new Error('List.renderTo: Invalid component - not an object');
377
386
  }
378
-
379
- if (!juxComponent._componentId || typeof juxComponent._componentId !== 'string') {
380
- throw new Error('List.renderTo: Invalid component - missing _componentId (not a Jux component)');
387
+
388
+ if (!juxComponent._id || typeof juxComponent._id !== 'string') {
389
+ throw new Error('List.renderTo: Invalid component - missing _id (not a Jux component)');
381
390
  }
382
-
383
- return this.render(`#${juxComponent._componentId}`);
391
+
392
+ return this.render(`#${juxComponent._id}`);
384
393
  }
385
394
  }
386
395
 
387
396
  /**
388
397
  * Factory helper
389
398
  */
390
- export function list(componentId: string, options: ListOptions = {}): List {
391
- return new List(componentId, options);
399
+ export function list(id: string, options: ListOptions = {}): List {
400
+ return new List(id, options);
392
401
  }