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