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,261 +1,331 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
1
+ import { FormInput, FormInputState } from './base/FormInput.js';
2
+
3
+ // Event definitions
4
+ const TRIGGER_EVENTS = [] as const;
5
+ const CALLBACK_EVENTS = ['change'] as const;
3
6
 
4
- /**
5
- * Switch component options
6
- */
7
7
  export interface SwitchOptions {
8
- label?: string;
9
8
  checked?: boolean;
9
+ label?: string;
10
+ required?: boolean;
10
11
  disabled?: boolean;
11
12
  name?: string;
12
- onChange?: (checked: boolean) => void;
13
+ value?: string;
13
14
  style?: string;
14
15
  class?: string;
16
+ onValidate?: (checked: boolean) => boolean | string;
15
17
  }
16
18
 
17
- /**
18
- * Switch component state
19
- */
20
- type SwitchState = {
21
- label: string;
19
+ interface SwitchState extends FormInputState {
22
20
  checked: boolean;
23
- disabled: boolean;
24
- name: string;
25
- style: string;
26
- class: string;
27
- };
28
-
29
- /**
30
- * Switch component - Toggle switch for boolean values
31
- *
32
- * Usage:
33
- * jux.switch('notifications', {
34
- * label: 'Enable notifications',
35
- * checked: true,
36
- * onChange: (checked) => console.log(checked)
37
- * }).render('#settings');
38
- *
39
- * // Two-way binding
40
- * const notificationsState = state(true);
41
- * jux.switch('notifications').label('Notifications').bind(notificationsState).render('#settings');
42
- */
43
- export class Switch {
44
- state: SwitchState;
45
- container: HTMLElement | null = null;
46
- _id: string;
47
- id: string;
48
- private _onChange?: (checked: boolean) => void;
49
- private _boundState?: State<boolean>;
21
+ value: string;
22
+ }
50
23
 
24
+ export class Switch extends FormInput<SwitchState> {
51
25
  constructor(id: string, options: SwitchOptions = {}) {
52
- this._id = id;
53
- this.id = id;
54
- this._onChange = options.onChange;
55
-
56
- this.state = {
57
- label: options.label ?? '',
26
+ super(id, {
58
27
  checked: options.checked ?? false,
28
+ value: options.value ?? 'on',
29
+ label: options.label ?? '',
30
+ required: options.required ?? false,
59
31
  disabled: options.disabled ?? false,
60
32
  name: options.name ?? id,
61
33
  style: options.style ?? '',
62
- class: options.class ?? ''
63
- };
64
- }
65
-
66
- /* -------------------------
67
- * Fluent API
68
- * ------------------------- */
34
+ class: options.class ?? '',
35
+ errorMessage: undefined
36
+ });
69
37
 
70
- label(value: string): this {
71
- this.state.label = value;
72
- return this;
38
+ if (options.onValidate) {
39
+ this._onValidate = options.onValidate;
40
+ }
73
41
  }
74
42
 
75
- checked(value: boolean): this {
76
- this.state.checked = value;
77
- this._updateElement();
78
- return this;
43
+ protected getTriggerEvents(): readonly string[] {
44
+ return TRIGGER_EVENTS;
79
45
  }
80
46
 
81
- disabled(value: boolean): this {
82
- this.state.disabled = value;
83
- this._updateElement();
84
- return this;
47
+ protected getCallbackEvents(): readonly string[] {
48
+ return CALLBACK_EVENTS;
85
49
  }
86
50
 
87
- name(value: string): this {
88
- this.state.name = value;
89
- return this;
90
- }
51
+ /* ═════════════════════════════════════════════════════════════════
52
+ * FLUENT API
53
+ * ═════════════════════════════════════════════════════════════════ */
91
54
 
92
- style(value: string): this {
93
- this.state.style = value;
94
- return this;
95
- }
55
+ // Inherited from FormInput/BaseComponent:
56
+ // - label(), required(), name(), onValidate()
57
+ // - validate(), isValid()
58
+ // - style(), class()
59
+ // - bind(), sync(), renderTo()
60
+ // - disabled(), enable(), disable()
96
61
 
97
- class(value: string): this {
98
- this.state.class = value;
99
- return this;
62
+ checked(value: boolean): this {
63
+ return this.setValue(value);
100
64
  }
101
65
 
102
- onChange(handler: (checked: boolean) => void): this {
103
- this._onChange = handler;
66
+ value(value: string): this {
67
+ this.state.value = value;
104
68
  return this;
105
69
  }
106
70
 
107
- /**
108
- * Two-way binding to state
109
- */
110
- bind(stateObj: State<boolean>): this {
111
- this._boundState = stateObj;
71
+ toggle(): this {
72
+ return this.setValue(!this.state.checked);
73
+ }
112
74
 
113
- // Update switch when state changes
114
- stateObj.subscribe((val) => {
115
- this.state.checked = val;
116
- this._updateElement();
117
- });
75
+ /* ═════════════════════════════════════════════════════════════════
76
+ * FORM INPUT IMPLEMENTATION
77
+ * ═════════════════════════════════════════════════════════════════ */
118
78
 
119
- // Update state when switch changes
120
- this.onChange((checked) => stateObj.set(checked));
79
+ getValue(): boolean {
80
+ return this.state.checked;
81
+ }
121
82
 
83
+ setValue(value: boolean): this {
84
+ this.state.checked = value;
85
+ if (this._inputElement) {
86
+ (this._inputElement as HTMLInputElement).checked = value;
87
+ }
122
88
  return this;
123
89
  }
124
90
 
125
- /* -------------------------
126
- * Helpers
127
- * ------------------------- */
91
+ protected _validateValue(checked: boolean): boolean | string {
92
+ const { required } = this.state;
128
93
 
129
- private _updateElement(): void {
130
- const input = document.getElementById(`${this._id}-input`) as HTMLInputElement;
131
- const track = document.getElementById(`${this._id}-track`);
132
-
133
- if (input) {
134
- input.checked = this.state.checked;
135
- input.disabled = this.state.disabled;
94
+ if (required && !checked) {
95
+ return 'This field must be enabled';
136
96
  }
137
97
 
138
- if (track) {
139
- if (this.state.checked) {
140
- track.classList.add('jux-switch-track-checked');
141
- } else {
142
- track.classList.remove('jux-switch-track-checked');
98
+ if (this._onValidate) {
99
+ const result = this._onValidate(checked);
100
+ if (result !== true) {
101
+ return result || 'Invalid value';
143
102
  }
144
103
  }
145
- }
146
104
 
147
- /**
148
- * Toggle the switch
149
- */
150
- toggle(): this {
151
- this.state.checked = !this.state.checked;
152
- this._updateElement();
153
- if (this._onChange) {
154
- this._onChange(this.state.checked);
155
- }
156
- return this;
105
+ return true;
157
106
  }
158
107
 
159
- isChecked(): boolean {
160
- return this.state.checked;
108
+ protected _buildInputElement(): HTMLElement {
109
+ const { checked, value, required, disabled, name } = this.state;
110
+
111
+ const input = document.createElement('input');
112
+ input.type = 'checkbox';
113
+ input.className = 'jux-switch-input';
114
+ input.id = `${this._id}-input`;
115
+ input.name = name;
116
+ input.value = value;
117
+ input.checked = checked;
118
+ input.required = required;
119
+ input.disabled = disabled;
120
+
121
+ return input;
161
122
  }
162
123
 
163
- /* -------------------------
164
- * Render
165
- * ------------------------- */
124
+ /* ═════════════════════════════════════════════════════════════════
125
+ * RENDER
126
+ * ═════════════════════════════════════════════════════════════════ */
166
127
 
167
128
  render(targetId?: string): this {
168
- let container: HTMLElement;
169
-
170
- if (targetId) {
171
- const target = document.querySelector(targetId);
172
- if (!target || !(target instanceof HTMLElement)) {
173
- throw new Error(`Switch: Target element "${targetId}" not found`);
174
- }
175
- container = target;
176
- } else {
177
- container = getOrCreateContainer(this._id);
178
- }
129
+ const container = this._setupContainer(targetId);
179
130
 
180
- this.container = container;
181
- const { label, checked, disabled, name, style, class: className } = this.state;
131
+ const { style, class: className } = this.state;
182
132
 
133
+ // Build wrapper
183
134
  const wrapper = document.createElement('div');
184
135
  wrapper.className = 'jux-switch';
185
136
  wrapper.id = this._id;
186
-
187
- if (className) {
188
- wrapper.className += ` ${className}`;
137
+ if (className) wrapper.className += ` ${className}`;
138
+ if (style) wrapper.setAttribute('style', style);
139
+
140
+ // Switch container
141
+ const switchContainer = document.createElement('label');
142
+ switchContainer.className = 'jux-switch-container';
143
+ switchContainer.htmlFor = `${this._id}-input`;
144
+
145
+ // Input element
146
+ const inputEl = this._buildInputElement() as HTMLInputElement;
147
+ this._inputElement = inputEl;
148
+ switchContainer.appendChild(inputEl);
149
+
150
+ // Toggle slider
151
+ const slider = document.createElement('span');
152
+ slider.className = 'jux-switch-slider';
153
+ switchContainer.appendChild(slider);
154
+
155
+ // Label text
156
+ if (this.state.label) {
157
+ const labelText = document.createElement('span');
158
+ labelText.className = 'jux-switch-label-text';
159
+ labelText.textContent = this.state.label;
160
+ if (this.state.required) {
161
+ const requiredSpan = document.createElement('span');
162
+ requiredSpan.className = 'jux-input-required';
163
+ requiredSpan.textContent = ' *';
164
+ labelText.appendChild(requiredSpan);
165
+ }
166
+ switchContainer.appendChild(labelText);
189
167
  }
190
168
 
191
- if (style) {
192
- wrapper.setAttribute('style', style);
193
- }
169
+ wrapper.appendChild(switchContainer);
194
170
 
195
- const labelEl = document.createElement('label');
196
- labelEl.className = 'jux-switch-label';
171
+ // Error element
172
+ wrapper.appendChild(this._renderError());
197
173
 
198
- const input = document.createElement('input');
199
- input.type = 'checkbox';
200
- input.className = 'jux-switch-input';
201
- input.id = `${this._id}-input`;
202
- input.name = name;
203
- input.checked = checked;
204
- input.disabled = disabled;
205
- input.setAttribute('role', 'switch');
206
-
207
- input.addEventListener('change', (e) => {
208
- const target = e.target as HTMLInputElement;
209
- this.state.checked = target.checked;
210
-
211
- const track = document.getElementById(`${this._id}-track`);
212
- if (track) {
213
- if (target.checked) {
214
- track.classList.add('jux-switch-track-checked');
215
- } else {
216
- track.classList.remove('jux-switch-track-checked');
217
- }
218
- }
174
+ // Wire events
175
+ this._wireStandardEvents(wrapper);
219
176
 
220
- if (this._onChange) {
221
- this._onChange(target.checked);
222
- }
223
- });
177
+ // Wire switch-specific sync (maps 'checked' to 'value' property for consistency)
178
+ const valueSync = this._syncBindings.find(b => b.property === 'value' || b.property === 'checked');
224
179
 
225
- const track = document.createElement('span');
226
- track.className = 'jux-switch-track';
227
- track.id = `${this._id}-track`;
228
- if (checked) {
229
- track.classList.add('jux-switch-track-checked');
230
- }
180
+ if (valueSync) {
181
+ const { stateObj, toState, toComponent } = valueSync;
182
+
183
+ const transformToState = toState || ((v: boolean) => v);
184
+ const transformToComponent = toComponent || ((v: any) => Boolean(v));
185
+
186
+ let isUpdating = false;
187
+
188
+ // State → Component
189
+ stateObj.subscribe((val: any) => {
190
+ if (isUpdating) return;
191
+ const transformed = transformToComponent(val);
192
+ this.setValue(transformed);
193
+ });
194
+
195
+ // Component → State
196
+ inputEl.addEventListener('change', () => {
197
+ if (isUpdating) return;
198
+ isUpdating = true;
199
+
200
+ const checked = inputEl.checked;
201
+ this.state.checked = checked;
202
+ this._clearError();
231
203
 
232
- const thumb = document.createElement('span');
233
- thumb.className = 'jux-switch-thumb';
204
+ const transformed = transformToState(checked);
205
+ stateObj.set(transformed);
234
206
 
235
- track.appendChild(thumb);
236
- labelEl.appendChild(input);
237
- labelEl.appendChild(track);
207
+ // 🎯 Fire the change callback event
208
+ this._triggerCallback('change', checked);
238
209
 
239
- if (label) {
240
- const span = document.createElement('span');
241
- span.className = 'jux-switch-text';
242
- span.textContent = label;
243
- labelEl.appendChild(span);
210
+ setTimeout(() => { isUpdating = false; }, 0);
211
+ });
212
+ } else {
213
+ // Default behavior without sync
214
+ inputEl.addEventListener('change', () => {
215
+ this.state.checked = inputEl.checked;
216
+ this._clearError();
217
+
218
+ // 🎯 Fire the change callback event
219
+ this._triggerCallback('change', inputEl.checked);
220
+ });
221
+ }
222
+
223
+ // Always add blur validation
224
+ inputEl.addEventListener('blur', () => {
225
+ this.validate();
226
+ });
227
+
228
+ // Sync label changes
229
+ const labelSync = this._syncBindings.find(b => b.property === 'label');
230
+ if (labelSync) {
231
+ const transform = labelSync.toComponent || ((v: any) => String(v));
232
+ labelSync.stateObj.subscribe((val: any) => {
233
+ this.label(transform(val));
234
+ });
244
235
  }
245
236
 
246
- wrapper.appendChild(labelEl);
247
237
  container.appendChild(wrapper);
238
+ this._injectSwitchStyles();
239
+
248
240
  return this;
249
241
  }
250
242
 
251
- renderTo(juxComponent: any): this {
252
- if (!juxComponent?._id) {
253
- throw new Error('Switch.renderTo: Invalid component');
254
- }
255
- return this.render(`#${juxComponent._id}`);
243
+ private _injectSwitchStyles(): void {
244
+ const styleId = 'jux-switch-styles';
245
+ if (document.getElementById(styleId)) return;
246
+
247
+ const style = document.createElement('style');
248
+ style.id = styleId;
249
+ style.textContent = `
250
+ .jux-switch {
251
+ margin-bottom: 12px;
252
+ }
253
+
254
+ .jux-switch-container {
255
+ display: inline-flex;
256
+ align-items: center;
257
+ cursor: pointer;
258
+ user-select: none;
259
+ position: relative;
260
+ }
261
+
262
+ .jux-switch-input {
263
+ position: absolute;
264
+ opacity: 0;
265
+ cursor: pointer;
266
+ height: 0;
267
+ width: 0;
268
+ }
269
+
270
+ .jux-switch-slider {
271
+ position: relative;
272
+ width: 48px;
273
+ height: 24px;
274
+ background-color: #d1d5db;
275
+ border-radius: 24px;
276
+ transition: background-color 0.3s;
277
+ margin-right: 8px;
278
+ }
279
+
280
+ .jux-switch-slider::before {
281
+ content: '';
282
+ position: absolute;
283
+ height: 18px;
284
+ width: 18px;
285
+ left: 3px;
286
+ top: 3px;
287
+ background-color: white;
288
+ border-radius: 50%;
289
+ transition: transform 0.3s;
290
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
291
+ }
292
+
293
+ .jux-switch-input:checked ~ .jux-switch-slider {
294
+ background-color: #3b82f6;
295
+ }
296
+
297
+ .jux-switch-input:checked ~ .jux-switch-slider::before {
298
+ transform: translateX(24px);
299
+ }
300
+
301
+ .jux-switch-input:disabled ~ .jux-switch-slider {
302
+ background-color: #f3f4f6;
303
+ cursor: not-allowed;
304
+ }
305
+
306
+ .jux-switch-input:disabled ~ .jux-switch-label-text {
307
+ color: #9ca3af;
308
+ cursor: not-allowed;
309
+ }
310
+
311
+ .jux-switch-input:focus ~ .jux-switch-slider {
312
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
313
+ }
314
+
315
+ .jux-switch-input.jux-input-invalid ~ .jux-switch-slider {
316
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
317
+ }
318
+
319
+ .jux-switch-label-text {
320
+ font-size: 14px;
321
+ color: #374151;
322
+ }
323
+ `;
324
+ document.head.appendChild(style);
256
325
  }
257
326
  }
258
327
 
259
328
  export function switchComponent(id: string, options: SwitchOptions = {}): Switch {
260
329
  return new Switch(id, options);
261
- }
330
+ }
331
+