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,12 +1,16 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
3
2
  import { renderIcon } from './icons.js';
4
3
  import { req } from './req.js';
5
4
 
5
+ // Event definitions
6
+ const TRIGGER_EVENTS = [] as const;
7
+ const CALLBACK_EVENTS = [] as const;
8
+
6
9
  export interface NavItem {
7
10
  label: string;
8
11
  href: string;
9
12
  active?: boolean;
13
+ itemClass?: string;
10
14
  }
11
15
 
12
16
  export interface NavBrand {
@@ -33,41 +37,30 @@ type NavState = {
33
37
  class: string;
34
38
  };
35
39
 
36
- export class Nav {
37
- state: NavState;
38
- container: HTMLElement | null = null;
39
- _id: string;
40
- id: string;
41
-
42
- // CRITICAL: Store bind/sync instructions for deferred wiring
43
- private _bindings: Array<{ event: string, handler: Function }> = [];
44
- private _syncBindings: Array<{
45
- property: string,
46
- stateObj: State<any>,
47
- toState?: Function,
48
- toComponent?: Function
49
- }> = [];
40
+ export class Nav extends BaseComponent<NavState> {
41
+ private _nav: HTMLElement | null = null;
50
42
 
51
43
  constructor(id: string, options: NavOptions = {}) {
52
- this._id = id;
53
- this.id = id;
54
-
55
- this.state = {
44
+ super(id, {
56
45
  items: options.items ?? [],
57
46
  brand: options.brand,
58
47
  variant: options.variant ?? 'default',
59
48
  sticky: options.sticky ?? false,
60
49
  style: options.style ?? '',
61
50
  class: options.class ?? ''
62
- };
51
+ });
63
52
 
64
- // Auto-set active state based on current path
65
53
  this._setActiveStates();
66
54
  }
67
55
 
68
- /**
69
- * Set active state on items based on current request path
70
- */
56
+ protected getTriggerEvents(): readonly string[] {
57
+ return TRIGGER_EVENTS;
58
+ }
59
+
60
+ protected getCallbackEvents(): readonly string[] {
61
+ return CALLBACK_EVENTS;
62
+ }
63
+
71
64
  private _setActiveStates(): void {
72
65
  this.state.items = this.state.items.map(item => ({
73
66
  ...item,
@@ -75,9 +68,11 @@ export class Nav {
75
68
  }));
76
69
  }
77
70
 
78
- /* -------------------------
79
- * Fluent API
80
- * ------------------------- */
71
+ /* ═════════════════════════════════════════════════════════════════
72
+ * FLUENT API
73
+ * ═════════════════════════════════════════════════════════════════ */
74
+
75
+ // ✅ Inherited from BaseComponent
81
76
 
82
77
  items(value: NavItem[]): this {
83
78
  this.state.items = value;
@@ -91,6 +86,14 @@ export class Nav {
91
86
  return this;
92
87
  }
93
88
 
89
+ itemClass(className: string): this {
90
+ this.state.items = this.state.items.map(item => ({
91
+ ...item,
92
+ itemClass: className
93
+ }));
94
+ return this;
95
+ }
96
+
94
97
  brand(value: NavBrand): this {
95
98
  this.state.brand = value;
96
99
  return this;
@@ -106,51 +109,15 @@ export class Nav {
106
109
  return this;
107
110
  }
108
111
 
109
- style(value: string): this {
110
- this.state.style = value;
111
- return this;
112
- }
113
-
114
- class(value: string): this {
115
- this.state.class = value;
116
- return this;
117
- }
118
-
119
- bind(event: string, handler: Function): this {
120
- this._bindings.push({ event, handler });
121
- return this;
122
- }
123
-
124
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
125
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
126
- throw new Error(`Nav.sync: Expected a State object for property "${property}"`);
127
- }
128
- this._syncBindings.push({ property, stateObj, toState, toComponent });
129
- return this;
130
- }
131
-
132
- /* -------------------------
133
- * Render (5-Step Pattern)
134
- * ------------------------- */
112
+ /* ═════════════════════════════════════════════════════════════════
113
+ * RENDER
114
+ * ═════════════════════════════════════════════════════════════════ */
135
115
 
136
116
  render(targetId?: string): this {
137
- // === 1. SETUP: Get or create container ===
138
- let container: HTMLElement;
139
- if (targetId) {
140
- const target = document.querySelector(targetId);
141
- if (!target || !(target instanceof HTMLElement)) {
142
- throw new Error(`Nav: Target "${targetId}" not found`);
143
- }
144
- container = target;
145
- } else {
146
- container = getOrCreateContainer(this._id);
147
- }
148
- this.container = container;
117
+ const container = this._setupContainer(targetId);
149
118
 
150
- // === 2. PREPARE: Destructure state ===
151
119
  const { brand, items, variant, sticky, style, class: className } = this.state;
152
120
 
153
- // === 3. BUILD: Create DOM elements ===
154
121
  const nav = document.createElement('nav');
155
122
  nav.className = `jux-nav jux-nav-${variant}`;
156
123
  nav.id = this._id;
@@ -193,55 +160,73 @@ export class Nav {
193
160
  nav.appendChild(brandEl);
194
161
  }
195
162
 
196
- // Nav items
163
+ // Nav items container
197
164
  const itemsContainer = document.createElement('div');
198
165
  itemsContainer.className = 'jux-nav-items';
199
166
 
200
167
  items.forEach(item => {
201
- const navItem = document.createElement('a');
202
- navItem.className = 'jux-nav-item';
203
- navItem.href = item.href;
204
- navItem.textContent = item.label;
205
- if (item.active) navItem.classList.add('jux-nav-item-active');
206
- itemsContainer.appendChild(navItem);
207
- });
168
+ const itemWrapper = document.createElement('div');
169
+ itemWrapper.className = 'jux-nav-item-wrapper';
208
170
 
209
- nav.appendChild(itemsContainer);
171
+ if (item.itemClass) {
172
+ itemWrapper.className += ` ${item.itemClass}`;
173
+ }
210
174
 
211
- // === 4. WIRE: Attach event listeners and sync bindings ===
175
+ const navLink = document.createElement('a');
176
+ navLink.className = 'jux-nav-link';
177
+ navLink.href = item.href;
178
+ navLink.textContent = item.label;
179
+ if (item.active) {
180
+ itemWrapper.classList.add('jux-nav-item-active');
181
+ navLink.classList.add('jux-nav-link-active');
182
+ }
212
183
 
213
- // Wire custom bindings from .bind() calls
214
- this._bindings.forEach(({ event, handler }) => {
215
- nav.addEventListener(event, handler as EventListener);
184
+ itemWrapper.appendChild(navLink);
185
+ itemsContainer.appendChild(itemWrapper);
216
186
  });
217
187
 
218
- // Wire sync bindings from .sync() calls
188
+ nav.appendChild(itemsContainer);
189
+
190
+ this._wireStandardEvents(nav);
191
+
192
+ // Wire sync bindings
219
193
  this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
220
194
  if (property === 'items') {
221
- const transformToComponent = toComponent || ((v: any) => v);
195
+ const transform = toComponent || ((v: any) => v);
222
196
 
223
197
  stateObj.subscribe((val: any) => {
224
- const transformed = transformToComponent(val);
198
+ const transformed = transform(val);
225
199
  this.state.items = transformed;
226
200
  this._setActiveStates();
227
201
 
228
- // Re-render items
229
202
  itemsContainer.innerHTML = '';
230
203
  this.state.items.forEach((item: any) => {
231
- const navItem = document.createElement('a');
232
- navItem.className = 'jux-nav-item';
233
- navItem.href = item.href;
234
- navItem.textContent = item.label;
235
- if (item.active) navItem.classList.add('jux-nav-item-active');
236
- itemsContainer.appendChild(navItem);
204
+ const itemWrapper = document.createElement('div');
205
+ itemWrapper.className = 'jux-nav-item-wrapper';
206
+
207
+ if (item.itemClass) {
208
+ itemWrapper.className += ` ${item.itemClass}`;
209
+ }
210
+
211
+ const navLink = document.createElement('a');
212
+ navLink.className = 'jux-nav-link';
213
+ navLink.href = item.href;
214
+ navLink.textContent = item.label;
215
+ if (item.active) {
216
+ itemWrapper.classList.add('jux-nav-item-active');
217
+ navLink.classList.add('jux-nav-link-active');
218
+ }
219
+
220
+ itemWrapper.appendChild(navLink);
221
+ itemsContainer.appendChild(itemWrapper);
237
222
  });
238
223
  });
239
224
  }
240
225
  else if (property === 'brand') {
241
- const transformToComponent = toComponent || ((v: any) => v);
226
+ const transform = toComponent || ((v: any) => v);
242
227
 
243
228
  stateObj.subscribe((val: any) => {
244
- const transformed = transformToComponent(val);
229
+ const transformed = transform(val);
245
230
  const brandEl = nav.querySelector('.jux-nav-brand');
246
231
  if (brandEl && transformed.text) {
247
232
  const textEl = brandEl.querySelector('span:last-child');
@@ -254,8 +239,8 @@ export class Nav {
254
239
  }
255
240
  });
256
241
 
257
- // === 5. RENDER: Append to DOM and finalize ===
258
242
  container.appendChild(nav);
243
+ this._nav = nav;
259
244
 
260
245
  requestAnimationFrame(() => {
261
246
  if ((window as any).lucide) {
@@ -265,13 +250,6 @@ export class Nav {
265
250
 
266
251
  return this;
267
252
  }
268
-
269
- renderTo(juxComponent: any): this {
270
- if (!juxComponent?._id) {
271
- throw new Error('Nav.renderTo: Invalid component');
272
- }
273
- return this.render(`#${juxComponent._id}`);
274
- }
275
253
  }
276
254
 
277
255
  export function nav(id: string, options: NavOptions = {}): Nav {
@@ -1,118 +1,61 @@
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;
5
+ const CALLBACK_EVENTS = [] as const;
3
6
 
4
- /**
5
- * Paragraph options
6
- */
7
7
  export interface ParagraphOptions {
8
8
  text?: string;
9
9
  class?: string;
10
10
  style?: string;
11
11
  }
12
12
 
13
- /**
14
- * Paragraph state
15
- */
16
13
  type ParagraphState = {
17
14
  text: string;
18
15
  class: string;
19
16
  style: string;
20
17
  };
21
18
 
22
- /**
23
- * Paragraph component - semantic paragraph element
24
- */
25
- export class Paragraph {
26
- state: ParagraphState;
27
- container: HTMLElement | null = null;
28
- _id: string;
29
- id: string;
30
-
31
- // CRITICAL: Store bind/sync instructions for deferred wiring
32
- private _bindings: Array<{ event: string, handler: Function }> = [];
33
- private _syncBindings: Array<{
34
- property: string,
35
- stateObj: State<any>,
36
- toState?: Function,
37
- toComponent?: Function
38
- }> = [];
39
-
19
+ export class Paragraph extends BaseComponent<ParagraphState> {
40
20
  constructor(id: string, options: ParagraphOptions = {}) {
41
- this._id = id;
42
- this.id = id;
43
-
44
- this.state = {
21
+ super(id, {
45
22
  text: options.text ?? '',
46
23
  class: options.class ?? '',
47
24
  style: options.style ?? ''
48
- };
25
+ });
49
26
  }
50
27
 
51
- /* -------------------------
52
- * Fluent API
53
- * ------------------------- */
54
-
55
- text(value: string): this {
56
- this.state.text = value;
57
- return this;
28
+ protected getTriggerEvents(): readonly string[] {
29
+ return TRIGGER_EVENTS;
58
30
  }
59
31
 
60
- class(value: string): this {
61
- this.state.class = value;
62
- return this;
32
+ protected getCallbackEvents(): readonly string[] {
33
+ return CALLBACK_EVENTS;
63
34
  }
64
35
 
65
- style(value: string): this {
66
- this.state.style = value;
67
- return this;
68
- }
36
+ /* ═════════════════════════════════════════════════════════════════
37
+ * FLUENT API
38
+ * ═════════════════════════════════════════════════════════════════ */
69
39
 
70
- /**
71
- * Bind event handler (stores for wiring in render)
72
- * DOM events only: click, mouseenter, etc.
73
- */
74
- bind(event: string, handler: Function): this {
75
- this._bindings.push({ event, handler });
76
- return this;
77
- }
40
+ // ✅ Inherited from BaseComponent:
41
+ // - style(), class()
42
+ // - bind(), sync(), renderTo()
43
+ // - All other base methods
78
44
 
79
- /**
80
- * Sync with state (one-way: State → Component)
81
- *
82
- * @param property - Component property to sync ('text', 'class', 'style')
83
- * @param stateObj - State object to sync with
84
- * @param transform - Optional transform function from state to component
85
- */
86
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
87
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
88
- throw new Error(`Paragraph.sync: Expected a State object for property "${property}"`);
89
- }
90
- this._syncBindings.push({ property, stateObj, toState, toComponent });
45
+ text(value: string): this {
46
+ this.state.text = value;
91
47
  return this;
92
48
  }
93
49
 
94
- /* -------------------------
95
- * Render
96
- * ------------------------- */
50
+ /* ═════════════════════════════════════════════════════════════════
51
+ * RENDER
52
+ * ═════════════════════════════════════════════════════════════════ */
97
53
 
98
54
  render(targetId?: string): this {
99
- // === 1. SETUP: Get or create container ===
100
- let container: HTMLElement;
101
- if (targetId) {
102
- const target = document.querySelector(targetId);
103
- if (!target || !(target instanceof HTMLElement)) {
104
- throw new Error(`Paragraph: Target "${targetId}" not found`);
105
- }
106
- container = target;
107
- } else {
108
- container = getOrCreateContainer(this._id);
109
- }
110
- this.container = container;
55
+ const container = this._setupContainer(targetId);
111
56
 
112
- // === 2. PREPARE: Destructure state ===
113
57
  const { text, style, class: className } = this.state;
114
58
 
115
- // === 3. BUILD: Create DOM elements ===
116
59
  const paragraph = document.createElement('p');
117
60
  paragraph.className = 'jux-paragraph';
118
61
  paragraph.id = this._id;
@@ -120,42 +63,35 @@ export class Paragraph {
120
63
  if (className) paragraph.className += ` ${className}`;
121
64
  if (style) paragraph.setAttribute('style', style);
122
65
 
123
- // === 4. WIRE: Attach event listeners and sync bindings ===
124
-
125
- // Wire custom bindings from .bind() calls
126
- this._bindings.forEach(({ event, handler }) => {
127
- paragraph.addEventListener(event, handler as EventListener);
128
- });
66
+ this._wireStandardEvents(paragraph);
129
67
 
130
- // Wire sync bindings from .sync() calls
68
+ // Wire sync bindings
131
69
  this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
132
70
  if (property === 'text') {
133
- const transformToComponent = toComponent || ((v: any) => String(v));
71
+ const transform = toComponent || ((v: any) => String(v));
134
72
 
135
73
  stateObj.subscribe((val: any) => {
136
- const transformed = transformToComponent(val);
74
+ const transformed = transform(val);
137
75
  paragraph.textContent = transformed;
138
76
  this.state.text = transformed;
139
77
  });
140
78
  }
79
+ else if (property === 'class') {
80
+ const transform = toComponent || ((v: any) => String(v));
81
+
82
+ stateObj.subscribe((val: any) => {
83
+ const transformed = transform(val);
84
+ paragraph.className = `jux-paragraph ${transformed}`;
85
+ this.state.class = transformed;
86
+ });
87
+ }
141
88
  });
142
89
 
143
- // === 5. RENDER: Append to DOM and finalize ===
144
90
  container.appendChild(paragraph);
145
91
  return this;
146
92
  }
147
-
148
- renderTo(juxComponent: any): this {
149
- if (!juxComponent?._id) {
150
- throw new Error('Paragraph.renderTo: Invalid component');
151
- }
152
- return this.render(`#${juxComponent._id}`);
153
- }
154
93
  }
155
94
 
156
- /**
157
- * Factory helper
158
- */
159
95
  export function paragraph(id: string, options: ParagraphOptions = {}): Paragraph {
160
96
  return new Paragraph(id, options);
161
97
  }