juxscript 1.1.2 → 1.1.4

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 (67) hide show
  1. package/machinery/build3.js +7 -91
  2. package/machinery/compiler3.js +3 -209
  3. package/machinery/config.js +93 -6
  4. package/machinery/serve.js +255 -0
  5. package/machinery/watcher.js +49 -161
  6. package/package.json +19 -5
  7. package/lib/components/alert.ts +0 -200
  8. package/lib/components/app.ts +0 -247
  9. package/lib/components/badge.ts +0 -101
  10. package/lib/components/base/BaseComponent.ts +0 -421
  11. package/lib/components/base/FormInput.ts +0 -227
  12. package/lib/components/button.ts +0 -178
  13. package/lib/components/card.ts +0 -173
  14. package/lib/components/chart.ts +0 -231
  15. package/lib/components/checkbox.ts +0 -242
  16. package/lib/components/code.ts +0 -123
  17. package/lib/components/container.ts +0 -140
  18. package/lib/components/data.ts +0 -135
  19. package/lib/components/datepicker.ts +0 -234
  20. package/lib/components/dialog.ts +0 -172
  21. package/lib/components/divider.ts +0 -100
  22. package/lib/components/dropdown.ts +0 -186
  23. package/lib/components/element.ts +0 -267
  24. package/lib/components/fileupload.ts +0 -309
  25. package/lib/components/grid.ts +0 -291
  26. package/lib/components/guard.ts +0 -92
  27. package/lib/components/heading.ts +0 -96
  28. package/lib/components/helpers.ts +0 -41
  29. package/lib/components/hero.ts +0 -224
  30. package/lib/components/icon.ts +0 -178
  31. package/lib/components/icons.ts +0 -464
  32. package/lib/components/include.ts +0 -410
  33. package/lib/components/input.ts +0 -457
  34. package/lib/components/list.ts +0 -419
  35. package/lib/components/loading.ts +0 -100
  36. package/lib/components/menu.ts +0 -275
  37. package/lib/components/modal.ts +0 -284
  38. package/lib/components/nav.ts +0 -257
  39. package/lib/components/paragraph.ts +0 -97
  40. package/lib/components/progress.ts +0 -159
  41. package/lib/components/radio.ts +0 -278
  42. package/lib/components/req.ts +0 -303
  43. package/lib/components/script.ts +0 -41
  44. package/lib/components/select.ts +0 -252
  45. package/lib/components/sidebar.ts +0 -275
  46. package/lib/components/style.ts +0 -41
  47. package/lib/components/switch.ts +0 -246
  48. package/lib/components/table.ts +0 -1249
  49. package/lib/components/tabs.ts +0 -250
  50. package/lib/components/theme-toggle.ts +0 -293
  51. package/lib/components/tooltip.ts +0 -144
  52. package/lib/components/view.ts +0 -190
  53. package/lib/components/write.ts +0 -272
  54. package/lib/layouts/default.css +0 -260
  55. package/lib/layouts/figma.css +0 -334
  56. package/lib/reactivity/state.ts +0 -78
  57. package/lib/utils/fetch.ts +0 -553
  58. package/machinery/ast.js +0 -347
  59. package/machinery/build.js +0 -466
  60. package/machinery/bundleAssets.js +0 -0
  61. package/machinery/bundleJux.js +0 -0
  62. package/machinery/bundleVendors.js +0 -0
  63. package/machinery/doc-generator.js +0 -136
  64. package/machinery/imports.js +0 -155
  65. package/machinery/server.js +0 -166
  66. package/machinery/ts-shim.js +0 -46
  67. package/machinery/validators/file-validator.js +0 -123
