juxscript 1.0.19 → 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 (77) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +212 -165
  3. package/lib/components/badge.ts +93 -103
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +63 -122
  7. package/lib/components/card.ts +109 -155
  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/charts/lib/chart-types.ts +159 -0
  13. package/lib/components/charts/lib/chart-utils.ts +160 -0
  14. package/lib/components/charts/lib/chart.ts +707 -0
  15. package/lib/components/checkbox.ts +264 -127
  16. package/lib/components/code.ts +75 -108
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +195 -147
  20. package/lib/components/dialog.ts +187 -157
  21. package/lib/components/divider.ts +85 -191
  22. package/lib/components/docs-data.json +544 -2027
  23. package/lib/components/dropdown.ts +178 -136
  24. package/lib/components/element.ts +227 -171
  25. package/lib/components/fileupload.ts +285 -228
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +46 -69
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +107 -95
  30. package/lib/components/icon.ts +160 -0
  31. package/lib/components/icons.ts +175 -0
  32. package/lib/components/include.ts +153 -5
  33. package/lib/components/input.ts +174 -374
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +378 -240
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +103 -97
  38. package/lib/components/modal.ts +138 -144
  39. package/lib/components/nav.ts +169 -90
  40. package/lib/components/paragraph.ts +49 -150
  41. package/lib/components/progress.ts +118 -200
  42. package/lib/components/radio.ts +297 -149
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +184 -186
  45. package/lib/components/sidebar.ts +152 -140
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +258 -188
  48. package/lib/components/table.ts +1117 -170
  49. package/lib/components/tabs.ts +162 -145
  50. package/lib/components/theme-toggle.ts +108 -169
  51. package/lib/components/tooltip.ts +86 -157
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +86 -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 -2
  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 -1246
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1250
  69. package/lib/components/chart.ts +0 -127
  70. package/lib/components/doughnutchart.ts +0 -1191
  71. package/lib/components/footer.ts +0 -165
  72. package/lib/components/header.ts +0 -187
  73. package/lib/components/layout.ts +0 -239
  74. package/lib/components/main.ts +0 -137
  75. package/lib/layouts/default.jux +0 -8
  76. package/lib/layouts/figma.jux +0 -0
  77. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
@@ -1,8 +1,9 @@
1
- import { getOrCreateContainer } from './helpers.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+
3
+ // Event definitions
4
+ const TRIGGER_EVENTS = [] as const; // Element has no trigger events
5
+ const CALLBACK_EVENTS = [] as const; // Element has no callback events
2
6
 
