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,68 +1,73 @@
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 DatePickerOptions {
5
9
  value?: string;
6
10
  label?: string;
7
11
  placeholder?: string;
12
+ required?: boolean;
8
13
  disabled?: boolean;
9
14
  name?: string;
10
15
  min?: string;
11
16
  max?: string;
12
17
  style?: string;
13
18
  class?: string;
19
+ onValidate?: (value: string) => boolean | string;
14
20
  }
15
21
 
16
- type DatePickerState = {
22
+ interface DatePickerState extends FormInputState {
17
23
  value: string;
18
- label: string;
19
24
  placeholder: string;
20
- disabled: boolean;
21
- name?: string;
22
25
  min?: string;
23
26
  max?: string;
24
- style: string;
25
- class: string;
26
- };
27
-
28
- export class DatePicker {
29
- state: DatePickerState;
30
- container: HTMLElement | null = null;
31
- _id: string;
32
- id: string;
33
-
34
- private _bindings: Array<{ event: string, handler: Function }> = [];
35
- private _syncBindings: Array<{ property: string, stateObj: State<any>, toState?: Function, toComponent?: Function }> = [];
27
+ }
36
28
 
29
+ export class DatePicker extends FormInput<DatePickerState> {
37
30
  constructor(id: string, options: DatePickerOptions = {}) {
38
- this._id = id;
39
- this.id = id;
40
-
41
- this.state = {
31
+ super(id, {
42
32
  value: options.value ?? '',
33
+ placeholder: options.placeholder ?? 'Select a date',
43
34
  label: options.label ?? '',
44
- placeholder: options.placeholder ?? '',
35
+ required: options.required ?? false,
45
36
  disabled: options.disabled ?? false,
46
- name: options.name,
37
+ name: options.name ?? id,
47
38
  min: options.min,
48
39
  max: options.max,
49
40
  style: options.style ?? '',
50
- class: options.class ?? ''
51
- };
41
+ class: options.class ?? '',
42
+ errorMessage: undefined
43
+ });
44
+
45
+ if (options.onValidate) {
46
+ this._onValidate = options.onValidate;
47
+ }
52
48
  }
53
49
 
54
- /* -------------------------
55
- * Fluent API
56
- * ------------------------- */
50
+ protected getTriggerEvents(): readonly string[] {
51
+ return TRIGGER_EVENTS;
52
+ }
57
53
 
58
- value(value: string): this {
59
- this.state.value = value;
60
- return this;
54
+ protected getCallbackEvents(): readonly string[] {
55
+ return CALLBACK_EVENTS;
61
56
  }
62
57
 
63
- label(value: string): this {
64
- this.state.label = value;
65
- return this;
58
+ /* ═════════════════════════════════════════════════════════════════
59
+ * FLUENT API
60
+ * ═════════════════════════════════════════════════════════════════ */
61
+
62
+ // ✅ Inherited from FormInput/BaseComponent:
63
+ // - label(), required(), name(), onValidate()
64
+ // - validate(), isValid()
65
+ // - style(), class()
66
+ // - bind(), sync(), renderTo()
67
+ // - disabled(), enable(), disable()
68
+
69
+ value(value: string): this {
70
+ return this.setValue(value);
66
71
  }
67
72
 
68
73
  placeholder(value: string): this {
@@ -70,11 +75,6 @@ export class DatePicker {
70
75
  return this;
71
76
  }
72
77
 
73
- disabled(value: boolean): this {
74
- this.state.disabled = value;
75
- return this;
76
- }
77
-
78
78
  min(value: string): this {
79
79
  this.state.min = value;
80
80
  return this;
@@ -85,154 +85,187 @@ export class DatePicker {
85
85
  return this;
86
86
  }
87
87
 
88
- style(value: string): this {
89
- this.state.style = value;
90
- return this;
91
- }
88
+ /* ═════════════════════════════════════════════════════════════════
89
+ * FORM INPUT IMPLEMENTATION
90
+ * ═════════════════════════════════════════════════════════════════ */
92
91
 
93
- class(value: string): this {
94
- this.state.class = value;
95
- return this;
92
+ getValue(): string {
93
+ return this.state.value;
96
94
  }
97
95
 
98
- bind(event: string, handler: Function): this {
99
- this._bindings.push({ event, handler });
96
+ setValue(value: string): this {
97
+ this.state.value = value;
98
+ if (this._inputElement) {
99
+ (this._inputElement as HTMLInputElement).value = value;
100
+ }
100
101
  return this;
101
102
  }
102
103
 
103
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
104
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
105
- throw new Error(`DatePicker.sync: Expected a State object for property "${property}"`);
106
- }
107
- this._syncBindings.push({ property, stateObj, toState, toComponent });
108
- return this;
104
+ getDate(): Date | null {
105
+ if (!this.state.value) return null;
106
+ const date = new Date(this.state.value);
107
+ return isNaN(date.getTime()) ? null : date;
109
108
  }
110
109
 
111
- /* -------------------------
112
- * Helpers
113
- * ------------------------- */
110
+ protected _validateValue(value: string): boolean | string {
111
+ const { required, min, max } = this.state;
114
112
 
115
- private _updateElement(): void {
116
- const input = document.getElementById(`${this._id}-input`) as HTMLInputElement;
117
- if (input) {
118
- input.value = this.state.value;
119
- input.disabled = this.state.disabled;
113
+ if (required && !value) {
114
+ return 'Please select a date';
120
115
  }
121
- }
122
116
 
123
- getValue(): string {
124
- return this.state.value;
125
- }
117
+ if (value) {
118
+ const date = new Date(value);
126
119
 
127
- getDate(): Date | null {
128
- return this.state.value ? new Date(this.state.value) : null;
129
- }
120
+ if (isNaN(date.getTime())) {
121
+ return 'Invalid date';
122
+ }
130
123
 
131
- /* -------------------------
132
- * Render
133
- * ------------------------- */
124
+ if (min) {
125
+ const minDate = new Date(min);
126
+ if (date < minDate) {
127
+ return `Date must be on or after ${min}`;
128
+ }
129
+ }
134
130
 
135
- render(targetId?: string): this {
136
- // === 1. SETUP: Get or create container ===
137
- let container: HTMLElement;
138
- if (targetId) {
139
- const target = document.querySelector(targetId);
140
- if (!target || !(target instanceof HTMLElement)) {
141
- throw new Error(`DatePicker: Target "${targetId}" not found`);
131
+ if (max) {
132
+ const maxDate = new Date(max);
133
+ if (date > maxDate) {
134
+ return `Date must be on or before ${max}`;
135
+ }
142
136
  }
143
- container = target;
144
- } else {
145
- container = getOrCreateContainer(this._id);
146
137
  }
147
- this.container = container;
148
138
 
149
- // === 2. PREPARE: Destructure state and check sync flags ===
150
- const { value, label, disabled, name, min, max, style, class: className } = this.state;
151
- const hasValueSync = this._syncBindings.some(b => b.property === 'value');
139
+ if (this._onValidate) {
140
+ const result = this._onValidate(value);
141
+ if (result !== true) {
142
+ return result || 'Invalid date';
143
+ }
144
+ }
152
145
 
153
- // === 3. BUILD: Create DOM elements ===
154
- const wrapper = document.createElement('div');
155
- wrapper.className = 'jux-datepicker';
156
- wrapper.id = this._id;
157
- if (className) wrapper.className += ` ${className}`;
158
- if (style) wrapper.setAttribute('style', style);
146
+ return true;
147
+ }
159
148
 
160
- if (label) {
161
- const labelEl = document.createElement('label');
162
- labelEl.className = 'jux-datepicker-label';
163
- labelEl.htmlFor = `${this._id}-input`;
164
- labelEl.textContent = label;
165
- wrapper.appendChild(labelEl);
166
- }
149
+ protected _buildInputElement(): HTMLElement {
150
+ const { value, placeholder, required, disabled, name, min, max } = this.state;
167
151
 
168
152
  const input = document.createElement('input');
169
153
  input.type = 'date';
170
- input.className = 'jux-datepicker-input';
154
+ input.className = 'jux-input-element jux-datepicker-input';
171
155
  input.id = `${this._id}-input`;
172
156
  input.name = name;
173
157
  input.value = value;
158
+ input.placeholder = placeholder;
159
+ input.required = required;
174
160
  input.disabled = disabled;
161
+
175
162
  if (min) input.min = min;
176
163
  if (max) input.max = max;
177
164
 
178
- wrapper.appendChild(input);
165
+ return input;
166
+ }
167
+
168
+ /* ═════════════════════════════════════════════════════════════════
169
+ * RENDER
170
+ * ═════════════════════════════════════════════════════════════════ */
171
+
172
+ render(targetId?: string): this {
173
+ const container = this._setupContainer(targetId);
179
174
 
180
- // === 4. WIRE: Attach event listeners and sync bindings ===
175
+ const { style, class: className } = this.state;
181
176
 
182
- // Default behavior (only if NOT using sync)
183
- if (!hasValueSync) {
184
- input.addEventListener('change', () => {
185
- this.state.value = input.value;
186
- });
177
+ // Build wrapper
178
+ const wrapper = document.createElement('div');
179
+ wrapper.className = 'jux-input jux-datepicker';
180
+ wrapper.id = this._id;
181
+ if (className) wrapper.className += ` ${className}`;
182
+ if (style) wrapper.setAttribute('style', style);
183
+
184
+ // Label
185
+ if (this.state.label) {
186
+ wrapper.appendChild(this._renderLabel());
187
187
  }
188
188
 
189
- // Wire custom bindings from .bind() calls
190
- this._bindings.forEach(({ event, handler }) => {
191
- wrapper.addEventListener(event, handler as EventListener);
189
+ // Input container
190
+ const inputContainer = document.createElement('div');
191
+ inputContainer.className = 'jux-input-container jux-datepicker-container';
192
+
193
+ // Calendar icon
194
+ const iconEl = document.createElement('span');
195
+ iconEl.className = 'jux-input-icon';
196
+ iconEl.appendChild(renderIcon('calendar'));
197
+ inputContainer.appendChild(iconEl);
198
+
199
+ // Date input
200
+ const inputEl = this._buildInputElement();
201
+ this._inputElement = inputEl;
202
+ inputContainer.appendChild(inputEl);
203
+ wrapper.appendChild(inputContainer);
204
+
205
+ // Error element
206
+ wrapper.appendChild(this._renderError());
207
+
208
+ // Wire events
209
+ this._wireStandardEvents(wrapper);
210
+ this._wireFormSync(inputEl, 'change');
211
+
212
+ // 🎯 Fire change event when date changes
213
+ inputEl.addEventListener('change', () => {
214
+ const input = inputEl as HTMLInputElement;
215
+ this._triggerCallback('change', input.value);
192
216
  });
193
217
 
194
- // Wire sync bindings from .sync() calls
195
- this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
196
- if (property === 'value') {
197
- const transformToState = toState || ((v: any) => v);
198
- const transformToComponent = toComponent || ((v: any) => String(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.value !== transformed) {
207
- input.value = transformed;
208
- this.state.value = transformed;
209
- }
210
- });
211
-
212
- // Component → State
213
- input.addEventListener('change', () => {
214
- if (isUpdating) return;
215
- isUpdating = true;
216
-
217
- const transformed = transformToState(input.value);
218
- this.state.value = input.value;
219
- stateObj.set(transformed);
220
-
221
- setTimeout(() => { isUpdating = false; }, 0);
222
- });
218
+ // Sync label changes
219
+ const labelSync = this._syncBindings.find(b => b.property === 'label');
220
+ if (labelSync) {
221
+ const transform = labelSync.toComponent || ((v: any) => String(v));
222
+ labelSync.stateObj.subscribe((val: any) => {
223
+ this.label(transform(val));
224
+ });
225
+ }
226
+
227
+ container.appendChild(wrapper);
228
+ this._injectDatePickerStyles();
229
+ this._injectFormStyles();
230
+
231
+ requestAnimationFrame(() => {
232
+ if ((window as any).lucide) {
233
+ (window as any).lucide.createIcons();
223
234
  }
224
235
  });
225
236
 
226
- // === 5. RENDER: Append to DOM and finalize ===
227
- container.appendChild(wrapper);
228
237
  return this;
229
238
  }
230
239
 
231
- renderTo(juxComponent: any): this {
232
- if (!juxComponent?._id) {
233
- throw new Error('DatePicker.renderTo: Invalid component');
234
- }
235
- return this.render(`#${juxComponent._id}`);
240
+ private _injectDatePickerStyles(): void {
241
+ const styleId = 'jux-datepicker-styles';
242
+ if (document.getElementById(styleId)) return;
243
+
244
+ const style = document.createElement('style');
245
+ style.id = styleId;
246
+ style.textContent = `
247
+ .jux-datepicker-container {
248
+ position: relative;
249
+ }
250
+
251
+ .jux-datepicker-container .jux-input-icon {
252
+ pointer-events: none;
253
+ }
254
+
255
+ .jux-datepicker-input {
256
+ padding-left: 40px;
257
+ }
258
+
259
+ .jux-datepicker-input::-webkit-calendar-picker-indicator {
260
+ cursor: pointer;
261
+ opacity: 0;
262
+ position: absolute;
263
+ right: 0;
264
+ width: 100%;
265
+ height: 100%;
266
+ }
267
+ `;
268
+ document.head.appendChild(style);
236
269
  }
237
270
  }
238
271