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