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
@@ -0,0 +1,322 @@
1
+ import { BaseComponent } from './BaseComponent.js';
2
+
3
+ /**
4
+ * Base state interface for all form inputs
5
+ */
6
+ export interface FormInputState extends Record<string, any> {
7
+ label: string;
8
+ required: boolean;
9
+ disabled: boolean;
10
+ name: string;
11
+ style: string;
12
+ class: string;
13
+ errorMessage?: string;
14
+ }
15
+
16
+ /**
17
+ * Abstract base class for all form input components
18
+ * Extends BaseComponent with form-specific functionality
19
+ */
20
+ export abstract class FormInput<TState extends FormInputState> extends BaseComponent<TState> {
21
+ protected _inputElement: HTMLElement | null = null;
22
+ protected _labelElement: HTMLLabelElement | null = null;
23
+ protected _errorElement: HTMLElement | null = null;
24
+ protected _onValidate?: (value: any) => boolean | string;
25
+ protected _hasBeenValidated: boolean = false; // NEW: Track if user has submitted/validated
26
+
27
+ /* ═════════════════════════════════════════════════════════════════
28
+ * ABSTRACT METHODS (Child must implement)
29
+ * ═════════════════════════════════════════════════════════════════ */
30
+
31
+ /**
32
+ * Get the current value of the input
33
+ */
34
+ abstract getValue(): any;
35
+
36
+ /**
37
+ * Set the value of the input
38
+ */
39
+ abstract setValue(value: any): this;
40
+
41
+ /**
42
+ * Build the actual input element (input, select, textarea, etc.)
43
+ */
44
+ protected abstract _buildInputElement(): HTMLElement;
45
+
46
+ /**
47
+ * Validate the current value
48
+ */
49
+ protected abstract _validateValue(value: any): boolean | string;
50
+
51
+ /* ═════════════════════════════════════════════════════════════════
52
+ * COMMON FORM INPUT API
53
+ * ═════════════════════════════════════════════════════════════════ */
54
+
55
+ label(value: string): this {
56
+ this.state.label = value;
57
+ if (this._labelElement) {
58
+ this._labelElement.textContent = value;
59
+ }
60
+ return this;
61
+ }
62
+
63
+ required(value: boolean): this {
64
+ this.state.required = value;
65
+ return this;
66
+ }
67
+
68
+ name(value: string): this {
69
+ this.state.name = value;
70
+ return this;
71
+ }
72
+
73
+ onValidate(handler: (value: any) => boolean | string): this {
74
+ this._onValidate = handler;
75
+ return this;
76
+ }
77
+
78
+ /* ═════════════════════════════════════════════════════════════════
79
+ * VALIDATION
80
+ * ═════════════════════════════════════════════════════════════════ */
81
+
82
+ /**
83
+ * Validate the current value and show/hide errors
84
+ */
85
+ validate(): boolean {
86
+ this._hasBeenValidated = true; // Mark as validated
87
+ const value = this.getValue();
88
+ const result = this._validateValue(value);
89
+
90
+ if (result === true) {
91
+ this._clearError();
92
+ return true;
93
+ } else {
94
+ this._showError(result as string);
95
+ return false;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Check if current value is valid without showing errors
101
+ */
102
+ isValid(): boolean {
103
+ const value = this.getValue();
104
+ return this._validateValue(value) === true;
105
+ }
106
+
107
+ /**
108
+ * Show error message
109
+ */
110
+ protected _showError(message: string): void {
111
+ if (this._errorElement) {
112
+ this._errorElement.textContent = message;
113
+ this._errorElement.style.display = 'block';
114
+ }
115
+
116
+ if (this._inputElement) {
117
+ this._inputElement.classList.add('jux-input-invalid');
118
+ }
119
+
120
+ this.state.errorMessage = message;
121
+ }
122
+
123
+ /**
124
+ * Clear error message
125
+ */
126
+ protected _clearError(): void {
127
+ if (this._errorElement) {
128
+ this._errorElement.textContent = '';
129
+ this._errorElement.style.display = 'none';
130
+ }
131
+
132
+ if (this._inputElement) {
133
+ this._inputElement.classList.remove('jux-input-invalid');
134
+ }
135
+
136
+ this.state.errorMessage = undefined;
137
+ }
138
+
139
+ /* ═════════════════════════════════════════════════════════════════
140
+ * COMMON RENDER HELPERS
141
+ * ═════════════════════════════════════════════════════════════════ */
142
+
143
+ /**
144
+ * Build label element
145
+ */
146
+ protected _renderLabel(): HTMLLabelElement {
147
+ const { label, required } = this.state;
148
+
149
+ const labelEl = document.createElement('label');
150
+ labelEl.className = 'jux-input-label';
151
+ labelEl.htmlFor = `${this._id}-input`;
152
+ labelEl.textContent = label;
153
+
154
+ if (required) {
155
+ const requiredSpan = document.createElement('span');
156
+ requiredSpan.className = 'jux-input-required';
157
+ requiredSpan.textContent = ' *';
158
+ labelEl.appendChild(requiredSpan);
159
+ }
160
+
161
+ this._labelElement = labelEl;
162
+ return labelEl;
163
+ }
164
+
165
+ /**
166
+ * Build error element
167
+ */
168
+ protected _renderError(): HTMLElement {
169
+ const errorEl = document.createElement('div');
170
+ errorEl.className = 'jux-input-error';
171
+ errorEl.id = `${this._id}-error`;
172
+ errorEl.style.display = 'none';
173
+
174
+ this._errorElement = errorEl;
175
+ return errorEl;
176
+ }
177
+
178
+ /**
179
+ * Wire up two-way sync for value property
180
+ */
181
+ protected _wireFormSync(inputElement: HTMLElement, eventName: string = 'input'): void {
182
+ const valueSync = this._syncBindings.find(b => b.property === 'value');
183
+
184
+ if (valueSync) {
185
+ const { stateObj, toState, toComponent } = valueSync;
186
+
187
+ // Default transforms
188
+ const transformToState = toState || ((v: any) => v);
189
+ const transformToComponent = toComponent || ((v: any) => v);
190
+
191
+ let isUpdating = false;
192
+
193
+ // State → Component
194
+ stateObj.subscribe((val: any) => {
195
+ if (isUpdating) return;
196
+ const transformed = transformToComponent(val);
197
+ this.setValue(transformed);
198
+ });
199
+
200
+ // Component → State
201
+ inputElement.addEventListener(eventName, () => {
202
+ if (isUpdating) return;
203
+ isUpdating = true;
204
+
205
+ const value = this.getValue();
206
+ const transformed = transformToState(value);
207
+ this._clearError();
208
+
209
+ stateObj.set(transformed);
210
+
211
+ setTimeout(() => { isUpdating = false; }, 0);
212
+ });
213
+ } else {
214
+ // Default behavior without sync
215
+ inputElement.addEventListener(eventName, () => {
216
+ this._clearError();
217
+ });
218
+ }
219
+
220
+ // Only validate on blur IF the field has been validated before (e.g., after submit)
221
+ inputElement.addEventListener('blur', () => {
222
+ if (this._hasBeenValidated) {
223
+ this.validate();
224
+ }
225
+ });
226
+ }
227
+
228
+ /**
229
+ * Inject default form input styles
230
+ */
231
+ protected _injectFormStyles(): void {
232
+ const styleId = 'jux-form-input-styles';
233
+ if (document.getElementById(styleId)) return;
234
+
235
+ const style = document.createElement('style');
236
+ style.id = styleId;
237
+ style.textContent = `
238
+ .jux-input {
239
+ margin-bottom: 16px;
240
+ }
241
+
242
+ .jux-input-container {
243
+ position: relative;
244
+ }
245
+
246
+ .jux-input-with-icon .jux-input-element {
247
+ padding-left: 40px;
248
+ }
249
+
250
+ .jux-input-icon {
251
+ position: absolute;
252
+ left: 12px;
253
+ top: 50%;
254
+ transform: translateY(-50%);
255
+ display: flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ color: #6b7280;
259
+ pointer-events: none;
260
+ }
261
+
262
+ .jux-input-icon svg {
263
+ width: 18px;
264
+ height: 18px;
265
+ }
266
+
267
+ .jux-input-label {
268
+ display: block;
269
+ margin-bottom: 6px;
270
+ font-weight: 500;
271
+ color: #374151;
272
+ }
273
+
274
+ .jux-input-required {
275
+ color: #ef4444;
276
+ }
277
+
278
+ .jux-input-element {
279
+ width: 100%;
280
+ padding: 8px 12px;
281
+ border: 1px solid #d1d5db;
282
+ border-radius: 6px;
283
+ font-size: 14px;
284
+ transition: border-color 0.2s;
285
+ box-sizing: border-box;
286
+ }
287
+
288
+ .jux-input-element:focus {
289
+ outline: none;
290
+ border-color: #3b82f6;
291
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
292
+ }
293
+
294
+ .jux-input-element:disabled {
295
+ background-color: #f3f4f6;
296
+ cursor: not-allowed;
297
+ }
298
+
299
+ .jux-input-element.jux-input-invalid {
300
+ border-color: #ef4444;
301
+ }
302
+
303
+ .jux-input-element.jux-input-invalid:focus {
304
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
305
+ }
306
+
307
+ .jux-input-error {
308
+ color: #ef4444;
309
+ font-size: 12px;
310
+ margin-top: 4px;
311
+ }
312
+
313
+ .jux-input-counter {
314
+ text-align: right;
315
+ font-size: 12px;
316
+ color: #6b7280;
317
+ margin-top: 4px;
318
+ }
319
+ `;
320
+ document.head.appendChild(style);
321
+ }
322
+ }
@@ -1,13 +1,13 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+ import { renderIcon } from './icons.js';
3
+
4
+ // Event definitions
5
+ const TRIGGER_EVENTS = [] as const;
6
+ const CALLBACK_EVENTS = ['click'] as const; // ✅ Button fires click events
3
7
 
4
- /**
5
- * Button component options
6
- */
7
8
  export interface ButtonOptions {
8
9
  label?: string;
9
10
  icon?: string;
10
- click?: (e: Event) => void;
11
11
  variant?: 'primary' | 'secondary' | 'danger' | 'success' | 'warning' | 'info' | 'link' | string;
12
12
  size?: 'small' | 'medium' | 'large';
13
13
  disabled?: boolean;
@@ -19,13 +19,9 @@ export interface ButtonOptions {
19
19
  class?: string;
20
20
  }
21
21
 
22
- /**
23
- * Button component state
24
- */
25
22
  type ButtonState = {
26
23
  label: string;
27
24
  icon: string;
28
- click: ((e: Event) => void) | null;
29
25
  variant: string;
30
26
  size: string;
31
27
  disabled: boolean;
@@ -37,28 +33,15 @@ type ButtonState = {
37
33
  class: string;
38
34
  };
39
35
 
40
- /**
41
- * Button component
42
- */
43
- export class Button {
44
- state: ButtonState;
45
- container: HTMLElement | null = null;
46
- _id: string;
47
- id: string;
48
-
49
- // Store bind() instructions
50
- private _bindings: Array<{ event: string, handler: Function }> = [];
36
+ export class Button extends BaseComponent<ButtonState> {
37
+ private _buttonElement: HTMLButtonElement | null = null; // ✅ Store button reference
51
38
 
52
39
  constructor(id: string, options?: ButtonOptions) {
53
- this._id = id;
54
- this.id = id;
55
-
56
40
  const opts = options || {};
57
41
 
58
- this.state = {
42
+ super(id, {
59
43
  label: opts.label ?? 'Button',
60
44
  icon: opts.icon ?? '',
61
- click: opts.click ?? null,
62
45
  variant: opts.variant ?? 'primary',
63
46
  size: opts.size ?? 'medium',
64
47
  disabled: opts.disabled ?? false,
@@ -68,12 +51,22 @@ export class Button {
68
51
  type: opts.type ?? 'button',
69
52
  style: opts.style ?? '',
70
53
  class: opts.class ?? ''
71
- };
54
+ });
55
+ }
56
+
57
+ protected getTriggerEvents(): readonly string[] {
58
+ return TRIGGER_EVENTS;
59
+ }
60
+
61
+ protected getCallbackEvents(): readonly string[] {
62
+ return CALLBACK_EVENTS;
72
63
  }
73
64
 
74
- /* -------------------------
75
- * Fluent API
76
- * ------------------------- */
65
+ /* ═════════════════════════════════════════════════════════════════
66
+ * FLUENT API
67
+ * ═════════════════════════════════════════════════════════════════ */
68
+
69
+ // ✅ Inherited from BaseComponent
77
70
 
78
71
  label(value: string): this {
79
72
  this.state.label = value;
@@ -85,20 +78,7 @@ export class Button {
85
78
  return this;
86
79
  }
87
80
 
88
- click(callback: (e: Event) => void): this {
89
- this.state.click = callback;
90
- return this;
91
- }
92
-
93
- /**
94
- * Bind event handler (stores for wiring in render)
95
- */
96
- bind(event: string, handler: Function): this {
97
- this._bindings.push({ event, handler });
98
- return this;
99
- }
100
-
101
- variant(value: string): this {
81
+ variant(value: 'primary' | 'secondary' | 'danger' | 'success' | 'warning' | 'info' | 'link' | string): this {
102
82
  this.state.variant = value;
103
83
  return this;
104
84
  }
@@ -108,16 +88,6 @@ export class Button {
108
88
  return this;
109
89
  }
110
90
 
111
- disabled(value: boolean): this {
112
- this.state.disabled = value;
113
- return this;
114
- }
115
-
116
- loading(value: boolean): this {
117
- this.state.loading = value;
118
- return this;
119
- }
120
-
121
91
  iconPosition(value: 'left' | 'right'): this {
122
92
  this.state.iconPosition = value;
123
93
  return this;
@@ -133,102 +103,73 @@ export class Button {
133
103
  return this;
134
104
  }
135
105
 
136
- style(value: string): this {
137
- this.state.style = value;
138
- return this;
139
- }
106
+ /* ═════════════════════════════════════════════════════════════════
107
+ * RENDER
108
+ * ═════════════════════════════════════════════════════════════════ */
109
+
110
+ /**
111
+ * Override visible() to update the actual button element
112
+ */
113
+ visible(value: boolean): this {
114
+ (this.state as any).visible = value;
115
+
116
+ // Update the button element directly, not the container
117
+ if (this._buttonElement) {
118
+ this._buttonElement.style.display = value ? '' : 'none';
119
+ }
140
120
 
141
- class(value: string): this {
142
- this.state.class = value;
143
121
  return this;
144
122
  }
145
123
 
146
- /* -------------------------
147
- * Render
148
- * ------------------------- */
149
-
150
124
  render(targetId?: string): this {
151
- let container: HTMLElement;
152
-
153
- if (targetId) {
154
- const target = document.querySelector(targetId);
155
- if (!target || !(target instanceof HTMLElement)) {
156
- throw new Error(`Button: Target element "${targetId}" not found`);
157
- }
158
- container = target;
159
- } else {
160
- container = getOrCreateContainer(this._id);
161
- }
162
-
163
- this.container = container;
164
- const { label, icon, click, variant, size, disabled, loading, iconPosition, fullWidth, type, style, class: className } = this.state;
125
+ const container = this._setupContainer(targetId);
126
+ const { label: text, variant, size, disabled, icon, iconPosition, loading, style, class: className } = this.state;
165
127
 
166
128
  const button = document.createElement('button');
129
+ this._buttonElement = button; // ✅ Store reference
167
130
  button.className = `jux-button jux-button-${variant} jux-button-${size}`;
168
131
  button.id = this._id;
169
- button.type = type;
170
132
  button.disabled = disabled || loading;
171
133
 
172
- if (fullWidth) {
173
- button.classList.add('jux-button-full-width');
174
- }
175
-
176
- if (loading) {
177
- button.classList.add('jux-button-loading');
178
- }
134
+ if (className) button.className += ` ${className}`;
135
+ if (style) button.setAttribute('style', style);
179
136
 
180
- if (className) {
181
- button.className += ` ${className}`;
182
- }
183
-
184
- if (style) {
185
- button.setAttribute('style', style);
186
- }
187
-
188
- // Build button content
189
137
  if (icon && iconPosition === 'left') {
190
138
  const iconEl = document.createElement('span');
191
- iconEl.className = 'jux-button-icon jux-button-icon-left';
192
- iconEl.textContent = icon;
139
+ iconEl.className = 'jux-button-icon';
140
+ iconEl.appendChild(renderIcon(icon));
193
141
  button.appendChild(iconEl);
194
142
  }
195
143
 
196
- const labelEl = document.createElement('span');
197
- labelEl.className = 'jux-button-label';
198
- labelEl.textContent = loading ? 'Loading...' : label;
199
- button.appendChild(labelEl);
144
+ const textEl = document.createElement('span');
145
+ textEl.textContent = loading ? 'Loading...' : text;
146
+ button.appendChild(textEl);
200
147
 
201
148
  if (icon && iconPosition === 'right') {
202
149
  const iconEl = document.createElement('span');
203
- iconEl.className = 'jux-button-icon jux-button-icon-right';
204
- iconEl.textContent = icon;
150
+ iconEl.className = 'jux-button-icon';
151
+ iconEl.appendChild(renderIcon(icon));
205
152
  button.appendChild(iconEl);
206
153
  }
207
154
 
208
- // Event binding - legacy click handler from state
209
- if (click) {
210
- button.addEventListener('click', click);
211
- }
212
-
213
- // Event binding - bind() method handlers
214
- this._bindings.forEach(({ event, handler }) => {
215
- button.addEventListener(event, handler as EventListener);
155
+ button.addEventListener('click', (e) => {
156
+ if (!disabled && !loading) {
157
+ this._triggerCallback('click', e);
158
+ }
216
159
  });
217
160
 
218
- container.appendChild(button);
219
- return this;
220
- }
161
+ this._wireStandardEvents(button);
162
+ this._wireAllSyncs();
221
163
 
222
- renderTo(juxComponent: this): this {
223
- if (!juxComponent || typeof juxComponent !== 'object') {
224
- throw new Error('Button.renderTo: Invalid component - not an object');
225
- }
164
+ container.appendChild(button);
226
165
 
227
- if (!juxComponent._id || typeof juxComponent._id !== 'string') {
228
- throw new Error('Button.renderTo: Invalid component - missing _id (not a Jux component)');
229
- }
166
+ requestAnimationFrame(() => {
167
+ if ((window as any).lucide) {
168
+ (window as any).lucide.createIcons();
169
+ }
170
+ });
230
171
 
231
- return this.render(`#${juxComponent._id}`);
172
+ return this;
232
173
  }
233
174
  }
234
175