3
- /**
4
- * Element component options
5
- */
6
7
  export interface ElementOptions {
7
8
  tagType?: string;
8
9
  className?: string;
@@ -14,9 +15,6 @@ export interface ElementOptions {
14
15
  class?: string;
15
16
  }
16
17
 
17
- /**
18
- * Element component state
19
- */
20
18
  type ElementState = {
21
19
  tagType: string;
22
20
  className: string;
@@ -28,35 +26,11 @@ type ElementState = {
28
26
  class: string;
29
27
  };
30
28
 
31
- /**
32
- * Element component - Create arbitrary HTML elements
33
- *
34
- * Usage:
35
- * const div = jux.element('myDiv', { tagType: 'div', className: 'container' });
36
- * div.render('#target');
37
- *
38
- * const span = jux.element('mySpan')
39
- * .tagType('span')
40
- * .className('highlight')
41
- * .text('Hello World')
42
- * .render();
43
- *
44
- * For reactive content, use State bindings after render:
45
- * const count = state(0);
46
- * jux.element('counter').render('#app');
47
- * count.bindText('counter', (val) => `Count: ${val}`);
48
- */
49
- export class Element {
50
- state: ElementState;
51
- container: HTMLElement | null = null;
52
- _id: string;
53
- id: string;
29
+ export class Element extends BaseComponent<ElementState> {
30
+ private _element: HTMLElement | null = null;
54
31
 
55
32
  constructor(id: string, options: ElementOptions = {}) {
56
- this._id = id;
57
- this.id = id;
58
-
59
- this.state = {
33
+ super(id, {
60
34
  tagType: options.tagType ?? 'div',
61
35
  className: options.className ?? '',
62
36
  text: options.text ?? '',
@@ -65,207 +39,289 @@ export class Element {
65
39
  styles: options.styles ?? {},
66
40
  style: options.style ?? '',
67
41
  class: options.class ?? ''
68
- };
42
+ });
69
43
  }
70
44
 
71
- /* -------------------------
72
- * Fluent API
73
- * ------------------------- */
45
+ protected getTriggerEvents(): readonly string[] {
46
+ return TRIGGER_EVENTS;
47
+ }
48
+
49
+ protected getCallbackEvents(): readonly string[] {
50
+ return CALLBACK_EVENTS;
51
+ }
52
+
53
+ /* ═════════════════════════════════════════════════════════════════
54
+ * FLUENT API
55
+ * ═════════════════════════════════════════════════════════════════ */
56
+
57
+ // ✅ Inherited from BaseComponent:
58
+ // - style(), class()
59
+ // - addClass(), removeClass(), toggleClass()
60
+ // - visible(), show(), hide(), toggleVisibility()
61
+ // - attr(), attrs(), removeAttr()
62
+ // - disabled(), enable(), disable()
63
+ // - loading()
64
+ // - focus(), blur()
65
+ // - remove()
66
+ // - bind(), sync(), renderTo()
74
67
 
75
- /**
76
- * Set the HTML tag type
77
- */
78
68
  tagType(value: string): Element {
79
69
  this.state.tagType = value;
80
70
  return this;
81
71
  }
82
72
 
83
- /**
84
- * Set the className
85
- */
86
73
  className(value: string): Element {
87
74
  this.state.className = value;
88
75
  return this;
89
76
  }
90
77
 
91
- /**
92
- * Add a class to existing classes
93
- */
94
- addClass(value: string): Element {
95
- const classes = this.state.className.split(' ').filter(c => c);
96
- if (!classes.includes(value)) {
97
- classes.push(value);
98
- this.state.className = classes.join(' ');
99
- }
100
- return this;
101
- }
102
-
103
- /**
104
- * Remove a class from existing classes
105
- */
106
- removeClass(value: string): Element {
107
- const classes = this.state.className.split(' ').filter(c => c && c !== value);
108
- this.state.className = classes.join(' ');
109
- return this;
110
- }
111
-
112
- /**
113
- * Set text content (static)
114
- */
115
78
  text(value: string): Element {
116
79
  this.state.text = value;
117
80
  this.state.innerHTML = '';
118
81
  return this;
119
82
  }
120
83
 
121
- /**
122
- * Set HTML content (static, clears text)
123
- */
124
84
  innerHTML(value: string): Element {
125
85
  this.state.innerHTML = value;
126
86
  this.state.text = '';
127
87
  return this;
128
88
  }
129
89
 
130
- /**
131
- * Set a single attribute
132
- */
133
- attr(name: string, value: string): Element {
134
- this.state.attributes = { ...this.state.attributes, [name]: value };
135
- return this;
136
- }
90
+ write(content: string, asHTML: boolean = false, newLine: boolean = false): Element {
91
+ if (!this._element) {
92
+ console.warn(`Element.write: Element "${this._id}" not yet rendered`);
93
+ return this;
94
+ }
137
95
 
138
- /**
139
- * Set multiple attributes
140
- */
141
- attrs(attributes: Record<string, string>): Element {
142
- this.state.attributes = { ...this.state.attributes, ...attributes };
143
- return this;
144
- }
96
+ const contentWithBreak = newLine ? (asHTML ? content + '<br>' : content + '\n') : content;
145
97
 
146
- /**
147
- * Set inline styles
148
- * @param propertyOrValue - CSS property name, style string, or style object
149
- * @param value - CSS value (when first arg is property name)
150
- */
151
- style(propertyOrValue: string | Record<string, string>, value?: string): Element {
152
- if (typeof propertyOrValue === 'string' && value !== undefined) {
153
- // Called as .style('color', 'red')
154
- this.state.styles = { ...this.state.styles, [propertyOrValue]: value };
155
- } else if (typeof propertyOrValue === 'string') {
156
- // Called as .style('color: red; background: blue')
157
- this.state.style = propertyOrValue;
158
- } else if (typeof propertyOrValue === 'object') {
159
- // Called as .style({ color: 'red', background: 'blue' })
160
- this.state.styles = { ...this.state.styles, ...propertyOrValue };
98
+ if (asHTML) {
99
+ this._element.insertAdjacentHTML('beforeend', contentWithBreak);
100
+ } else {
101
+ const textNode = document.createTextNode(contentWithBreak);
102
+ this._element.appendChild(textNode);
161
103
  }
104
+
162
105
  return this;
163
106
  }
164
107
 
165
- /**
166
- * Set multiple styles as object
167
- */
168
- styles(styles: Record<string, string>): Element {
169
- this.state.styles = { ...this.state.styles, ...styles };
108
+ prepend(content: string, asHTML: boolean = false, newLine: boolean = false): Element {
109
+ if (!this._element) {
110
+ console.warn(`Element.prepend: Element "${this._id}" not yet rendered`);
111
+ return this;
112
+ }
113
+
114
+ const contentWithBreak = newLine ? (asHTML ? content + '<br>' : content + '\n') : content;
115
+
116
+ if (asHTML) {
117
+ this._element.insertAdjacentHTML('afterbegin', contentWithBreak);
118
+ } else {
119
+ const textNode = document.createTextNode(contentWithBreak);
120
+ this._element.insertBefore(textNode, this._element.firstChild);
121
+ }
122
+
170
123
  return this;
171
124
  }
172
125
 
173
- /**
174
- * Add additional classes (fluent alternative to className)
175
- */
176
- class(value: string): Element {
177
- this.state.class = value;
126
+ clear(): Element {
127
+ if (!this._element) {
128
+ console.warn(`Element.clear: Element "${this._id}" not yet rendered`);
129
+ return this;
130
+ }
131
+
132
+ this._element.innerHTML = '';
178
133
  return this;
179
134
  }
180
135
 
181
- /* -------------------------
182
- * Render
183
- * ------------------------- */
184
-
185
136
  /**
186
- * Render the element to a target element
187
- * @param targetId - CSS selector for target element (optional, defaults to body)
137
+ * Append child component or HTML element
188
138
  */
189
- render(targetId?: string): Element {
190
- let container: HTMLElement;
139
+ append(child: any): Element {
140
+ if (!this._element) {
141
+ console.warn(`Element.append: Element "${this._id}" not yet rendered`);
142
+ return this;
143
+ }
191
144
 
192
- if (targetId) {
193
- const target = document.querySelector(targetId);
194
- if (!target || !(target instanceof HTMLElement)) {
195
- throw new Error(`Element: Target element "${targetId}" not found`);
196
- }
197
- container = target;
198
- } else {
199
- container = getOrCreateContainer(this._id);
145
+ if (child instanceof HTMLElement) {
146
+ this._element.appendChild(child);
147
+ } else if (child?._element) {
148
+ // Another Jux component
149
+ this._element.appendChild(child._element);
150
+ } else if (typeof child === 'string') {
151
+ const div = document.createElement('div');
152
+ div.innerHTML = child;
153
+ this._element.appendChild(div.firstChild || div);
200
154
  }
201
155
 
202
- this.container = container;
156
+ return this;
157
+ }
158
+
159
+ render(targetId?: string): this {
160
+ const container = this._setupContainer(targetId);
161
+
203
162
  const { tagType, className, text, innerHTML, attributes, styles, style, class: classValue } = this.state;
204
163
 
205
- // Create the element
164
+ // Build element
206
165
  const element = document.createElement(tagType);
207
-
208
- // Set ID to component ID
209
166
  element.id = this._id;
210
167
 
211
- // Set className
212
- if (className) {
213
- element.className = className;
214
- }
215
-
216
- if (classValue) {
217
- element.className = element.className ? `${element.className} ${classValue}` : classValue;
218
- }
168
+ if (className) element.className = className;
169
+ if (classValue) element.className = `${element.className} ${classValue}`.trim();
219
170
 
220
- // Set content (innerHTML takes precedence over text)
221
- if (innerHTML) {
222
- element.innerHTML = innerHTML;
223
- } else if (text) {
171
+ if (text) {
224
172
  element.textContent = text;
173
+ } else if (innerHTML) {
174
+ element.innerHTML = innerHTML;
225
175
  }
226
176
 
227
- // Set attributes
228
- Object.entries(attributes).forEach(([name, value]) => {
229
- element.setAttribute(name, value);
177
+ Object.entries(attributes).forEach(([key, value]) => {
178
+ element.setAttribute(key, value);
230
179
  });
231
180
 
232
- // Set styles (object)
233
- Object.entries(styles).forEach(([property, value]) => {
234
- element.style.setProperty(property, value);
235
- });
181
+ const styleString = Object.entries(styles)
182
+ .map(([key, value]) => `${key}: ${value}`)
183
+ .join('; ');
236
184
 
237
- // Set style (string)
238
- if (style) {
239
- element.setAttribute('style', style);
185
+ if (styleString || style) {
186
+ element.setAttribute('style', `${styleString}; ${style}`.trim());
240
187
  }
241
188
 
189
+ // Wire events using inherited method
190
+ this._wireStandardEvents(element);
191
+
192
+ // Wire sync bindings
193
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
194
+ const transform = toComponent || ((v: any) => v);
195
+
196
+ stateObj.subscribe((val: any) => {
197
+ const transformed = transform(val);
198
+
199
+ if (property === 'text') {
200
+ element.textContent = String(transformed);
201
+ this.state.text = String(transformed);
202
+ } else if (property === 'innerHTML') {
203
+ element.innerHTML = String(transformed);
204
+ this.state.innerHTML = String(transformed);
205
+ } else if (property === 'class') {
206
+ element.className = String(transformed);
207
+ this.state.class = String(transformed);
208
+ }
209
+ });
210
+ });
211
+
242
212
  container.appendChild(element);
213
+ this._element = element;
214
+
215
+ // Inject base semantic styles if this is a semantic element
216
+ this._injectSemanticStyles();
217
+
243
218
  return this;
244
219
  }
245
220
 
246
- /**
247
- * Render to another Jux component's container
248
- *
249
- * Usage:
250
- * const container = jux.element('myContainer');
251
- * const child = jux.element('myChild').renderTo(container);
252
- */
253
- renderTo(juxComponent: Element): Element {
254
- if (!juxComponent || typeof juxComponent !== 'object') {
255
- throw new Error('Element.renderTo: Invalid component - not an object');
256
- }
221
+ private _injectSemanticStyles(): void {
222
+ const styleId = 'jux-semantic-styles';
223
+ if (document.getElementById(styleId)) return;
224
+
225
+ const style = document.createElement('style');
226
+ style.id = styleId;
227
+ style.textContent = `
228
+ /* Jux Semantic Element Base Styles */
229
+ .jux-header {
230
+ display: block;
231
+ width: 100%;
232
+ background: var(--jux-header-bg, #ffffff);
233
+ border-bottom: 1px solid var(--jux-border-color, #e5e7eb);
234
+ padding: var(--jux-header-padding, 1rem 2rem);
235
+ position: sticky;
236
+ top: 0;
237
+ z-index: 1000;
238
+ }
257
239
 
258
- if (!juxComponent._id || typeof juxComponent._id !== 'string') {
259
- throw new Error('Element.renderTo: Invalid component - missing _id (not a Jux component)');
260
- }
240
+ .jux-footer {
241
+ display: block;
242
+ width: 100%;
243
+ background: var(--jux-footer-bg, #f9fafb);
244
+ border-top: 1px solid var(--jux-border-color, #e5e7eb);
245
+ padding: var(--jux-footer-padding, 2rem);
246
+ margin-top: auto;
247
+ }
261
248
 
262
- return this.render(`#${juxComponent._id}`);
249
+ .jux-main {
250
+ display: block;
251
+ flex: 1;
252
+ width: 100%;
253
+ padding: var(--jux-main-padding, 2rem);
254
+ min-height: calc(100vh - 200px);
255
+ }
256
+
257
+ .jux-aside {
258
+ display: block;
259
+ background: var(--jux-aside-bg, #f9fafb);
260
+ border-right: 1px solid var(--jux-border-color, #e5e7eb);
261
+ padding: var(--jux-aside-padding, 1.5rem);
262
+ }
263
+
264
+ .jux-section {
265
+ display: block;
266
+ margin-bottom: var(--jux-section-margin, 2rem);
267
+ }
268
+
269
+ .jux-article {
270
+ display: block;
271
+ max-width: var(--jux-article-max-width, 65ch);
272
+ margin: 0 auto;
273
+ }
274
+ `;
275
+ document.head.appendChild(style);
263
276
  }
264
277
  }
265
278
 
266
- /**
267
- * Factory helper
268
- */
269
279
  export function element(id: string, options: ElementOptions = {}): Element {
270
280
  return new Element(id, options);
271
- }
281
+ }
282
+
283
+ // Semantic HTML element factories with default classes
284
+ export function header(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
285
+ const defaultClass = 'jux-header';
286
+ const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
287
+ return new Element(id, { ...options, tagType: 'header', class: mergedClass });
288
+ }
289
+
290
+ export function footer(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
291
+ const defaultClass = 'jux-footer';
292
+ const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
293
+ return new Element(id, { ...options, tagType: 'footer', class: mergedClass });
294
+ }
295
+
296
+ export function main(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
297
+ const defaultClass = 'jux-main';
298
+ const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
299
+ return new Element(id, { ...options, tagType: 'main', class: mergedClass });
300
+ }
301
+
302
+ export function aside(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
303
+ const defaultClass = 'jux-aside';
304
+ const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
305
+ return new Element(id, { ...options, tagType: 'aside', class: mergedClass });
306
+ }
307
+
308
+ export function section(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
309
+ const defaultClass = 'jux-section';
310
+ const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
311
+ return new Element(id, { ...options, tagType: 'section', class: mergedClass });
312
+ }
313
+
314
+ export function article(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
315
+ const defaultClass = 'jux-article';
316
+ const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
317
+ return new Element(id, { ...options, tagType: 'article', class: mergedClass });
318
+ }
319
+
320
+ export function div(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
321
+ return new Element(id, { ...options, tagType: 'div' });
322
+ }
323
+
324
+ export function span(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
325
+ return new Element(id, { ...options, tagType: 'span' });
326
+ }
327
+