@@ -1,421 +0,0 @@
1
- import { State } from '../../reactivity/state.js';
2
- import { getOrCreateContainer } from '../helpers.js';
3
-
4
- /**
5
- * Abstract base class for all JUX components
6
- * Provides common storage, event routing, and lifecycle methods
7
- *
8
- * Children must provide:
9
- * - TRIGGER_EVENTS constant (readonly string[])
10
- * - CALLBACK_EVENTS constant (readonly string[])
11
- * - render() implementation
12
- */
13
- export abstract class BaseComponent<TState extends Record<string, any>> {
14
- // Common properties (all components have these)
15
- state: TState;
16
- container: HTMLElement | null = null;
17
- _id: string;
18
- id: string;
19
-
20
- // Event & sync storage (populated by bind() and sync())
21
- protected _bindings: Array<{ event: string, handler: Function }> = [];
22
- protected _syncBindings: Array<{
23
- property: string,
24
- stateObj: State<any>,
25
- toState?: Function,
26
- toComponent?: Function
27
- }> = [];
28
- protected _triggerHandlers: Map<string, Function> = new Map();
29
- protected _callbackHandlers: Map<string, Function> = new Map();
30
-
31
- constructor(id: string, initialState: TState) {
32
- this._id = id;
33
- this.id = id;
34
- this.state = initialState;
35
- }
36
-
37
- /* ═════════════════════════════════════════════════════════════════
38
- * ABSTRACT METHODS (Child must implement)
39
- * ═════════════════════════════════════════════════════════════════ */
40
-
41
- protected abstract getTriggerEvents(): readonly string[];
42
- protected abstract getCallbackEvents(): readonly string[];
43
- abstract render(targetId?: string): this;
44
-
45
- /* ═════════════════════════════════════════════════════════════════
46
- * COMMON FLUENT API (Inherited by all components)
47
- * ═════════════════════════════════════════════════════════════════ */
48
-
49
- /**
50
- * Set component style
51
- */
52
- style(value: string): this {
53
- (this.state as any).style = value;
54
- return this;
55
- }
56
-
57
- /**
58
- * Set component class
59
- */
60
- class(value: string): this {
61
- (this.state as any).class = value;
62
- return this;
63
- }
64
-
65
- /* ═════════════════════════════════════════════════════════════════
66
- * CSS CLASS MANAGEMENT
67
- * ═════════════════════════════════════════════════════════════════ */
68
-
69
- /**
70
- * Add a CSS class to the component
71
- */
72
- addClass(value: string): this {
73
- const current = (this.state as any).class || '';
74
- const classes = current.split(' ').filter((c: string) => c);
75
- if (!classes.includes(value)) {
76
- classes.push(value);
77
- (this.state as any).class = classes.join(' ');
78
- if (this.container) this.container.classList.add(value);
79
- }
80
- return this;
81
- }
82
-
83
- /**
84
- * Remove a CSS class from the component
85
- */
86
- removeClass(value: string): this {
87
- const current = (this.state as any).class || '';
88
- const classes = current.split(' ').filter((c: string) => c && c !== value);
89
- (this.state as any).class = classes.join(' ');
90
- if (this.container) this.container.classList.remove(value);
91
- return this;
92
- }
93
-
94
- /**
95
- * Toggle a CSS class on the component
96
- */
97
- toggleClass(value: string): this {
98
- const current = (this.state as any).class || '';
99
- const hasClass = current.split(' ').includes(value);
100
- return hasClass ? this.removeClass(value) : this.addClass(value);
101
- }
102
-
103
- /* ═════════════════════════════════════════════════════════════════
104
- * VISIBILITY CONTROL
105
- * ═════════════════════════════════════════════════════════════════ */
106
-
107
- /**
108
- * Set component visibility
109
- */
110
- visible(value: boolean): this {
111
- (this.state as any).visible = value;
112
- if (this.container) {
113
- // Find the actual component wrapper, not the parent container
114
- const wrapper = this.container.querySelector(`#${this._id}`) as HTMLElement;
115
- if (wrapper) {
116
- wrapper.style.display = value ? '' : 'none';
117
- }
118
- }
119
- return this;
120
- }
121
-
122
- /**
123
- * Show the component
124
- */
125
- show(): this {
126
- return this.visible(true);
127
- }
128
-
129
- /**
130
- * Hide the component
131
- */
132
- hide(): this {
133
- return this.visible(false);
134
- }
135
-
136
- /**
137
- * Toggle component visibility
138
- */
139
- toggleVisibility(): this {
140
- const isVisible = (this.state as any).visible ?? true;
141
- return this.visible(!isVisible);
142
- }
143
-
144
- /* ═════════════════════════════════════════════════════════════════
145
- * ATTRIBUTE MANAGEMENT
146
- * ═════════════════════════════════════════════════════════════════ */
147
-
148
- /**
149
- * Set a single HTML attribute
150
- */
151
- attr(name: string, value: string): this {
152
- const attrs = (this.state as any).attributes || {};
153
- (this.state as any).attributes = { ...attrs, [name]: value };
154
- if (this.container) this.container.setAttribute(name, value);
155
- return this;
156
- }
157
-
158
- /**
159
- * Set multiple HTML attributes
160
- */
161
- attrs(attributes: Record<string, string>): this {
162
- Object.entries(attributes).forEach(([name, value]) => {
163
- this.attr(name, value);
164
- });
165
- return this;
166
- }
167
-
168
- /**
169
- * Remove an HTML attribute
170
- */
171
- removeAttr(name: string): this {
172
- const attrs = (this.state as any).attributes || {};
173
- delete attrs[name];
174
- if (this.container) this.container.removeAttribute(name);
175
- return this;
176
- }
177
-
178
- /* ═════════════════════════════════════════════════════════════════
179
- * DISABLED STATE
180
- * ═════════════════════════════════════════════════════════════════ */
181
-
182
- /**
183
- * Set disabled state for interactive elements
184
- */
185
- disabled(value: boolean): this {
186
- (this.state as any).disabled = value;
187
- if (this.container) {
188
- const inputs = this.container.querySelectorAll('input, button, select, textarea');
189
- inputs.forEach(el => {
190
- (el as HTMLInputElement).disabled = value;
191
- });
192
- this.container.setAttribute('aria-disabled', String(value));
193
- }
194
- return this;
195
- }
196
-
197
- /**
198
- * Enable the component
199
- */
200
- enable(): this {
201
- return this.disabled(false);
202
- }
203
-
204
- /**
205
- * Disable the component
206
- */
207
- disable(): this {
208
- return this.disabled(true);
209
- }
210
-
211
- /* ═════════════════════════════════════════════════════════════════
212
- * LOADING STATE
213
- * ═════════════════════════════════════════════════════════════════ */
214
-
215
- /**
216
- * Set loading state
217
- */
218
- loading(value: boolean): this {
219
- (this.state as any).loading = value;
220
- if (this.container) {
221
- if (value) {
222
- this.container.classList.add('jux-loading');
223
- this.container.setAttribute('aria-busy', 'true');
224
- } else {
225
- this.container.classList.remove('jux-loading');
226
- this.container.removeAttribute('aria-busy');
227
- }
228
- }
229
- return this;
230
- }
231
-
232
- /* ═════════════════════════════════════════════════════════════════
233
- * FOCUS MANAGEMENT
234
- * ═════════════════════════════════════════════════════════════════ */
235
-
236
- /**
237
- * Focus the first focusable element in the component
238
- */
239
- focus(): this {
240
- if (this.container) {
241
- const focusable = this.container.querySelector('input, button, select, textarea, [tabindex]');
242
- if (focusable) (focusable as HTMLElement).focus();
243
- }
244
- return this;
245
- }
246
-
247
- /**
248
- * Blur the currently focused element in the component
249
- */
250
- blur(): this {
251
- if (this.container) {
252
- const focused = this.container.querySelector(':focus');
253
- if (focused) (focused as HTMLElement).blur();
254
- }
255
- return this;
256
- }
257
-
258
- /* ═════════════════════════════════════════════════════════════════
259
- * DOM MANIPULATION
260
- * ═════════════════════════════════════════════════════════════════ */
261
-
262
- /**
263
- * Remove the component from the DOM
264
- */
265
- remove(): this {
266
- if (this.container) {
267
- this.container.remove();
268
- this.container = null;
269
- }
270
- return this;
271
- }
272
-
273
- /* ═════════════════════════════════════════════════════════════════
274
- * EVENT BINDING (Shared logic)
275
- * ═════════════════════════════════════════════════════════════════ */
276
-
277
- bind(event: string, handler: Function): this {
278
- if (this._isTriggerEvent(event)) {
279
- this._triggerHandlers.set(event, handler);
280
- } else if (this._isCallbackEvent(event)) {
281
- this._callbackHandlers.set(event, handler);
282
- } else {
283
- this._bindings.push({ event, handler });
284
- }
285
- return this;
286
- }
287
-
288
- /**
289
- * Sync a component property with a State object
290
- * @param property - The property to sync
291
- * @param stateObj - The State object to sync with
292
- * @param toStateOrTransform - Either toState function OR a simple transform function
293
- * @param toComponent - Optional toComponent function (if toState was provided)
294
- */
295
- sync(property: string, stateObj: State<any>, toStateOrTransform?: Function, toComponent?: Function): this {
296
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
297
- throw new Error(`${this.constructor.name}.sync: Expected a State object for property "${property}"`);
298
- }
299
-
300
- // If only 3 args provided, treat the function as toComponent (the common case)
301
- const actualToState = (toComponent !== undefined) ? toStateOrTransform : undefined;
302
- const actualToComponent = (toComponent !== undefined) ? toComponent : toStateOrTransform;
303
-
304
- this._syncBindings.push({
305
- property,
306
- stateObj,
307
- toState: actualToState,
308
- toComponent: actualToComponent
309
- });
310
- return this;
311
- }
312
-
313
- protected _isTriggerEvent(event: string): boolean {
314
- return this.getTriggerEvents().includes(event);
315
- }
316
-
317
- protected _isCallbackEvent(event: string): boolean {
318
- return this.getCallbackEvents().includes(event);
319
- }
320
-
321
- protected _triggerCallback(eventName: string, ...args: any[]): void {
322
-
323
- if (this._callbackHandlers.has(eventName)) {
324
- const handler = this._callbackHandlers.get(eventName)!;
325
- handler(...args);
326
- } else {
327
- console.warn(`🔍 No handler found for "${eventName}"`);
328
- }
329
- }
330
-
331
- /* ═════════════════════════════════════════════════════════════════
332
- * COMMON RENDER HELPERS
333
- * ═════════════════════════════════════════════════════════════════ */
334
-
335
- protected _setupContainer(targetId?: string): HTMLElement {
336
- let container: HTMLElement;
337
- if (targetId) {
338
- // Strip leading # if present
339
- const id = targetId.startsWith('#') ? targetId.slice(1) : targetId;
340
- const target = document.getElementById(id);
341
- if (target) {
342
- container = target;
343
- } else {
344
- // Gracefully create the container instead of throwing
345
- console.warn(`[Jux] Target "${targetId}" not found, creating it with graceful fallback`);
346
- container = getOrCreateContainer(id);
347
- }
348
- } else {
349
- container = getOrCreateContainer(this._id);
350
- }
351
-
352
- // Add universal component class for DOM inspection
353
- // container.classList.add('jux-component');
354
-
355
- this.container = container;
356
- return container;
357
- }
358
-
359
- protected _wireStandardEvents(element: HTMLElement): void {
360
- this._bindings.forEach(({ event, handler }) => {
361
- element.addEventListener(event, handler as EventListener);
362
- });
363
- }
364
-
365
- /**
366
- * Automatically wire ALL sync bindings by calling the corresponding method
367
- * if it exists on the component
368
- */
369
- protected _wireAllSyncs(): void {
370
- this._syncBindings.forEach(({ property, stateObj, toComponent }) => {
371
- const transform = toComponent || ((v: any) => v);
372
-
373
- // Check if component has a method matching the property name
374
- const method = (this as any)[property];
375
-
376
- if (typeof method === 'function') {
377
- // Set initial value
378
- const initialValue = transform(stateObj.value);
379
- method.call(this, initialValue);
380
-
381
- // Subscribe to changes
382
- stateObj.subscribe((val: any) => {
383
- const transformed = transform(val);
384
- method.call(this, transformed);
385
- });
386
- } else {
387
- console.warn(
388
- `[Jux] ${this.constructor.name}.sync('${property}'): ` +
389
- `No method .${property}() found. Property will not be synced.`
390
- );
391
- }
392
- });
393
- }
394
-
395
- renderTo(juxComponent: any): this {
396
- if (!juxComponent?._id) {
397
- throw new Error(`${this.constructor.name}.renderTo: Invalid component`);
398
- }
399
- return this.render(`#${juxComponent._id}`);
400
- }
401
-
402
- /* ═════════════════════════════════════════════════════════════════
403
- * PROPS ACCESSOR - Read-only access to component state
404
- * ═════════════════════════════════════════════════════════════════ */
405
-
406
- /**
407
- * ✅ Read-only accessor for component state
408
- * Provides clear separation between setters (fluent methods) and getters
409
- *
410
- * @example
411
- * const myCard = card('example')
412
- * .title('Hello') // ✅ SETTER (fluent)
413
- * .content('World'); // ✅ SETTER (fluent)
414
- *
415
- * console.log(myCard.props.title); // ✅ GETTER: 'Hello'
416
- * console.log(myCard.props.content); // ✅ GETTER: 'World'
417
- */
418
- get props(): Readonly<TState> {
419
- return this.state as Readonly<TState>;
420
- }
421
- }
@@ -1,227 +0,0 @@
1
- import { BaseComponent } from './BaseComponent.js';
2
-
3
- /**
4
- * Base state interface for all form inputs
5
- */
6
- export interface FormInputState extends Record<string, any> {
7
- label: string;
8
- required: boolean;
9
- disabled: boolean;
10
- name: string;
11
- style: string;
12
- class: string;
13
- errorMessage?: string;
14
- }
15
-
16
- /**
17
- * Abstract base class for all form input components
18
- * Extends BaseComponent with form-specific functionality
19
- */
20
- export abstract class FormInput<TState extends FormInputState> extends BaseComponent<TState> {
21
- protected _inputElement: HTMLElement | null = null;
22
- protected _labelElement: HTMLLabelElement | null = null;
23
- protected _errorElement: HTMLElement | null = null;
24
- protected _onValidate?: (value: any) => boolean | string;
25
- protected _hasBeenValidated: boolean = false; // NEW: Track if user has submitted/validated
26
-
27
- /* ═════════════════════════════════════════════════════════════════
28
- * ABSTRACT METHODS (Child must implement)
29
- * ═════════════════════════════════════════════════════════════════ */
30
-
31
- /**
32
- * Get the current value of the input
33
- */
34
- abstract getValue(): any;
35
-
36
- /**
37
- * Set the value of the input
38
- */
39
- abstract setValue(value: any): this;
40
-
41
- /**
42
- * Build the actual input element (input, select, textarea, etc.)
43
- */
44
- protected abstract _buildInputElement(): HTMLElement;
45
-
46
- /**
47
- * Validate the current value
48
- */
49
- protected abstract _validateValue(value: any): boolean | string;
50
-
51
- /* ═════════════════════════════════════════════════════════════════
52
- * COMMON FORM INPUT API
53
- * ═════════════════════════════════════════════════════════════════ */
54
-
55
- label(value: string): this {
56
- this.state.label = value;
57
- if (this._labelElement) {
58
- this._labelElement.textContent = value;
59
- }
60
- return this;
61
- }
62
-
63
- required(value: boolean): this {
64
- this.state.required = value;
65
- return this;
66
- }
67
-
68
- name(value: string): this {
69
- this.state.name = value;
70
- return this;
71
- }
72
-
73
- onValidate(handler: (value: any) => boolean | string): this {
74
- this._onValidate = handler;
75
- return this;
76
- }
77
-
78
- /* ═════════════════════════════════════════════════════════════════
79
- * VALIDATION
80
- * ═════════════════════════════════════════════════════════════════ */
81
-
82
- /**
83
- * Validate the current value and show/hide errors
84
- */
85
- validate(): boolean {
86
- this._hasBeenValidated = true; // Mark as validated
87
- const value = this.getValue();
88
- const result = this._validateValue(value);
89
-
90
- if (result === true) {
91
- this._clearError();
92
- return true;
93
- } else {
94
- this._showError(result as string);
95
- return false;
96
- }
97
- }
98
-
99
- /**
100
- * Check if current value is valid without showing errors
101
- */
102
- isValid(): boolean {
103
- const value = this.getValue();
104
- return this._validateValue(value) === true;
105
- }
106
-
107
- /**
108
- * Show error message
109
- */
110
- protected _showError(message: string): void {
111
- if (this._errorElement) {
112
- this._errorElement.textContent = message;
113
- this._errorElement.style.display = 'block';
114
- }
115
-
116
- if (this._inputElement) {
117
- this._inputElement.classList.add('jux-input-invalid');
118
- }
119
-
120
- this.state.errorMessage = message;
121
- }
122
-
123
- /**
124
- * Clear error message
125
- */
126
- protected _clearError(): void {
127
- if (this._errorElement) {
128
- this._errorElement.textContent = '';
129
- this._errorElement.style.display = 'none';
130
- }
131
-
132
- if (this._inputElement) {
133
- this._inputElement.classList.remove('jux-input-invalid');
134
- }
135
-
136
- this.state.errorMessage = undefined;
137
- }
138
-
139
- /* ═════════════════════════════════════════════════════════════════
140
- * COMMON RENDER HELPERS
141
- * ═════════════════════════════════════════════════════════════════ */
142
-
143
- /**
144
- * Build label element
145
- */
146
- protected _renderLabel(): HTMLLabelElement {
147
- const { label, required } = this.state;
148
-
149
- const labelEl = document.createElement('label');
150
- labelEl.className = 'jux-input-label';
151
- labelEl.htmlFor = `${this._id}-input`;
152
- labelEl.textContent = label;
153
-
154
- if (required) {
155
- const requiredSpan = document.createElement('span');
156
- requiredSpan.className = 'jux-input-required';
157
- requiredSpan.textContent = ' *';
158
- labelEl.appendChild(requiredSpan);
159
- }
160
-
161
- this._labelElement = labelEl;
162
- return labelEl;
163
- }
164
-
165
- /**
166
- * Build error element
167
- */
168
- protected _renderError(): HTMLElement {
169
- const errorEl = document.createElement('div');
170
- errorEl.className = 'jux-input-error';
171
- errorEl.id = `${this._id}-error`;
172
- errorEl.style.display = 'none';
173
-
174
- this._errorElement = errorEl;
175
- return errorEl;
176
- }
177
-
178
- /**
179
- * Wire up two-way sync for value property
180
- */
181
- protected _wireFormSync(inputElement: HTMLElement, eventName: string = 'input'): void {
182
- const valueSync = this._syncBindings.find(b => b.property === 'value');
183
-
184
- if (valueSync) {
185
- const { stateObj, toState, toComponent } = valueSync;
186
-
187
- // Default transforms
188
- const transformToState = toState || ((v: any) => v);
189
- const transformToComponent = toComponent || ((v: any) => v);
190
-
191
- let isUpdating = false;
192
-
193
- // State → Component
194
- stateObj.subscribe((val: any) => {
195
- if (isUpdating) return;
196
- const transformed = transformToComponent(val);
197
- this.setValue(transformed);
198
- });
199
-
200
- // Component → State
201
- inputElement.addEventListener(eventName, () => {
202
- if (isUpdating) return;
203
- isUpdating = true;
204
-
205
- const value = this.getValue();
206
- const transformed = transformToState(value);
207
- this._clearError();
208
-
209
- stateObj.set(transformed);
210
-
211
- setTimeout(() => { isUpdating = false; }, 0);
212
- });
213
- } else {
214
- // Default behavior without sync
215
- inputElement.addEventListener(eventName, () => {
216
- this._clearError();
217
- });
218
- }
219
-
220
- // Only validate on blur IF the field has been validated before (e.g., after submit)
221
- inputElement.addEventListener('blur', () => {
222
- if (this._hasBeenValidated) {
223
- this.validate();
224
- }
225
- });
226
- }
227
- }