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
@@ -6,106 +6,38 @@ import { ErrorHandler } from './error-handler.js';
6
6
  * Auto-renders when created or modified
7
7
  */
8
8
  export class Script {
9
- private _content: string = '';
10
- private _type: string = '';
11
- private _element: HTMLScriptElement | null = null;
9
+ private _id: string;
10
+ private code: string;
12
11
 
13
- constructor(content: string = '') {
14
- this._content = content;
15
-
16
- // Auto-render if content provided
17
- if (content) {
18
- this.render();
19
- }
20
- }
21
-
22
- /**
23
- * Set inline JavaScript content
24
- */
25
- content(js: string): this {
26
- this._content = js;
27
- this.render();
28
- return this;
12
+ constructor(code: string, id?: string) {
13
+ // ID only for deduplication, auto-generate if not provided
14
+ this._id = id || `jux-script-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
15
+ this.code = code;
29
16
  }
30
17
 
31
- /**
32
- * Set script type (e.g., 'module', 'text/javascript')
33
- */
34
- type(value: string): this {
35
- this._type = value;
36
- this.render();
37
- return this;
38
- }
39
-
40
- /**
41
- * Set as module script
42
- */
43
- module(): this {
44
- this._type = 'module';
45
- this.render();
46
- return this;
47
- }
48
-
49
- /**
50
- * Render the inline script element
51
- */
52
18
  render(): this {
53
- if (typeof document === 'undefined') {
19
+ // Check if script with this ID already exists
20
+ if (document.getElementById(this._id)) {
54
21
  return this;
55
22
  }
56
23
 
57
- try {
58
- // Remove existing element if it exists
59
- this.remove();
60
-
61
- const script = document.createElement('script');
62
- script.textContent = this._content;
63
-
64
- if (this._type) {
65
- script.type = this._type;
66
- }
67
-
68
- document.head.appendChild(script);
69
- this._element = script;
70
-
71
- console.log('✓ Inline script rendered');
72
- } catch (error: any) {
73
- ErrorHandler.captureError({
74
- component: 'Script',
75
- method: 'render',
76
- message: error.message,
77
- stack: error.stack,
78
- timestamp: new Date(),
79
- context: {
80
- type: this._type || 'inline',
81
- error: 'runtime_exception'
82
- }
83
- });
84
- }
24
+ const script = document.createElement('script');
25
+ script.id = this._id;
26
+ script.textContent = this.code;
27
+ document.head.appendChild(script);
85
28
 
86
29
  return this;
87
30
  }
88
31
 
89
- /**
90
- * Remove the script from the document
91
- */
92
- remove(): this {
93
- if (this._element && this._element.parentNode) {
94
- this._element.parentNode.removeChild(this._element);
95
- this._element = null;
32
+ remove(): void {
33
+ const existing = document.getElementById(this._id);
34
+ if (existing) {
35
+ existing.remove();
96
36
  }
97
- return this;
98
37
  }
99
38
  }
100
39
 
101
- /**
102
- * Factory function
103
- *
104
- * Usage:
105
- * jux.script('console.log("Hello")');
106
- * jux.script().content('alert("Hi")');
107
- * jux.script('export const x = 1;').module();
108
- */
109
- export function script(content: string = ''): Script {
110
- return new Script(content);
40
+ // ✅ ID is optional
41
+ export function script(code: string, id?: string): Script {
42
+ return new Script(code, id);
111
43
  }
@@ -1,5 +1,8 @@
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 SelectOption {
5
8
  label: string;
@@ -10,74 +13,68 @@ export interface SelectOption {
10
13
  export interface SelectOptions {
11
14
  options?: SelectOption[];
12
15
  value?: string;
13
- placeholder?: string;
14
16
  label?: string;
17
+ placeholder?: string;
18
+ required?: boolean;
15
19
  disabled?: boolean;
16
20
  name?: string;
17
21
  style?: string;
18
22
  class?: string;
23
+ onValidate?: (value: string) => boolean | string;
19
24
  }
20
25
 
21
- type SelectState = {
26
+ interface SelectState extends FormInputState {
22
27
  options: SelectOption[];
23
28
  value: string;
24
29
  placeholder: string;
25
- label: string;
26
- disabled: boolean;
27
- name: string;
28
- style: string;
29
- class: string;
30
- };
31
-
32
- export class Select {
33
- state: SelectState;
34
- container: HTMLElement | null = null;
35
- _id: string;
36
- id: string;
37
-
38
- // CRITICAL: Store bind/sync instructions for deferred wiring
39
- private _bindings: Array<{ event: string, handler: Function }> = [];
40
- private _syncBindings: Array<{
41
- property: string,
42
- stateObj: State<any>,
43
- toState?: Function,
44
- toComponent?: Function
45
- }> = [];
30
+ }
46
31
 
32
+ export class Select extends FormInput<SelectState> {
47
33
  constructor(id: string, options: SelectOptions = {}) {
48
- this._id = id;
49
- this.id = id;
50
-
51
- this.state = {
34
+ super(id, {
52
35
  options: options.options ?? [],
53
36
  value: options.value ?? '',
54
- placeholder: options.placeholder ?? 'Select...',
37
+ placeholder: options.placeholder ?? 'Select an option',
55
38
  label: options.label ?? '',
39
+ required: options.required ?? false,
56
40
  disabled: options.disabled ?? false,
57
41
  name: options.name ?? id,
58
42
  style: options.style ?? '',
59
- class: options.class ?? ''
60
- };
43
+ class: options.class ?? '',
44
+ errorMessage: undefined
45
+ });
46
+
47
+ if (options.onValidate) {
48
+ this._onValidate = options.onValidate;
49
+ }
61
50
  }
62
51
 
63
- /* -------------------------
64
- * Fluent API
65
- * ------------------------- */
52
+ protected getTriggerEvents(): readonly string[] {
53
+ return TRIGGER_EVENTS;
54
+ }
66
55
 
67
- options(value: SelectOption[]): this {
68
- this.state.options = value;
69
- return this;
56
+ protected getCallbackEvents(): readonly string[] {
57
+ return CALLBACK_EVENTS;
70
58
  }
71
59
 
72
- addOption(option: SelectOption): this {
73
- this.state.options = [...this.state.options, option];
60
+ /* ═════════════════════════════════════════════════════════════════
61
+ * FLUENT API
62
+ * ═════════════════════════════════════════════════════════════════ */
63
+
64
+ // ✅ Inherited from FormInput/BaseComponent:
65
+ // - label(), required(), name(), onValidate()
66
+ // - validate(), isValid()
67
+ // - style(), class()
68
+ // - bind(), sync(), renderTo()
69
+ // - disabled(), enable(), disable()
70
+
71
+ options(value: SelectOption[]): this {
72
+ this.state.options = value;
74
73
  return this;
75
74
  }
76
75
 
77
76
  value(value: string): this {
78
- this.state.value = value;
79
- this._updateElement();
80
- return this;
77
+ return this.setValue(value);
81
78
  }
82
79
 
83
80
  placeholder(value: string): this {
@@ -85,201 +82,194 @@ export class Select {
85
82
  return this;
86
83
  }
87
84
 
88
- label(value: string): this {
89
- this.state.label = value;
90
- return this;
91
- }
92
-
93
- disabled(value: boolean): this {
94
- this.state.disabled = value;
95
- this._updateElement();
85
+ addOption(option: SelectOption): this {
86
+ this.state.options = [...this.state.options, option];
96
87
  return this;
97
88
  }
98
89
 
99
- name(value: string): this {
100
- this.state.name = value;
101
- return this;
102
- }
90
+ /* ═════════════════════════════════════════════════════════════════
91
+ * FORM INPUT IMPLEMENTATION
92
+ * ═════════════════════════════════════════════════════════════════ */
103
93
 
104
- style(value: string): this {
105
- this.state.style = value;
106
- return this;
107
- }
108
-
109
- class(value: string): this {
110
- this.state.class = value;
111
- return this;
94
+ getValue(): string {
95
+ return this.state.value;
112
96
  }
113
97
 
114
- /**
115
- * Bind event handler (stores for wiring in render)
116
- * DOM events only: change, focus, blur, etc.
117
- */
118
- bind(event: string, handler: Function): this {
119
- this._bindings.push({ event, handler });
120
- return this;
121
- }
98
+ setValue(value: string): this {
99
+ this.state.value = value;
122
100
 
123
- /**
124
- * Two-way sync with state (stores for wiring in render)
125
- *
126
- * @param property - Component property to sync ('value', 'label', 'disabled', 'options')
127
- * @param stateObj - State object to sync with
128
- * @param toState - Optional transform function when going from component to state
129
- * @param toComponent - Optional transform function when going from state to component
130
- */
131
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
132
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
133
- throw new Error(`Select.sync: Expected a State object for property "${property}"`);
101
+ if (this._inputElement) {
102
+ (this._inputElement as HTMLSelectElement).value = value;
134
103
  }
135
- this._syncBindings.push({ property, stateObj, toState, toComponent });
104
+
136
105
  return this;
137
106
  }
138
107
 
139
- /* -------------------------
140
- * Helpers
141
- * ------------------------- */
108
+ protected _validateValue(value: string): boolean | string {
109
+ const { required, options } = this.state;
142
110
 
143
- private _updateElement(): void {
144
- const select = document.getElementById(`${this._id}-select`) as HTMLSelectElement;
145
- if (select) {
146
- select.value = this.state.value;
147
- select.disabled = this.state.disabled;
111
+ if (required && !value) {
112
+ return 'Please select an option';
148
113
  }
149
- }
150
-
151
- getValue(): string {
152
- return this.state.value;
153
- }
154
114
 
155
- /* -------------------------
156
- * Render
157
- * ------------------------- */
115
+ // Validate that value is one of the options
116
+ if (value && !options.some(opt => opt.value === value)) {
117
+ return 'Invalid option selected';
118
+ }
158
119
 
159
- render(targetId?: string): this {
160
- // === 1. SETUP: Get or create container ===
161
- let container: HTMLElement;
162
- if (targetId) {
163
- const target = document.querySelector(targetId);
164
- if (!target || !(target instanceof HTMLElement)) {
165
- throw new Error(`Select: Target "${targetId}" not found`);
120
+ if (this._onValidate) {
121
+ const result = this._onValidate(value);
122
+ if (result !== true) {
123
+ return result || 'Invalid value';
166
124
  }
167
- container = target;
168
- } else {
169
- container = getOrCreateContainer(this._id);
170
125
  }
171
- this.container = container;
172
-
173
- // === 2. PREPARE: Destructure state and check sync flags ===
174
- const { value, options, label, disabled, name, style, class: className } = this.state;
175
- const hasValueSync = this._syncBindings.some(b => b.property === 'value');
176
126
 
177
- // === 3. BUILD: Create DOM elements ===
178
- const wrapper = document.createElement('div');
179
- wrapper.className = 'jux-select';
180
- wrapper.id = this._id;
181
- if (className) wrapper.className += ` ${className}`;
182
- if (style) wrapper.setAttribute('style', style);
127
+ return true;
128
+ }
183
129
 
184
- if (label) {
185
- const labelEl = document.createElement('label');
186
- labelEl.className = 'jux-select-label';
187
- labelEl.htmlFor = `${this._id}-select`;
188
- labelEl.textContent = label;
189
- wrapper.appendChild(labelEl);
190
- }
130
+ protected _buildInputElement(): HTMLElement {
131
+ const { options, value, placeholder, required, disabled, name } = this.state;
191
132
 
192
133
  const select = document.createElement('select');
193
- select.className = 'jux-select-element';
194
- select.id = `${this._id}-select`;
134
+ select.className = 'jux-input-element jux-select-element';
135
+ select.id = `${this._id}-input`;
195
136
  select.name = name;
196
- select.value = value;
137
+ select.required = required;
197
138
  select.disabled = disabled;
198
139
 
199
- options.forEach(opt => {
200
- const option = document.createElement('option');
201
- option.value = opt.value;
202
- option.textContent = opt.label;
203
- if (opt.value === value) option.selected = true;
204
- select.appendChild(option);
140
+ // Placeholder option
141
+ if (placeholder) {
142
+ const placeholderOption = document.createElement('option');
143
+ placeholderOption.value = '';
144
+ placeholderOption.textContent = placeholder;
145
+ placeholderOption.disabled = true;
146
+ placeholderOption.selected = !value;
147
+ select.appendChild(placeholderOption);
148
+ }
149
+
150
+ // Options
151
+ options.forEach(option => {
152
+ const optionEl = document.createElement('option');
153
+ optionEl.value = option.value;
154
+ optionEl.textContent = option.label;
155
+ optionEl.disabled = option.disabled || false;
156
+ optionEl.selected = option.value === value;
157
+ select.appendChild(optionEl);
205
158
  });
206
159
 
207
- wrapper.appendChild(select);
160
+ return select;
161
+ }
208
162
 
209
- // === 4. WIRE: Attach event listeners and sync bindings ===
163
+ /* ═════════════════════════════════════════════════════════════════
164
+ * RENDER
165
+ * ═════════════════════════════════════════════════════════════════ */
210
166
 
211
- // Default behavior (only if NOT using sync)
212
- if (!hasValueSync) {
213
- select.addEventListener('change', () => {
214
- this.state.value = select.value;
215
- });
216
- }
167
+ render(targetId?: string): this {
168
+ const container = this._setupContainer(targetId);
217
169
 
218
- // Wire custom bindings from .bind() calls
219
- this._bindings.forEach(({ event, handler }) => {
220
- wrapper.addEventListener(event, handler as EventListener);
221
- });
170
+ const { style, class: className } = this.state;
222
171
 
223
- // Wire sync bindings from .sync() calls
224
- this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
225
- if (property === 'value') {
226
- const transformToState = toState || ((v: any) => v);
227
- const transformToComponent = toComponent || ((v: any) => String(v));
228
-
229
- let isUpdating = false;
230
-
231
- // State → Component
232
- stateObj.subscribe((val: any) => {
233
- if (isUpdating) return;
234
- const transformed = transformToComponent(val);
235
- if (select.value !== transformed) {
236
- select.value = transformed;
237
- this.state.value = transformed;
238
- }
239
- });
172
+ // Build wrapper
173
+ const wrapper = document.createElement('div');
174
+ wrapper.className = 'jux-input jux-select';
175
+ wrapper.id = this._id;
176
+ if (className) wrapper.className += ` ${className}`;
177
+ if (style) wrapper.setAttribute('style', style);
240
178
 
241
- // Component → State
242
- select.addEventListener('change', () => {
243
- if (isUpdating) return;
244
- isUpdating = true;
179
+ // Label
180
+ if (this.state.label) {
181
+ wrapper.appendChild(this._renderLabel());
182
+ }
245
183
 
246
- const transformed = transformToState(select.value);
247
- this.state.value = select.value;
248
- stateObj.set(transformed);
184
+ // Select container
185
+ const selectContainer = document.createElement('div');
186
+ selectContainer.className = 'jux-input-container jux-select-container';
187
+
188
+ // Select element
189
+ const selectEl = this._buildInputElement() as HTMLSelectElement;
190
+ this._inputElement = selectEl;
191
+ selectContainer.appendChild(selectEl);
192
+ wrapper.appendChild(selectContainer);
193
+
194
+ // Error element
195
+ wrapper.appendChild(this._renderError());
196
+
197
+ // Wire events
198
+ this._wireStandardEvents(wrapper);
199
+ this._wireFormSync(selectEl, 'change');
200
+
201
+ // Sync label changes
202
+ const labelSync = this._syncBindings.find(b => b.property === 'label');
203
+ if (labelSync) {
204
+ const transform = labelSync.toComponent || ((v: any) => String(v));
205
+ labelSync.stateObj.subscribe((val: any) => {
206
+ this.label(transform(val));
207
+ });
208
+ }
249
209
 
250
- setTimeout(() => { isUpdating = false; }, 0);
251
- });
252
- }
253
- else if (property === 'options') {
254
- const transformToComponent = toComponent || ((v: any) => v);
255
-
256
- stateObj.subscribe((val: any) => {
257
- const transformed = transformToComponent(val);
258
- this.state.options = transformed;
259
-
260
- // Re-render options
261
- select.innerHTML = '';
262
- transformed.forEach((opt: any) => {
263
- const option = document.createElement('option');
264
- option.value = opt.value;
265
- option.textContent = opt.label;
266
- if (opt.value === this.state.value) option.selected = true;
267
- select.appendChild(option);
268
- });
210
+ // Sync options changes
211
+ const optionsSync = this._syncBindings.find(b => b.property === 'options');
212
+ if (optionsSync) {
213
+ const transform = optionsSync.toComponent || ((v: any) => v);
214
+ optionsSync.stateObj.subscribe((val: any) => {
215
+ const transformed = transform(val);
216
+ this.state.options = transformed;
217
+
218
+ // Rebuild select options
219
+ selectEl.innerHTML = '';
220
+
221
+ if (this.state.placeholder) {
222
+ const placeholderOption = document.createElement('option');
223
+ placeholderOption.value = '';
224
+ placeholderOption.textContent = this.state.placeholder;
225
+ placeholderOption.disabled = true;
226
+ placeholderOption.selected = !this.state.value;
227
+ selectEl.appendChild(placeholderOption);
228
+ }
229
+
230
+ transformed.forEach((option: SelectOption) => {
231
+ const optionEl = document.createElement('option');
232
+ optionEl.value = option.value;
233
+ optionEl.textContent = option.label;
234
+ optionEl.disabled = option.disabled || false;
235
+ optionEl.selected = option.value === this.state.value;
236
+ selectEl.appendChild(optionEl);
269
237
  });
270
- }
271
- });
238
+ });
239
+ }
272
240
 
273
- // === 5. RENDER: Append to DOM and finalize ===
274
241
  container.appendChild(wrapper);
242
+ this._injectSelectStyles();
243
+ this._injectFormStyles();
244
+
275
245
  return this;
276
246
  }
277
247
 
278
- renderTo(juxComponent: any): this {
279
- if (!juxComponent?._id) {
280
- throw new Error('Select.renderTo: Invalid component');
281
- }
282
- return this.render(`#${juxComponent._id}`);
248
+ private _injectSelectStyles(): void {
249
+ const styleId = 'jux-select-styles';
250
+ if (document.getElementById(styleId)) return;
251
+
252
+ const style = document.createElement('style');
253
+ style.id = styleId;
254
+ style.textContent = `
255
+ .jux-select-container {
256
+ position: relative;
257
+ }
258
+
259
+ .jux-select-element {
260
+ appearance: none;
261
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236b7280' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
262
+ background-repeat: no-repeat;
263
+ background-position: right 12px center;
264
+ padding-right: 36px;
265
+ cursor: pointer;
266
+ }
267
+
268
+ .jux-select-element:disabled {
269
+ cursor: not-allowed;
270
+ }
271
+ `;
272
+ document.head.appendChild(style);
283
273
  }
284
274
  }
285
275