juxscript 1.0.20 → 1.0.21

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 (76) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +143 -92
  3. package/lib/components/badge.ts +93 -94
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +40 -131
  7. package/lib/components/card.ts +57 -79
  8. package/lib/components/charts/areachart.ts +315 -0
  9. package/lib/components/charts/barchart.ts +421 -0
  10. package/lib/components/charts/doughnutchart.ts +263 -0
  11. package/lib/components/charts/lib/BaseChart.ts +402 -0
  12. package/lib/components/{chart-types.ts → charts/lib/chart-types.ts} +1 -1
  13. package/lib/components/{chart-utils.ts → charts/lib/chart-utils.ts} +1 -1
  14. package/lib/components/{chart.ts → charts/lib/chart.ts} +3 -3
  15. package/lib/components/checkbox.ts +255 -204
  16. package/lib/components/code.ts +31 -78
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +180 -147
  20. package/lib/components/dialog.ts +218 -221
  21. package/lib/components/divider.ts +63 -87
  22. package/lib/components/docs-data.json +498 -2404
  23. package/lib/components/dropdown.ts +191 -236
  24. package/lib/components/element.ts +196 -145
  25. package/lib/components/fileupload.ts +253 -167
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +31 -97
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +51 -114
  30. package/lib/components/icon.ts +33 -120
  31. package/lib/components/icons.ts +2 -1
  32. package/lib/components/include.ts +76 -3
  33. package/lib/components/input.ts +155 -407
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +358 -261
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +63 -152
  38. package/lib/components/modal.ts +42 -129
  39. package/lib/components/nav.ts +79 -101
  40. package/lib/components/paragraph.ts +38 -102
  41. package/lib/components/progress.ts +108 -166
  42. package/lib/components/radio.ts +283 -234
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +189 -199
  45. package/lib/components/sidebar.ts +110 -141
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +254 -183
  48. package/lib/components/table.ts +1078 -208
  49. package/lib/components/tabs.ts +42 -106
  50. package/lib/components/theme-toggle.ts +73 -165
  51. package/lib/components/tooltip.ts +85 -316
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +67 -41
  54. package/machinery/build.js +466 -0
  55. package/machinery/compiler.js +354 -105
  56. package/machinery/server.js +23 -100
  57. package/machinery/watcher.js +153 -130
  58. package/package.json +1 -1
  59. package/presets/base.css +1166 -0
  60. package/presets/notion.css +2 -1975
  61. package/lib/adapters/base-adapter.js +0 -35
  62. package/lib/adapters/index.js +0 -33
  63. package/lib/adapters/mysql-adapter.js +0 -65
  64. package/lib/adapters/postgres-adapter.js +0 -70
  65. package/lib/adapters/sqlite-adapter.js +0 -56
  66. package/lib/components/areachart.ts +0 -1128
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1322
  69. package/lib/components/doughnutchart.ts +0 -1259
  70. package/lib/components/footer.ts +0 -165
  71. package/lib/components/header.ts +0 -187
  72. package/lib/components/layout.ts +0 -239
  73. package/lib/components/main.ts +0 -137
  74. package/lib/layouts/default.jux +0 -8
  75. package/lib/layouts/figma.jux +0 -0
  76. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
@@ -1,10 +1,10 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
3
2
  import { renderIcon, renderEmoji } from './icons.js';
4
3
 
