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