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,5 +1,8 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.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
3
6
 
4
7
  export interface ElementOptions {
5
8
  tagType?: string;
@@ -23,20 +26,11 @@ type ElementState = {
23
26
  class: string;
24
27
  };
25
28
 
26
- export class Element {
27
- state: ElementState;
28
- container: HTMLElement | null = null;
29
- _id: string;
30
- id: string;
31
-
32
- private _bindings: Array<{ event: string, handler: Function }> = [];
33
- private _syncBindings: Array<{ property: string, stateObj: State<any>, transform?: Function }> = [];
29
+ export class Element extends BaseComponent<ElementState> {
30
+ private _element: HTMLElement | null = null;
34
31
 
35
32
  constructor(id: string, options: ElementOptions = {}) {
36
- this._id = id;
37
- this.id = id;
38
-
39
- this.state = {
33
+ super(id, {
40
34
  tagType: options.tagType ?? 'div',
41
35
  className: options.className ?? '',
42
36
  text: options.text ?? '',
@@ -45,164 +39,129 @@ export class Element {
45
39
  styles: options.styles ?? {},
46
40
  style: options.style ?? '',
47
41
  class: options.class ?? ''
48
- };
42
+ });
49
43
  }
50
44
 
51
- /* -------------------------
52
- * Fluent API
53
- * ------------------------- */
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()
54
67
 
55
- /**
56
- * Set the HTML tag type
57
- */
58
68
  tagType(value: string): Element {
59
69
  this.state.tagType = value;
60
70
  return this;
61
71
  }
62
72
 
63
- /**
64
- * Set the className
65
- */
66
73
  className(value: string): Element {
67
74
  this.state.className = value;
68
75
  return this;
69
76
  }
70
77
 
71
- /**
72
- * Add a class to existing classes
73
- */
74
- addClass(value: string): Element {
75
- const classes = this.state.className.split(' ').filter(c => c);
76
- if (!classes.includes(value)) {
77
- classes.push(value);
78
- this.state.className = classes.join(' ');
79
- }
80
- return this;
81
- }
82
-
83
- /**
84
- * Remove a class from existing classes
85
- */
86
- removeClass(value: string): Element {
87
- const classes = this.state.className.split(' ').filter(c => c && c !== value);
88
- this.state.className = classes.join(' ');
89
- return this;
90
- }
91
-
92
- /**
93
- * Set text content (static)
94
- */
95
78
  text(value: string): Element {
96
79
  this.state.text = value;
97
80
  this.state.innerHTML = '';
98
81
  return this;
99
82
  }
100
83
 
101
- /**
102
- * Set HTML content (static, clears text)
103
- */
104
84
  innerHTML(value: string): Element {
105
85
  this.state.innerHTML = value;
106
86
  this.state.text = '';
107
87
  return this;
108
88
  }
109
89
 
110
- /**
111
- * Set a single attribute
112
- */
113
- attr(name: string, value: string): Element {
114
- this.state.attributes = { ...this.state.attributes, [name]: value };
115
- return this;
116
- }
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
+ }
117
95
 
118
- /**
119
- * Set multiple attributes
120
- */
121
- attrs(attributes: Record<string, string>): Element {
122
- this.state.attributes = { ...this.state.attributes, ...attributes };
123
- return this;
124
- }
96
+ const contentWithBreak = newLine ? (asHTML ? content + '<br>' : content + '\n') : content;
125
97
 
126
- /**
127
- * Set inline styles
128
- * @param propertyOrValue - CSS property name, style string, or style object
129
- * @param value - CSS value (when first arg is property name)
130
- */
131
- style(propertyOrValue: string | Record<string, string>, value?: string): Element {
132
- if (typeof propertyOrValue === 'string' && value !== undefined) {
133
- // Called as .style('color', 'red')
134
- this.state.styles = { ...this.state.styles, [propertyOrValue]: value };
135
- } else if (typeof propertyOrValue === 'string') {
136
- // Called as .style('color: red; background: blue')
137
- this.state.style = propertyOrValue;
138
- } else if (typeof propertyOrValue === 'object') {
139
- // Called as .style({ color: 'red', background: 'blue' })
140
- 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);
141
103
  }
142
- return this;
143
- }
144
104
 
145
- /**
146
- * Set multiple styles as object
147
- */
148
- styles(styles: Record<string, string>): Element {
149
- this.state.styles = { ...this.state.styles, ...styles };
150
105
  return this;
151
106
  }
152
107
 
153
- /**
154
- * Add additional classes (fluent alternative to className)
155
- */
156
- class(value: string): Element {
157
- this.state.class = value;
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
+
158
123
  return this;
159
124
  }
160
125
 
161
- /**
162
- * Bind an event to a handler
163
- */
164
- bind(event: string, handler: Function): Element {
165
- this._bindings.push({ event, handler });
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 = '';
166
133
  return this;
167
134
  }
168
135
 
169
136
  /**
170
- * Sync a property to a state object
137
+ * Append child component or HTML element
171
138
  */
172
- sync(property: string, stateObj: State<any>, transform?: Function): Element {
173
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
174
- throw new Error(`Element.sync: Expected a State object for property "${property}"`);
139
+ append(child: any): Element {
140
+ if (!this._element) {
141
+ console.warn(`Element.append: Element "${this._id}" not yet rendered`);
142
+ return this;
175
143
  }
176
- this._syncBindings.push({ property, stateObj, transform });
144
+
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);
154
+ }
155
+
177
156
  return this;
178
157
  }
179
158
 
180
- /* -------------------------
181
- * Render
182
- * ------------------------- */
159
+ render(targetId?: string): this {
160
+ const container = this._setupContainer(targetId);
183
161
 
184
- /**
185
- * Render the element to a target element
186
- * @param targetId - CSS selector for target element (optional, defaults to body)
187
- */
188
- render(targetId?: string): Element {
189
- // === 1. SETUP: Get container ===
190
- let container: HTMLElement;
191
- if (targetId) {
192
- const target = document.querySelector(targetId);
193
- if (!target || !(target instanceof HTMLElement)) {
194
- throw new Error(`Element: Target element "${targetId}" not found`);
195
- }
196
- container = target;
197
- } else {
198
- container = getOrCreateContainer(this._id);
199
- }
200
- this.container = container;
201
-
202
- // === 2. PREPARE: Destructure state ===
203
162
  const { tagType, className, text, innerHTML, attributes, styles, style, class: classValue } = this.state;
204
163
 
205
- // === 3. BUILD: Create DOM element ===
164
+ // Build element
206
165
  const element = document.createElement(tagType);
207
166
  element.id = this._id;
208
167
 
@@ -227,15 +186,15 @@ export class Element {
227
186
  element.setAttribute('style', `${styleString}; ${style}`.trim());
228
187
  }
229
188
 
230
- // === 4. WIRE: Add event listeners ===
189
+ // Wire events using inherited method
190
+ this._wireStandardEvents(element);
231
191
 
232
- this._bindings.forEach(({ event, handler }) => {
233
- element.addEventListener(event, handler as EventListener);
234
- });
192
+ // Wire sync bindings
193
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
194
+ const transform = toComponent || ((v: any) => v);
235
195
 
236
- this._syncBindings.forEach(({ property, stateObj, transform }) => {
237
196
  stateObj.subscribe((val: any) => {
238
- const transformed = transform ? transform(val) : val;
197
+ const transformed = transform(val);
239
198
 
240
199
  if (property === 'text') {
241
200
  element.textContent = String(transformed);
@@ -250,27 +209,119 @@ export class Element {
250
209
  });
251
210
  });
252
211
 
253
- // === 5. RENDER: Append to DOM ===
254
212
  container.appendChild(element);
213
+ this._element = element;
214
+
215
+ // Inject base semantic styles if this is a semantic element
216
+ this._injectSemanticStyles();
255
217
 
256
218
  return this;
257
219
  }
258
220
 
259
- /**
260
- * Render to another Jux component's container
261
- *
262
- * Usage:
263
- * const container = jux.element('myContainer');
264
- * const child = jux.element('myChild').renderTo(container);
265
- */
266
- renderTo(juxComponent: Element): Element {
267
- if (!juxComponent || !juxComponent._id) {
268
- throw new Error('Element.renderTo: Invalid component');
269
- }
270
- return this.render(`#${juxComponent._id}`);
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
+ }
239
+
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
+ }
248
+
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);
271
276
  }
272
277
  }
273
278
 
274
279
  export function element(id: string, options: ElementOptions = {}): Element {
275
280
  return new Element(id, options);
276
- }
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
+