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,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,269 +13,264 @@ 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
- onChange?: (value: string) => void;
18
21
  style?: string;
19
22
  class?: string;
23
+ onValidate?: (value: string) => boolean | string;
20
24
  }
21
25
 
22
- type SelectState = {
26
+ interface SelectState extends FormInputState {
23
27
  options: SelectOption[];
24
28
  value: string;
25
29
  placeholder: string;
26
- label: string;
27
- disabled: boolean;
28
- name: string;
29
- style: string;
30
- class: string;
31
- };
32
-
33
- export class Select {
34
- state: SelectState;
35
- container: HTMLElement | null = null;
36
- _id: string;
37
- id: string;
38
- private _onChange?: (value: string) => void;
39
- private _boundState?: State<string>;
30
+ }
40
31
 
32
+ export class Select extends FormInput<SelectState> {
41
33
  constructor(id: string, options: SelectOptions = {}) {
42
- this._id = id;
43
- this.id = id;
44
- this._onChange = options.onChange;
45
-
46
- this.state = {
34
+ super(id, {
47
35
  options: options.options ?? [],
48
36
  value: options.value ?? '',
49
- placeholder: options.placeholder ?? 'Select...',
37
+ placeholder: options.placeholder ?? 'Select an option',
50
38
  label: options.label ?? '',
39
+ required: options.required ?? false,
51
40
  disabled: options.disabled ?? false,
52
41
  name: options.name ?? id,
53
42
  style: options.style ?? '',
54
- class: options.class ?? ''
55
- };
56
- }
57
-
58
- options(value: SelectOption[]): this {
59
- this.state.options = value;
60
- return this;
61
- }
43
+ class: options.class ?? '',
44
+ errorMessage: undefined
45
+ });
62
46
 
63
- addOption(option: SelectOption): this {
64
- this.state.options = [...this.state.options, option];
65
- return this;
47
+ if (options.onValidate) {
48
+ this._onValidate = options.onValidate;
49
+ }
66
50
  }
67
51
 
68
- value(value: string): this {
69
- this.state.value = value;
70
- this._updateElement();
71
- return this;
52
+ protected getTriggerEvents(): readonly string[] {
53
+ return TRIGGER_EVENTS;
72
54
  }
73
55
 
74
- placeholder(value: string): this {
75
- this.state.placeholder = value;
76
- return this;
56
+ protected getCallbackEvents(): readonly string[] {
57
+ return CALLBACK_EVENTS;
77
58
  }
78
59
 
79
- label(value: string): this {
80
- this.state.label = value;
81
- return this;
82
- }
60
+ /* ═════════════════════════════════════════════════════════════════
61
+ * FLUENT API
62
+ * ═════════════════════════════════════════════════════════════════ */
83
63
 
84
- disabled(value: boolean): this {
85
- this.state.disabled = value;
86
- this._updateElement();
87
- return this;
88
- }
64
+ // Inherited from FormInput/BaseComponent:
65
+ // - label(), required(), name(), onValidate()
66
+ // - validate(), isValid()
67
+ // - style(), class()
68
+ // - bind(), sync(), renderTo()
69
+ // - disabled(), enable(), disable()
89
70
 
90
- name(value: string): this {
91
- this.state.name = value;
71
+ options(value: SelectOption[]): this {
72
+ this.state.options = value;
92
73
  return this;
93
74
  }
94
75
 
95
- style(value: string): this {
96
- this.state.style = value;
97
- return this;
76
+ value(value: string): this {
77
+ return this.setValue(value);
98
78
  }
99
79
 
100
- class(value: string): this {
101
- this.state.class = value;
80
+ placeholder(value: string): this {
81
+ this.state.placeholder = value;
102
82
  return this;
103
83
  }
104
84
 
105
- onChange(handler: (value: string) => void): this {
106
- this._onChange = handler;
85
+ addOption(option: SelectOption): this {
86
+ this.state.options = [...this.state.options, option];
107
87
  return this;
108
88
  }
109
89
 
110
- /**
111
- * Two-way binding to state
112
- */
113
- bind(stateObj: State<string>): this {
114
- this._boundState = stateObj;
115
-
116
- // Update select when state changes
117
- stateObj.subscribe((val) => {
118
- this.state.value = val;
119
- this._updateElement();
120
- });
121
-
122
- // Update state when select changes
123
- const originalOnChange = this._onChange;
124
- this._onChange = (value) => {
125
- stateObj.set(value);
126
- if (originalOnChange) {
127
- originalOnChange(value);
128
- }
129
- };
130
-
131
- return this;
132
- }
133
-
134
- private _updateElement(): void {
135
- const select = document.getElementById(`${this._id}-select`) as HTMLSelectElement;
136
- if (select) {
137
- select.value = this.state.value;
138
- select.disabled = this.state.disabled;
139
- }
140
- }
90
+ /* ═════════════════════════════════════════════════════════════════
91
+ * FORM INPUT IMPLEMENTATION
92
+ * ═════════════════════════════════════════════════════════════════ */
141
93
 
142
94
  getValue(): string {
143
95
  return this.state.value;
144
96
  }
145
97
 
146
- render(targetId?: string): this {
147
- let container: HTMLElement;
98
+ setValue(value: string): this {
99
+ this.state.value = value;
148
100
 
149
- if (targetId) {
150
- const target = document.querySelector(targetId);
151
- if (!target || !(target instanceof HTMLElement)) {
152
- throw new Error(`Select: Target element "${targetId}" not found`);
153
- }
154
- container = target;
155
- } else {
156
- container = getOrCreateContainer(this._id);
101
+ if (this._inputElement) {
102
+ (this._inputElement as HTMLSelectElement).value = value;
157
103
  }
158
104
 
159
- this.container = container;
160
- const { options, value, placeholder, label, disabled, name, style, class: className } = this.state;
105
+ return this;
106
+ }
161
107
 
162
- const wrapper = document.createElement('div');
163
- wrapper.className = 'jux-select';
164
- wrapper.id = this._id;
108
+ protected _validateValue(value: string): boolean | string {
109
+ const { required, options } = this.state;
165
110
 
166
- if (className) {
167
- wrapper.className += ` ${className}`;
111
+ if (required && !value) {
112
+ return 'Please select an option';
168
113
  }
169
114
 
170
- if (style) {
171
- wrapper.setAttribute('style', style);
115
+ // Validate that value is one of the options
116
+ if (value && !options.some(opt => opt.value === value)) {
117
+ return 'Invalid option selected';
172
118
  }
173
119
 
174
- // Label
175
- if (label) {
176
- const labelEl = document.createElement('label');
177
- labelEl.className = 'jux-select-label';
178
- labelEl.htmlFor = `${this._id}-select`;
179
- labelEl.textContent = label;
180
- wrapper.appendChild(labelEl);
120
+ if (this._onValidate) {
121
+ const result = this._onValidate(value);
122
+ if (result !== true) {
123
+ return result || 'Invalid value';
124
+ }
181
125
  }
182
126
 
127
+ return true;
128
+ }
129
+
130
+ protected _buildInputElement(): HTMLElement {
131
+ const { options, value, placeholder, required, disabled, name } = this.state;
132
+
183
133
  const select = document.createElement('select');
184
- select.className = 'jux-select-element';
185
- select.id = `${this._id}-select`;
134
+ select.className = 'jux-input-element jux-select-element';
135
+ select.id = `${this._id}-input`;
186
136
  select.name = name;
137
+ select.required = required;
187
138
  select.disabled = disabled;
188
139
 
189
140
  // Placeholder option
190
141
  if (placeholder) {
191
- const placeholderOpt = document.createElement('option');
192
- placeholderOpt.value = '';
193
- placeholderOpt.textContent = placeholder;
194
- placeholderOpt.disabled = true;
195
- placeholderOpt.selected = value === '';
196
- select.appendChild(placeholderOpt);
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);
197
148
  }
198
149
 
199
150
  // Options
200
- options.forEach(opt => {
201
- const option = document.createElement('option');
202
- option.value = opt.value;
203
- option.textContent = opt.label;
204
- option.disabled = opt.disabled ?? false;
205
- option.selected = opt.value === value;
206
- select.appendChild(option);
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);
207
158
  });
208
159
 
209
- select.addEventListener('change', (e) => {
210
- const target = e.target as HTMLSelectElement;
211
- this.state.value = target.value;
212
- if (this._onChange) {
213
- this._onChange(target.value);
214
- }
215
- });
160
+ return select;
161
+ }
216
162
 
217
- wrapper.appendChild(select);
218
- container.appendChild(wrapper);
163
+ /* ═════════════════════════════════════════════════════════════════
164
+ * RENDER
165
+ * ═════════════════════════════════════════════════════════════════ */
219
166
 
220
- // Add default styles if not already present
221
- this._injectDefaultStyles();
167
+ render(targetId?: string): this {
168
+ const container = this._setupContainer(targetId);
169
+
170
+ const { style, class: className } = this.state;
171
+
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);
178
+
179
+ // Label
180
+ if (this.state.label) {
181
+ wrapper.appendChild(this._renderLabel());
182
+ }
183
+
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
+ }
209
+
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);
237
+ });
238
+ });
239
+ }
240
+
241
+ container.appendChild(wrapper);
242
+ this._injectSelectStyles();
243
+ this._injectFormStyles();
222
244
 
223
245
  return this;
224
246
  }
225
247
 
226
- private _injectDefaultStyles(): void {
248
+ private _injectSelectStyles(): void {
227
249
  const styleId = 'jux-select-styles';
228
250
  if (document.getElementById(styleId)) return;
229
251
 
230
252
  const style = document.createElement('style');
231
253
  style.id = styleId;
232
254
  style.textContent = `
233
- .jux-select {
234
- margin-bottom: 16px;
235
- }
236
-
237
- .jux-select-label {
238
- display: block;
239
- margin-bottom: 6px;
240
- font-weight: 500;
241
- color: #374151;
242
- }
243
-
244
- .jux-select-element {
245
- width: 100%;
246
- padding: 8px 12px;
247
- border: 1px solid #d1d5db;
248
- border-radius: 6px;
249
- font-size: 14px;
250
- background: white;
251
- cursor: pointer;
252
- transition: border-color 0.2s;
253
- box-sizing: border-box;
254
- }
255
-
256
- .jux-select-element:focus {
257
- outline: none;
258
- border-color: #3b82f6;
259
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
260
- }
261
-
262
- .jux-select-element:disabled {
263
- background-color: #f3f4f6;
264
- cursor: not-allowed;
265
- }
266
- `;
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
+ `;
267
272
  document.head.appendChild(style);
268
273
  }
269
-
270
- renderTo(juxComponent: any): this {
271
- if (!juxComponent?._id) {
272
- throw new Error('Select.renderTo: Invalid component');
273
- }
274
- return this.render(`#${juxComponent._id}`);
275
- }
276
274
  }
277
275
 
278
276
  export function select(id: string, options: SelectOptions = {}): Select {