5
- /**
6
- * Icon component options
7
- */
4
+ // Event definitions
5
+ const TRIGGER_EVENTS = [] as const;
6
+ const CALLBACK_EVENTS = [] as const;
7
+
8
8
  export interface IconOptions {
9
9
  value: string;
10
10
  size?: string;
@@ -14,9 +14,6 @@ export interface IconOptions {
14
14
  class?: string;
15
15
  }
16
16
 
17
- /**
18
- * Icon component state
19
- */
20
17
  type IconState = {
21
18
  value: string;
22
19
  size: string;
@@ -26,54 +23,31 @@ type IconState = {
26
23
  class: string;
27
24
  };
28
25
 
29
- /**
30
- * Icon component - Render an icon from emoji, icon name, or image path
31
- *
32
- * Usage:
33
- * // Render Lucide icon from emoji
34
- * jux.icon('myIcon', { value: '🚀' }).render('#container');
35
- *
36
- * // Render Lucide icon from name
37
- * jux.icon('myIcon', { value: 'rocket', size: '32px', color: 'red' }).render('#container');
38
- *
39
- * // Render image icon
40
- * jux.icon('myIcon', { value: '/path/icon.png' }).render('#container');
41
- *
42
- * // Force emoji rendering (skip conversion)
43
- * jux.icon('myIcon', { value: '🚀', useEmoji: true }).render('#container');
44
- */
45
- export class Icon {
46
- state: IconState;
47
- container: HTMLElement | null = null;
48
- _id: string;
49
- id: string;
50
-
51
- // CRITICAL: Store bind/sync instructions for deferred wiring
52
- private _bindings: Array<{ event: string, handler: Function }> = [];
53
- private _syncBindings: Array<{
54
- property: string,
55
- stateObj: State<any>,
56
- toState?: Function,
57
- toComponent?: Function
58
- }> = [];
59
-
26
+ export class Icon extends BaseComponent<IconState> {
60
27
  constructor(id: string, options: IconOptions) {
61
- this._id = id;
62
- this.id = id;
63
-
64
- this.state = {
28
+ super(id, {
65
29
  value: options.value,
66
30
  size: options.size ?? '24px',
67
31
  color: options.color ?? '',
68
32
  useEmoji: options.useEmoji ?? false,
69
33
  style: options.style ?? '',
70
34
  class: options.class ?? ''
71
- };
35
+ });
36
+ }
37
+
38
+ protected getTriggerEvents(): readonly string[] {
39
+ return TRIGGER_EVENTS;
40
+ }
41
+
42
+ protected getCallbackEvents(): readonly string[] {
43
+ return CALLBACK_EVENTS;
72
44
  }
73
45
 
74
- /* -------------------------
75
- * Fluent API
76
- * ------------------------- */
46
+ /* ═════════════════════════════════════════════════════════════════
47
+ * FLUENT API
48
+ * ═════════════════════════════════════════════════════════════════ */
49
+
50
+ // ✅ Inherited from BaseComponent
77
51
 
78
52
  value(value: string): this {
79
53
  this.state.value = value;
@@ -95,51 +69,15 @@ export class Icon {
95
69
  return this;
96
70
  }
97
71
 
98
- style(value: string): this {
99
- this.state.style = value;
100
- return this;
101
- }
102
-
103
- class(value: string): this {
104
- this.state.class = value;
105
- return this;
106
- }
107
-
108
- bind(event: string, handler: Function): this {
109
- this._bindings.push({ event, handler });
110
- return this;
111
- }
112
-
113
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
114
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
115
- throw new Error(`Icon.sync: Expected a State object for property "${property}"`);
116
- }
117
- this._syncBindings.push({ property, stateObj, toState, toComponent });
118
- return this;
119
- }
120
-
121
- /* -------------------------
122
- * Render (5-Step Pattern)
123
- * ------------------------- */
72
+ /* ═════════════════════════════════════════════════════════════════
73
+ * RENDER
74
+ * ═════════════════════════════════════════════════════════════════ */
124
75
 
125
76
  render(targetId?: string): this {
126
- // === 1. SETUP: Get or create container ===
127
- let container: HTMLElement;
128
- if (targetId) {
129
- const target = document.querySelector(targetId);
130
- if (!target || !(target instanceof HTMLElement)) {
131
- throw new Error(`Icon: Target element "${targetId}" not found`);
132
- }
133
- container = target;
134
- } else {
135
- container = getOrCreateContainer(this._id);
136
- }
137
- this.container = container;
77
+ const container = this._setupContainer(targetId);
138
78
 
139
- // === 2. PREPARE: Destructure state ===
140
79
  const { value, size, color, useEmoji, style, class: className } = this.state;
141
80
 
142
- // === 3. BUILD: Create DOM elements ===
143
81
  const wrapper = document.createElement('span');
144
82
  wrapper.className = 'jux-icon';
145
83
  wrapper.id = this._id;
@@ -153,23 +91,17 @@ export class Icon {
153
91
 
154
92
  wrapper.appendChild(iconElement);
155
93
 
156
- // === 4. WIRE: Attach event listeners and sync bindings ===
157
-
158
- // Wire custom bindings from .bind() calls
159
- this._bindings.forEach(({ event, handler }) => {
160
- wrapper.addEventListener(event, handler as EventListener);
161
- });
94
+ this._wireStandardEvents(wrapper);
162
95
 
163
- // Wire sync bindings from .sync() calls
96
+ // Wire sync bindings
164
97
  this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
165
98
  if (property === 'value') {
166
- const transformToComponent = toComponent || ((v: any) => String(v));
99
+ const transform = toComponent || ((v: any) => String(v));
167
100
 
168
101
  stateObj.subscribe((val: any) => {
169
- const transformed = transformToComponent(val);
102
+ const transformed = transform(val);
170
103
  this.state.value = transformed;
171
104
 
172
- // Re-render icon
173
105
  wrapper.innerHTML = '';
174
106
  const newIcon = this.state.useEmoji ? renderEmoji(transformed) : renderIcon(transformed);
175
107
  newIcon.style.width = this.state.size;
@@ -185,10 +117,10 @@ export class Icon {
185
117
  });
186
118
  }
187
119
  else if (property === 'size') {
188
- const transformToComponent = toComponent || ((v: any) => String(v));
120
+ const transform = toComponent || ((v: any) => String(v));
189
121
 
190
122
  stateObj.subscribe((val: any) => {
191
- const transformed = transformToComponent(val);
123
+ const transformed = transform(val);
192
124
  const icon = wrapper.querySelector('img, svg, span');
193
125
  if (icon instanceof HTMLElement) {
194
126
  icon.style.width = transformed;
@@ -198,10 +130,10 @@ export class Icon {
198
130
  });
199
131
  }
200
132
  else if (property === 'color') {
201
- const transformToComponent = toComponent || ((v: any) => String(v));
133
+ const transform = toComponent || ((v: any) => String(v));
202
134
 
203
135
  stateObj.subscribe((val: any) => {
204
- const transformed = transformToComponent(val);
136
+ const transformed = transform(val);
205
137
  const icon = wrapper.querySelector('img, svg, span');
206
138
  if (icon instanceof HTMLElement) {
207
139
  icon.style.color = transformed;
@@ -211,7 +143,6 @@ export class Icon {
211
143
  }
212
144
  });
213
145
 
214
- // === 5. RENDER: Append to DOM and finalize ===
215
146
  container.appendChild(wrapper);
216
147
 
217
148
  requestAnimationFrame(() => {
@@ -222,26 +153,8 @@ export class Icon {
222
153
 
223
154
  return this;
224
155
  }
225
-
226
- /**
227
- * Render to another Jux component's container
228
- */
229
- renderTo(juxComponent: any): this {
230
- if (!juxComponent || typeof juxComponent !== 'object') {
231
- throw new Error('Icon.renderTo: Invalid component - not an object');
232
- }
233
-
234
- if (!juxComponent._id || typeof juxComponent._id !== 'string') {
235
- throw new Error('Icon.renderTo: Invalid component - missing _id (not a Jux component)');
236
- }
237
-
238
- return this.render(`#${juxComponent._id}`);
239
- }
240
156
  }
241
157
 
242
- /**
243
- * Factory helper
244
- */
245
158
  export function icon(id: string, options: IconOptions): Icon {
246
159
  return new Icon(id, options);
247
160
  }
@@ -66,7 +66,8 @@ const EMOJI_TO_LUCIDE: Record<string, string> = {
66
66
  "🧾": "receipt",
67
67
  "➕": "plus",
68
68
  "➖": "minus",
69
- "💾": "save"
69
+ "💾": "save",
70
+ "🩺": "stethoscope"
70
71
  };
71
72
 
72
73
  const LUCIDE_CDN_URL = "https://unpkg.com/lucide@latest";
@@ -22,6 +22,7 @@ export class Include {
22
22
  private type: IncludeType;
23
23
  private options: IncludeOptions = {};
24
24
  private element: HTMLElement | null = null;
25
+ private explicitType: boolean = false; // NEW: Track if type was explicitly set
25
26
 
26
27
  constructor(urlOrFile: string) {
27
28
  this.url = urlOrFile;
@@ -33,11 +34,25 @@ export class Include {
33
34
  * ------------------------- */
34
35
 
35
36
  private detectType(url: string): IncludeType {
37
+ // Check for common script patterns in URLs (CDN, etc.)
38
+ if (url.includes('tailwindcss') ||
39
+ url.includes('jsdelivr.net/npm') ||
40
+ url.includes('unpkg.com') ||
41
+ url.includes('cdn.') ||
42
+ url.match(/\.(js|mjs)($|\?)/)) {
43
+ return 'script';
44
+ }
45
+
36
46
  if (url.endsWith('.css')) return 'stylesheet';
37
- if (url.endsWith('.js') || url.endsWith('.mjs')) return 'script';
38
47
  if (url.endsWith('.json')) return 'json';
39
48
  if (url.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i)) return 'image';
40
49
  if (url.match(/\.(woff|woff2|ttf|otf|eot)$/i)) return 'font';
50
+
51
+ // Default to script for extensionless URLs from CDN domains
52
+ if (!url.includes('.') || url.match(/^https?:\/\/cdn/)) {
53
+ return 'script';
54
+ }
55
+
41
56
  return 'preload';
42
57
  }
43
58
 
@@ -45,49 +60,93 @@ export class Include {
45
60
  * Fluent Type Setters
46
61
  * ------------------------- */
47
62
 
63
+ /**
64
+ * Force treat as CSS stylesheet
65
+ * Use when URL doesn't have .css extension
66
+ */
48
67
  withCss(): this {
49
68
  this.type = 'stylesheet';
69
+ this.explicitType = true; // Mark as explicit
50
70
  return this;
51
71
  }
52
72
 
73
+ /**
74
+ * Force treat as JavaScript
75
+ * Use when URL doesn't have .js extension (CDN scripts, etc.)
76
+ *
77
+ * @example
78
+ * jux.include('https://cdn.tailwindcss.com').withJs().render();
79
+ * jux.include('https://unpkg.com/alpine').withJs({ defer: true }).render();
80
+ */
53
81
  withJs(options?: { async?: boolean; defer?: boolean }): this {
54
82
  this.type = 'script';
83
+ this.explicitType = true; // Mark as explicit
55
84
  if (options?.async) this.options.async = true;
56
85
  if (options?.defer) this.options.defer = true;
57
86
  return this;
58
87
  }
59
88
 
89
+ /**
90
+ * Force treat as ES module
91
+ * Use for module scripts
92
+ */
60
93
  withModule(): this {
61
94
  this.type = 'module';
95
+ this.explicitType = true; // Mark as explicit
62
96
  return this;
63
97
  }
64
98
 
65
99
  withImage(): this {
66
100
  this.type = 'image';
101
+ this.explicitType = true;
67
102
  return this;
68
103
  }
69
104
 
70
105
  withFont(): this {
71
106
  this.type = 'font';
107
+ this.explicitType = true;
72
108
  return this;
73
109
  }
74
110
 
75
111
  withPreload(as?: string): this {
76
112
  this.type = 'preload';
113
+ this.explicitType = true;
77
114
  if (as) this.options.as = as;
78
115
  return this;
79
116
  }
80
117
 
81
118
  withPrefetch(): this {
82
119
  this.type = 'prefetch';
120
+ this.explicitType = true;
83
121
  return this;
84
122
  }
85
123
 
86
124
  withJson(): this {
87
125
  this.type = 'json';
126
+ this.explicitType = true;
88
127
  return this;
89
128
  }
90
129
 
130
+ /* -------------------------
131
+ * Convenience aliases for common patterns
132
+ * ------------------------- */
133
+
134
+ /**
135
+ * Shorthand for .withJs()
136
+ * @example jux.include(url).asScript()
137
+ */
138
+ asScript(options?: { async?: boolean; defer?: boolean }): this {
139
+ return this.withJs(options);
140
+ }
141
+
142
+ /**
143
+ * Shorthand for .withCss()
144
+ * @example jux.include(url).asStylesheet()
145
+ */
146
+ asStylesheet(): this {
147
+ return this.withCss();
148
+ }
149
+
91
150
  /* -------------------------
92
151
  * JSON Fetching
93
152
  * ------------------------- */
@@ -227,6 +286,10 @@ export class Include {
227
286
  }
228
287
 
229
288
  this.element = element;
289
+
290
+ // Log with type indicator
291
+ const typeIndicator = this.explicitType ? '(explicit)' : '(auto-detected)';
292
+ console.log(`✓ Include loaded as ${this.type} ${typeIndicator}: ${this.url}`);
230
293
  } catch (error: any) {
231
294
  ErrorHandler.captureError({
232
295
  component: 'Include',
@@ -238,6 +301,7 @@ export class Include {
238
301
  type: this.type,
239
302
  url: this.url,
240
303
  location: this.options.location,
304
+ explicitType: this.explicitType,
241
305
  error: 'runtime_exception'
242
306
  }
243
307
  });
@@ -345,11 +409,20 @@ export class Include {
345
409
  * Factory function - auto-detects type and renders immediately
346
410
  *
347
411
  * Usage:
412
+ * // Auto-detect (works for most cases)
348
413
  * jux.include('styles.css');
349
414
  * jux.include('script.js').async();
415
+ *
416
+ * // Explicit type (for extensionless URLs)
417
+ * jux.include('https://cdn.tailwindcss.com').withJs();
418
+ * jux.include('https://cdn.tailwindcss.com').asScript();
419
+ * jux.include('https://unpkg.com/htmx.org').withJs({ defer: true });
420
+ *
421
+ * // CDN with parameters
422
+ * jux.include('https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4').withJs();
423
+ *
424
+ * // Module
350
425
  * jux.include('app.mjs').withModule();
351
- * jux.include('custom.js').withJs({ async: true, defer: true });
352
- * jux.include('https://cdn.com/lib.js').inHead().defer();
353
426
  *
354
427
  * // For JSON:
355
428
  * const data = await jux.include('config.json').asJson();