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,98 +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
15
  style?: string;
11
16
  class?: string;
17
+ onValidate?: (checked: boolean) => boolean | string;
12
18
  }
13
19
 
14
- type CheckboxState = {
15
- label: string;
20
+ interface CheckboxState extends FormInputState {
16
21
  checked: boolean;
17
- disabled: boolean;
18
- name: string;
19
22
  value: string;
20
- style: string;
21
- class: string;
22
- };
23
-
24
- /**
25
- * Checkbox component
26
- *
27
- * Usage:
28
- * jux.checkbox('terms', {
29
- * label: 'I agree to terms',
30
- * checked: false
31
- * })
32
- * .bind('change', (e) => console.log(e.target.checked))
33
- * .render('#form');
34
- *
35
- * // Two-way binding with state
36
- * const termsState = state(false);
37
- * jux.checkbox('terms')
38
- * .label('I agree to terms')
39
- * .sync('checked', termsState)
40
- * .render('#form');
41
- */
42
- export class Checkbox {
43
- state: CheckboxState;
44
- container: HTMLElement | null = null;
45
- _id: string;
46
- id: string;
47
-
48
- // CRITICAL: Store bind/sync instructions for deferred wiring
49
- private _bindings: Array<{ event: string, handler: Function }> = [];
50
- private _syncBindings: Array<{
51
- property: string,
52
- stateObj: State<any>,
53
- toState?: Function,
54
- toComponent?: Function
55
- }> = [];
23
+ }
56
24
 
25
+ export class Checkbox extends FormInput<CheckboxState> {
57
26
  constructor(id: string, options: CheckboxOptions = {}) {
58
- this._id = id;
59
- this.id = id;
60
-
61
- this.state = {
62
- label: options.label ?? '',
27
+ super(id, {
63
28
  checked: options.checked ?? false,
29
+ value: options.value ?? 'on',
30
+ label: options.label ?? '',
31
+ required: options.required ?? false,
64
32
  disabled: options.disabled ?? false,
65
33
  name: options.name ?? id,
66
- value: options.value ?? 'on',
67
34
  style: options.style ?? '',
68
- class: options.class ?? ''
69
- };
70
- }
71
-
72
- /* -------------------------
73
- * Fluent API
74
- * ------------------------- */
35
+ class: options.class ?? '',
36
+ errorMessage: undefined
37
+ });
75
38
 
76
- label(value: string): this {
77
- this.state.label = value;
78
- return this;
39
+ if (options.onValidate) {
40
+ this._onValidate = options.onValidate;
41
+ }
79
42
  }
80
43
 
81
- checked(value: boolean): this {
82
- this.state.checked = value;
83
- this._updateElement();
84
- return this;
44
+ protected getTriggerEvents(): readonly string[] {
45
+ return TRIGGER_EVENTS;
85
46
  }
86
47
 
87
- disabled(value: boolean): this {
88
- this.state.disabled = value;
89
- this._updateElement();
90
- return this;
48
+ protected getCallbackEvents(): readonly string[] {
49
+ return CALLBACK_EVENTS;
91
50
  }
92
51
 
93
- name(value: string): this {
94
- this.state.name = value;
95
- 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);
96
65
  }
97
66
 
98
67
  value(value: string): this {
@@ -100,183 +69,265 @@ export class Checkbox {
100
69
  return this;
101
70
  }
102
71
 
103
- style(value: string): this {
104
- this.state.style = value;
105
- return this;
106
- }
107
-
108
- class(value: string): this {
109
- this.state.class = value;
110
- return this;
111
- }
72
+ /* ═════════════════════════════════════════════════════════════════
73
+ * FORM INPUT IMPLEMENTATION
74
+ * ═════════════════════════════════════════════════════════════════ */
112
75
 
113
- /**
114
- * Bind event handler (stores for wiring in render)
115
- * DOM events only: change, click, focus, blur, etc.
116
- */
117
- bind(event: string, handler: Function): this {
118
- this._bindings.push({ event, handler });
119
- return this;
76
+ getValue(): boolean {
77
+ return this.state.checked;
120
78
  }
121
79
 
122
- /**
123
- * Two-way sync with state (stores for wiring in render)
124
- *
125
- * @param property - Component property to sync ('checked', 'label', 'disabled')
126
- * @param stateObj - State object to sync with
127
- * @param toState - Optional transform function when going from component to state
128
- * @param toComponent - Optional transform function when going from state to component
129
- */
130
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
131
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
132
- throw new Error(`Checkbox.sync: Expected a State object for property "${property}"`);
80
+ setValue(value: boolean): this {
81
+ this.state.checked = value;
82
+ if (this._inputElement) {
83
+ (this._inputElement as HTMLInputElement).checked = value;
133
84
  }
134
- this._syncBindings.push({ property, stateObj, toState, toComponent });
135
85
  return this;
136
86
  }
137
87
 
138
- /* -------------------------
139
- * Helpers
140
- * ------------------------- */
88
+ protected _validateValue(checked: boolean): boolean | string {
89
+ const { required } = this.state;
141
90
 
142
- private _updateElement(): void {
143
- const input = document.getElementById(`${this._id}-input`) as HTMLInputElement;
144
- if (input) {
145
- input.checked = this.state.checked;
146
- input.disabled = this.state.disabled;
91
+ if (required && !checked) {
92
+ return 'This field must be checked';
147
93
  }
148
- }
149
94
 
150
- /**
151
- * Toggle the checkbox
152
- */
153
- toggle(): this {
154
- this.state.checked = !this.state.checked;
155
- this._updateElement();
156
- return this;
95
+ if (this._onValidate) {
96
+ const result = this._onValidate(checked);
97
+ if (result !== true) {
98
+ return result || 'Invalid value';
99
+ }
100
+ }
101
+
102
+ return true;
157
103
  }
158
104
 
159
- isChecked(): boolean {
160
- return this.state.checked;
105
+ protected _buildInputElement(): HTMLElement {
106
+ const { checked, value, required, disabled, name } = this.state;
107
+
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;
161
119
  }
162
120
 
163
- /* -------------------------
164
- * Render
165
- * ------------------------- */
121
+ /* ═════════════════════════════════════════════════════════════════
122
+ * RENDER
123
+ * ═════════════════════════════════════════════════════════════════ */
166
124
 
167
125
  render(targetId?: string): this {
168
- // === 1. SETUP: Get or create container ===
169
- let container: HTMLElement;
170
- if (targetId) {
171
- const target = document.querySelector(targetId);
172
- if (!target || !(target instanceof HTMLElement)) {
173
- throw new Error(`Checkbox: Target "${targetId}" not found`);
174
- }
175
- container = target;
176
- } else {
177
- container = getOrCreateContainer(this._id);
178
- }
179
- this.container = container;
126
+ const container = this._setupContainer(targetId);
180
127
 
181
- // === 2. PREPARE: Destructure state and check sync flags ===
182
- const { checked, label, disabled, name, style, class: className } = this.state;
183
- const hasCheckedSync = this._syncBindings.some(b => b.property === 'checked');
128
+ const { style, class: className } = this.state;
184
129
 
185
- // === 3. BUILD: Create DOM elements ===
130
+ // Build wrapper
186
131
  const wrapper = document.createElement('div');
187
132
  wrapper.className = 'jux-checkbox';
188
133
  wrapper.id = this._id;
189
134
  if (className) wrapper.className += ` ${className}`;
190
135
  if (style) wrapper.setAttribute('style', style);
191
136
 
192
- const labelEl = document.createElement('label');
193
- labelEl.className = 'jux-checkbox-label';
137
+ // Checkbox container
138
+ const checkboxContainer = document.createElement('label');
139
+ checkboxContainer.className = 'jux-checkbox-container';
140
+ checkboxContainer.htmlFor = `${this._id}-input`;
194
141
 
195
- const input = document.createElement('input');
196
- input.type = 'checkbox';
197
- input.className = 'jux-checkbox-input';
198
- input.id = `${this._id}-input`;
199
- input.name = name;
200
- input.checked = checked;
201
- input.disabled = disabled;
142
+ // Input element
143
+ const inputEl = this._buildInputElement() as HTMLInputElement;
144
+ this._inputElement = inputEl;
145
+ checkboxContainer.appendChild(inputEl);
202
146
 
147
+ // Checkmark (custom styled)
203
148
  const checkmark = document.createElement('span');
204
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);
165
+ }
166
+
167
+ wrapper.appendChild(checkboxContainer);
168
+
169
+ // Error element
170
+ wrapper.appendChild(this._renderError());
205
171
 
206
- const text = document.createElement('span');
207
- text.className = 'jux-checkbox-text';
208
- text.textContent = label;
172
+ // Wire events
173
+ this._wireStandardEvents(wrapper);
209
174
 
210
- labelEl.appendChild(input);
211
- labelEl.appendChild(checkmark);
212
- labelEl.appendChild(text);
213
- wrapper.appendChild(labelEl);
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');
214
177
 
215
- // === 4. WIRE: Attach event listeners and sync bindings ===
178
+ if (valueSync) {
179
+ const { stateObj, toState, toComponent } = valueSync;
216
180
 
217
- // Default behavior (only if NOT using sync)
218
- if (!hasCheckedSync) {
219
- input.addEventListener('change', () => {
220
- this.state.checked = input.checked;
181
+ const transformToState = toState || ((v: boolean) => v);
182
+ const transformToComponent = toComponent || ((v: any) => Boolean(v));
183
+
184
+ let isUpdating = false;
185
+
186
+ // State → Component
187
+ stateObj.subscribe((val: any) => {
188
+ if (isUpdating) return;
189
+ const transformed = transformToComponent(val);
190
+ this.setValue(transformed);
191
+ });
192
+
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);
221
218
  });
222
219
  }
223
220
 
224
- // Wire custom bindings from .bind() calls
225
- this._bindings.forEach(({ event, handler }) => {
226
- wrapper.addEventListener(event, handler as EventListener);
221
+ // Always add blur validation
222
+ inputEl.addEventListener('blur', () => {
223
+ this.validate();
227
224
  });
228
225
 
229
- // Wire sync bindings from .sync() calls
230
- this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
231
- if (property === 'checked') {
232
- const transformToState = toState || ((v: any) => Boolean(v));
233
- const transformToComponent = toComponent || ((v: any) => Boolean(v));
234
-
235
- let isUpdating = false;
236
-
237
- // State → Component
238
- stateObj.subscribe((val: any) => {
239
- if (isUpdating) return;
240
- const transformed = transformToComponent(val);
241
- if (input.checked !== transformed) {
242
- input.checked = transformed;
243
- this.state.checked = transformed;
244
- }
245
- });
246
-
247
- // Component → State
248
- input.addEventListener('change', () => {
249
- if (isUpdating) return;
250
- isUpdating = true;
251
-
252
- const transformed = transformToState(input.checked);
253
- this.state.checked = input.checked;
254
- stateObj.set(transformed);
255
-
256
- setTimeout(() => { isUpdating = false; }, 0);
257
- });
258
- }
259
- else if (property === 'label') {
260
- const transformToComponent = toComponent || ((v: any) => String(v));
261
-
262
- stateObj.subscribe((val: any) => {
263
- const transformed = transformToComponent(val);
264
- text.textContent = transformed;
265
- this.state.label = transformed;
266
- });
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
+ });
233
+ }
234
+
235
+ container.appendChild(wrapper);
236
+ this._injectCheckboxStyles();
237
+
238
+ requestAnimationFrame(() => {
239
+ if ((window as any).lucide) {
240
+ (window as any).lucide.createIcons();
267
241
  }
268
242
  });
269
243
 
270
- // === 5. RENDER: Append to DOM and finalize ===
271
- container.appendChild(wrapper);
272
244
  return this;
273
245
  }
274
246
 
275
- renderTo(juxComponent: any): this {
276
- if (!juxComponent?._id) {
277
- throw new Error('Checkbox.renderTo: Invalid component');
278
- }
279
- 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);
280
331
  }
281
332
  }
282
333