juxscript 1.1.97 → 1.1.100
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.
- package/dom-structure-map.json +1 -1
- package/lib/components/base/FormInput.d.ts +77 -0
- package/lib/components/base/FormInput.d.ts.map +1 -0
- package/lib/components/base/FormInput.js +171 -0
- package/lib/components/base/FormInput.ts +237 -0
- package/lib/components/stack/BaseStack.d.ts +1 -0
- package/lib/components/stack/BaseStack.d.ts.map +1 -1
- package/lib/components/stack/BaseStack.js +16 -11
- package/lib/components/stack/BaseStack.ts +18 -11
- package/lib/components/stack/HStack.d.ts.map +1 -1
- package/lib/components/stack/HStack.js +2 -1
- package/lib/components/stack/HStack.ts +2 -1
- package/lib/components/stack/VStack.js +2 -2
- package/lib/components/stack/VStack.ts +2 -2
- package/lib/components/stack/ZStack.d.ts.map +1 -1
- package/lib/components/stack/ZStack.js +2 -1
- package/lib/components/stack/ZStack.ts +2 -1
- package/lib/layouts/VStack.d.ts +1 -0
- package/lib/layouts/VStack.d.ts.map +1 -0
- package/lib/layouts/VStack.js +1 -0
- package/lib/layouts/VStack.ts +0 -0
- package/lib/layouts/ZStack.d.ts +1 -0
- package/lib/layouts/ZStack.d.ts.map +1 -0
- package/lib/layouts/ZStack.js +1 -0
- package/lib/layouts/ZStack.ts +0 -0
- package/machinery/compiler3.js +116 -68
- package/package.json +1 -1
package/dom-structure-map.json
CHANGED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { BaseComponent } from './BaseComponent.js';
|
|
2
|
+
/**
|
|
3
|
+
* Base state interface for all form inputs
|
|
4
|
+
*/
|
|
5
|
+
export interface FormInputState extends Record<string, any> {
|
|
6
|
+
label: string;
|
|
7
|
+
required: boolean;
|
|
8
|
+
disabled: boolean;
|
|
9
|
+
name: string;
|
|
10
|
+
style: string;
|
|
11
|
+
class: string;
|
|
12
|
+
errorMessage?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Abstract base class for all form input components
|
|
16
|
+
* Extends BaseComponent with form-specific functionality
|
|
17
|
+
*/
|
|
18
|
+
export declare abstract class FormInput<TState extends FormInputState> extends BaseComponent<TState> {
|
|
19
|
+
protected _inputElement: HTMLElement | null;
|
|
20
|
+
protected _labelElement: HTMLLabelElement | null;
|
|
21
|
+
protected _errorElement: HTMLElement | null;
|
|
22
|
+
protected _onValidate?: (value: any) => boolean | string;
|
|
23
|
+
protected _hasBeenValidated: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Get the current value of the input
|
|
26
|
+
*/
|
|
27
|
+
abstract getValue(): any;
|
|
28
|
+
/**
|
|
29
|
+
* Set the value of the input
|
|
30
|
+
*/
|
|
31
|
+
abstract setValue(value: any): this;
|
|
32
|
+
/**
|
|
33
|
+
* Build the actual input element (input, select, textarea, etc.)
|
|
34
|
+
*/
|
|
35
|
+
protected abstract _buildInputElement(): HTMLElement;
|
|
36
|
+
/**
|
|
37
|
+
* Validate the current value
|
|
38
|
+
*/
|
|
39
|
+
protected abstract _validateValue(value: any): boolean | string;
|
|
40
|
+
label(value: string): this;
|
|
41
|
+
required(value: boolean): this;
|
|
42
|
+
name(value: string): this;
|
|
43
|
+
onValidate(handler: (value: any) => boolean | string): this;
|
|
44
|
+
/**
|
|
45
|
+
* Validate the current value and show/hide errors
|
|
46
|
+
*/
|
|
47
|
+
validate(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Check if current value is valid without showing errors
|
|
50
|
+
*/
|
|
51
|
+
isValid(): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Show error message
|
|
54
|
+
*/
|
|
55
|
+
protected _showError(message: string): void;
|
|
56
|
+
/**
|
|
57
|
+
* Clear error message
|
|
58
|
+
*/
|
|
59
|
+
protected _clearError(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Build label element with auto-generated text from ID if not provided
|
|
62
|
+
*/
|
|
63
|
+
protected _renderLabel(): HTMLLabelElement;
|
|
64
|
+
/**
|
|
65
|
+
* Build error element
|
|
66
|
+
*/
|
|
67
|
+
protected _renderError(): HTMLElement;
|
|
68
|
+
/**
|
|
69
|
+
* Default update implementation for form inputs
|
|
70
|
+
*/
|
|
71
|
+
update(prop: string, value: any): void;
|
|
72
|
+
/**
|
|
73
|
+
* Wire up two-way sync for value property
|
|
74
|
+
*/
|
|
75
|
+
protected _wireFormSync(inputElement: HTMLElement, eventName?: string): void;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=FormInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormInput.d.ts","sourceRoot":"","sources":["FormInput.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD;;GAEG;AACH,MAAM,WAAW,cAAe,SAAQ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,8BAAsB,SAAS,CAAC,MAAM,SAAS,cAAc,CAAE,SAAQ,aAAa,CAAC,MAAM,CAAC;IACxF,SAAS,CAAC,aAAa,EAAE,WAAW,GAAG,IAAI,CAAQ;IACnD,SAAS,CAAC,aAAa,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IACxD,SAAS,CAAC,aAAa,EAAE,WAAW,GAAG,IAAI,CAAQ;IACnD,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,GAAG,MAAM,CAAC;IACzD,SAAS,CAAC,iBAAiB,EAAE,OAAO,CAAS;IAM7C;;OAEG;IACH,QAAQ,CAAC,QAAQ,IAAI,GAAG;IAExB;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI;IAEnC;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,kBAAkB,IAAI,WAAW;IAEpD;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,GAAG,MAAM;IAM/D,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ1B,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAK9B,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKzB,UAAU,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,GAAG,MAAM,GAAG,IAAI;IAS3D;;OAEG;IACH,QAAQ,IAAI,OAAO;IAcnB;;OAEG;IACH,OAAO,IAAI,OAAO;IAKlB;;OAEG;IACH,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAa3C;;OAEG;IACH,SAAS,CAAC,WAAW,IAAI,IAAI;IAiB7B;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,gBAAgB;IAqB1C;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,WAAW;IAUrC;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAItC;;OAEG;IACH,SAAS,CAAC,aAAa,CAAC,YAAY,EAAE,WAAW,EAAE,SAAS,GAAE,MAAgB,GAAG,IAAI;CA8CxF"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { BaseComponent } from './BaseComponent.js';
|
|
2
|
+
import { formatIdAsLabel } from '../../utils/formatId.js'; // ✅ Import utility
|
|
3
|
+
/**
|
|
4
|
+
* Abstract base class for all form input components
|
|
5
|
+
* Extends BaseComponent with form-specific functionality
|
|
6
|
+
*/
|
|
7
|
+
export class FormInput extends BaseComponent {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
this._inputElement = null;
|
|
11
|
+
this._labelElement = null;
|
|
12
|
+
this._errorElement = null;
|
|
13
|
+
this._hasBeenValidated = false; // NEW: Track if user has submitted/validated
|
|
14
|
+
}
|
|
15
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
16
|
+
* COMMON FORM INPUT API
|
|
17
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
18
|
+
label(value) {
|
|
19
|
+
this.state.label = value;
|
|
20
|
+
if (this._labelElement) {
|
|
21
|
+
this._labelElement.textContent = value;
|
|
22
|
+
}
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
required(value) {
|
|
26
|
+
this.state.required = value;
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
name(value) {
|
|
30
|
+
this.state.name = value;
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
onValidate(handler) {
|
|
34
|
+
this._onValidate = handler;
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
38
|
+
* VALIDATION
|
|
39
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
40
|
+
/**
|
|
41
|
+
* Validate the current value and show/hide errors
|
|
42
|
+
*/
|
|
43
|
+
validate() {
|
|
44
|
+
this._hasBeenValidated = true; // Mark as validated
|
|
45
|
+
const value = this.getValue();
|
|
46
|
+
const result = this._validateValue(value);
|
|
47
|
+
if (result === true) {
|
|
48
|
+
this._clearError();
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
this._showError(result);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if current value is valid without showing errors
|
|
58
|
+
*/
|
|
59
|
+
isValid() {
|
|
60
|
+
const value = this.getValue();
|
|
61
|
+
return this._validateValue(value) === true;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Show error message
|
|
65
|
+
*/
|
|
66
|
+
_showError(message) {
|
|
67
|
+
if (this._errorElement) {
|
|
68
|
+
this._errorElement.textContent = message;
|
|
69
|
+
this._errorElement.style.display = 'block';
|
|
70
|
+
}
|
|
71
|
+
if (this._inputElement) {
|
|
72
|
+
this._inputElement.classList.add('jux-input-invalid');
|
|
73
|
+
}
|
|
74
|
+
this.state.errorMessage = message;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Clear error message
|
|
78
|
+
*/
|
|
79
|
+
_clearError() {
|
|
80
|
+
if (this._errorElement) {
|
|
81
|
+
this._errorElement.textContent = '';
|
|
82
|
+
this._errorElement.style.display = 'none';
|
|
83
|
+
}
|
|
84
|
+
if (this._inputElement) {
|
|
85
|
+
this._inputElement.classList.remove('jux-input-invalid');
|
|
86
|
+
}
|
|
87
|
+
this.state.errorMessage = undefined;
|
|
88
|
+
}
|
|
89
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
90
|
+
* COMMON RENDER HELPERS
|
|
91
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
92
|
+
/**
|
|
93
|
+
* Build label element with auto-generated text from ID if not provided
|
|
94
|
+
*/
|
|
95
|
+
_renderLabel() {
|
|
96
|
+
const { label, required } = this.state;
|
|
97
|
+
const labelEl = document.createElement('label');
|
|
98
|
+
labelEl.className = 'jux-input-label';
|
|
99
|
+
labelEl.htmlFor = `${this._id}-input`;
|
|
100
|
+
// ✅ Use provided label or auto-generate from ID
|
|
101
|
+
labelEl.textContent = label || formatIdAsLabel(this._id);
|
|
102
|
+
if (required) {
|
|
103
|
+
const requiredSpan = document.createElement('span');
|
|
104
|
+
requiredSpan.className = 'jux-input-required';
|
|
105
|
+
requiredSpan.textContent = ' *';
|
|
106
|
+
labelEl.appendChild(requiredSpan);
|
|
107
|
+
}
|
|
108
|
+
this._labelElement = labelEl;
|
|
109
|
+
return labelEl;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Build error element
|
|
113
|
+
*/
|
|
114
|
+
_renderError() {
|
|
115
|
+
const errorEl = document.createElement('div');
|
|
116
|
+
errorEl.className = 'jux-input-error';
|
|
117
|
+
errorEl.id = `${this._id}-error`;
|
|
118
|
+
errorEl.style.display = 'none';
|
|
119
|
+
this._errorElement = errorEl;
|
|
120
|
+
return errorEl;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Default update implementation for form inputs
|
|
124
|
+
*/
|
|
125
|
+
update(prop, value) {
|
|
126
|
+
// No reactive updates needed - form inputs handle their own state
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Wire up two-way sync for value property
|
|
130
|
+
*/
|
|
131
|
+
_wireFormSync(inputElement, eventName = 'input') {
|
|
132
|
+
const valueSync = this._syncBindings.find(b => b.property === 'value');
|
|
133
|
+
if (valueSync) {
|
|
134
|
+
const { stateObj, toState, toComponent } = valueSync;
|
|
135
|
+
// Default transforms
|
|
136
|
+
const transformToState = toState || ((v) => v);
|
|
137
|
+
const transformToComponent = toComponent || ((v) => v);
|
|
138
|
+
let isUpdating = false;
|
|
139
|
+
// State → Component
|
|
140
|
+
stateObj.subscribe((val) => {
|
|
141
|
+
if (isUpdating)
|
|
142
|
+
return;
|
|
143
|
+
const transformed = transformToComponent(val);
|
|
144
|
+
this.setValue(transformed);
|
|
145
|
+
});
|
|
146
|
+
// Component → State
|
|
147
|
+
inputElement.addEventListener(eventName, () => {
|
|
148
|
+
if (isUpdating)
|
|
149
|
+
return;
|
|
150
|
+
isUpdating = true;
|
|
151
|
+
const value = this.getValue();
|
|
152
|
+
const transformed = transformToState(value);
|
|
153
|
+
this._clearError();
|
|
154
|
+
stateObj.set(transformed);
|
|
155
|
+
setTimeout(() => { isUpdating = false; }, 0);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// Default behavior without sync
|
|
160
|
+
inputElement.addEventListener(eventName, () => {
|
|
161
|
+
this._clearError();
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
// Only validate on blur IF the field has been validated before (e.g., after submit)
|
|
165
|
+
inputElement.addEventListener('blur', () => {
|
|
166
|
+
if (this._hasBeenValidated) {
|
|
167
|
+
this.validate();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { BaseComponent } from './BaseComponent.js';
|
|
2
|
+
import { formatIdAsLabel } from '../../utils/formatId.js'; // ✅ Import utility
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base state interface for all form inputs
|
|
6
|
+
*/
|
|
7
|
+
export interface FormInputState extends Record<string, any> {
|
|
8
|
+
label: string;
|
|
9
|
+
required: boolean;
|
|
10
|
+
disabled: boolean;
|
|
11
|
+
name: string;
|
|
12
|
+
style: string;
|
|
13
|
+
class: string;
|
|
14
|
+
errorMessage?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Abstract base class for all form input components
|
|
19
|
+
* Extends BaseComponent with form-specific functionality
|
|
20
|
+
*/
|
|
21
|
+
export abstract class FormInput<TState extends FormInputState> extends BaseComponent<TState> {
|
|
22
|
+
protected _inputElement: HTMLElement | null = null;
|
|
23
|
+
protected _labelElement: HTMLLabelElement | null = null;
|
|
24
|
+
protected _errorElement: HTMLElement | null = null;
|
|
25
|
+
protected _onValidate?: (value: any) => boolean | string;
|
|
26
|
+
protected _hasBeenValidated: boolean = false; // NEW: Track if user has submitted/validated
|
|
27
|
+
|
|
28
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
29
|
+
* ABSTRACT METHODS (Child must implement)
|
|
30
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get the current value of the input
|
|
34
|
+
*/
|
|
35
|
+
abstract getValue(): any;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Set the value of the input
|
|
39
|
+
*/
|
|
40
|
+
abstract setValue(value: any): this;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Build the actual input element (input, select, textarea, etc.)
|
|
44
|
+
*/
|
|
45
|
+
protected abstract _buildInputElement(): HTMLElement;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validate the current value
|
|
49
|
+
*/
|
|
50
|
+
protected abstract _validateValue(value: any): boolean | string;
|
|
51
|
+
|
|
52
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
53
|
+
* COMMON FORM INPUT API
|
|
54
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
55
|
+
|
|
56
|
+
label(value: string): this {
|
|
57
|
+
this.state.label = value;
|
|
58
|
+
if (this._labelElement) {
|
|
59
|
+
this._labelElement.textContent = value;
|
|
60
|
+
}
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
required(value: boolean): this {
|
|
65
|
+
this.state.required = value;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
name(value: string): this {
|
|
70
|
+
this.state.name = value;
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
onValidate(handler: (value: any) => boolean | string): this {
|
|
75
|
+
this._onValidate = handler;
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
80
|
+
* VALIDATION
|
|
81
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Validate the current value and show/hide errors
|
|
85
|
+
*/
|
|
86
|
+
validate(): boolean {
|
|
87
|
+
this._hasBeenValidated = true; // Mark as validated
|
|
88
|
+
const value = this.getValue();
|
|
89
|
+
const result = this._validateValue(value);
|
|
90
|
+
|
|
91
|
+
if (result === true) {
|
|
92
|
+
this._clearError();
|
|
93
|
+
return true;
|
|
94
|
+
} else {
|
|
95
|
+
this._showError(result as string);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if current value is valid without showing errors
|
|
102
|
+
*/
|
|
103
|
+
isValid(): boolean {
|
|
104
|
+
const value = this.getValue();
|
|
105
|
+
return this._validateValue(value) === true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Show error message
|
|
110
|
+
*/
|
|
111
|
+
protected _showError(message: string): void {
|
|
112
|
+
if (this._errorElement) {
|
|
113
|
+
this._errorElement.textContent = message;
|
|
114
|
+
this._errorElement.style.display = 'block';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (this._inputElement) {
|
|
118
|
+
this._inputElement.classList.add('jux-input-invalid');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.state.errorMessage = message;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Clear error message
|
|
126
|
+
*/
|
|
127
|
+
protected _clearError(): void {
|
|
128
|
+
if (this._errorElement) {
|
|
129
|
+
this._errorElement.textContent = '';
|
|
130
|
+
this._errorElement.style.display = 'none';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (this._inputElement) {
|
|
134
|
+
this._inputElement.classList.remove('jux-input-invalid');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.state.errorMessage = undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
141
|
+
* COMMON RENDER HELPERS
|
|
142
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Build label element with auto-generated text from ID if not provided
|
|
146
|
+
*/
|
|
147
|
+
protected _renderLabel(): HTMLLabelElement {
|
|
148
|
+
const { label, required } = this.state;
|
|
149
|
+
|
|
150
|
+
const labelEl = document.createElement('label');
|
|
151
|
+
labelEl.className = 'jux-input-label';
|
|
152
|
+
labelEl.htmlFor = `${this._id}-input`;
|
|
153
|
+
|
|
154
|
+
// ✅ Use provided label or auto-generate from ID
|
|
155
|
+
labelEl.textContent = label || formatIdAsLabel(this._id);
|
|
156
|
+
|
|
157
|
+
if (required) {
|
|
158
|
+
const requiredSpan = document.createElement('span');
|
|
159
|
+
requiredSpan.className = 'jux-input-required';
|
|
160
|
+
requiredSpan.textContent = ' *';
|
|
161
|
+
labelEl.appendChild(requiredSpan);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this._labelElement = labelEl;
|
|
165
|
+
return labelEl;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Build error element
|
|
170
|
+
*/
|
|
171
|
+
protected _renderError(): HTMLElement {
|
|
172
|
+
const errorEl = document.createElement('div');
|
|
173
|
+
errorEl.className = 'jux-input-error';
|
|
174
|
+
errorEl.id = `${this._id}-error`;
|
|
175
|
+
errorEl.style.display = 'none';
|
|
176
|
+
|
|
177
|
+
this._errorElement = errorEl;
|
|
178
|
+
return errorEl;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Default update implementation for form inputs
|
|
183
|
+
*/
|
|
184
|
+
update(prop: string, value: any): void {
|
|
185
|
+
// No reactive updates needed - form inputs handle their own state
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Wire up two-way sync for value property
|
|
190
|
+
*/
|
|
191
|
+
protected _wireFormSync(inputElement: HTMLElement, eventName: string = 'input'): void {
|
|
192
|
+
const valueSync = this._syncBindings.find(b => b.property === 'value');
|
|
193
|
+
|
|
194
|
+
if (valueSync) {
|
|
195
|
+
const { stateObj, toState, toComponent } = valueSync;
|
|
196
|
+
|
|
197
|
+
// Default transforms
|
|
198
|
+
const transformToState = toState || ((v: any) => v);
|
|
199
|
+
const transformToComponent = toComponent || ((v: any) => v);
|
|
200
|
+
|
|
201
|
+
let isUpdating = false;
|
|
202
|
+
|
|
203
|
+
// State → Component
|
|
204
|
+
stateObj.subscribe((val: any) => {
|
|
205
|
+
if (isUpdating) return;
|
|
206
|
+
const transformed = transformToComponent(val);
|
|
207
|
+
this.setValue(transformed);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Component → State
|
|
211
|
+
inputElement.addEventListener(eventName, () => {
|
|
212
|
+
if (isUpdating) return;
|
|
213
|
+
isUpdating = true;
|
|
214
|
+
|
|
215
|
+
const value = this.getValue();
|
|
216
|
+
const transformed = transformToState(value);
|
|
217
|
+
this._clearError();
|
|
218
|
+
|
|
219
|
+
stateObj.set(transformed);
|
|
220
|
+
|
|
221
|
+
setTimeout(() => { isUpdating = false; }, 0);
|
|
222
|
+
});
|
|
223
|
+
} else {
|
|
224
|
+
// Default behavior without sync
|
|
225
|
+
inputElement.addEventListener(eventName, () => {
|
|
226
|
+
this._clearError();
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Only validate on blur IF the field has been validated before (e.g., after submit)
|
|
231
|
+
inputElement.addEventListener('blur', () => {
|
|
232
|
+
if (this._hasBeenValidated) {
|
|
233
|
+
this.validate();
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -26,6 +26,7 @@ interface StackState extends BaseState {
|
|
|
26
26
|
export declare abstract class BaseStack extends BaseComponent<StackState> {
|
|
27
27
|
protected abstract baseClassName: string;
|
|
28
28
|
private _inlineStyles;
|
|
29
|
+
_container: HTMLElement | null;
|
|
29
30
|
constructor(id: string, children: Record<string, any> | any[], options?: StackOptions);
|
|
30
31
|
protected getTriggerEvents(): readonly string[];
|
|
31
32
|
protected getCallbackEvents(): readonly string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseStack.d.ts","sourceRoot":"","sources":["BaseStack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAMpE,MAAM,WAAW,YAAY;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;IACjD,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IAC/B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B;AAED,UAAU,UAAW,SAAQ,SAAS;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACrB,WAAW,CAAC,EAAE,kBAAkB,GAAG,MAAM,EAAE,CAAC;CAC/C;AAED;;GAEG;AACH,8BAAsB,SAAU,SAAQ,aAAa,CAAC,UAAU,CAAC;IAC7D,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IACzC,OAAO,CAAC,aAAa,CAAkC;
|
|
1
|
+
{"version":3,"file":"BaseStack.d.ts","sourceRoot":"","sources":["BaseStack.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,6 +26,7 @@ export class BaseStack extends BaseComponent {
|
|
|
26
26
|
childStyles: undefined
|
|
27
27
|
});
|
|
28
28
|
this._inlineStyles = new Map();
|
|
29
|
+
this._container = null; // ✅ Make public for nesting
|
|
29
30
|
}
|
|
30
31
|
getTriggerEvents() {
|
|
31
32
|
return TRIGGER_EVENTS;
|
|
@@ -184,22 +185,24 @@ export class BaseStack extends BaseComponent {
|
|
|
184
185
|
}
|
|
185
186
|
renderChild(child, index) {
|
|
186
187
|
let childElement;
|
|
187
|
-
//
|
|
188
|
-
if (typeof child === '
|
|
189
|
-
childElement =
|
|
190
|
-
childElement.textContent = child;
|
|
188
|
+
// ✅ Priority 1: Check if it's a Stack (has _container property)
|
|
189
|
+
if (child && typeof child === 'object' && '_container' in child && child._container instanceof HTMLElement) {
|
|
190
|
+
childElement = child._container;
|
|
191
191
|
}
|
|
192
|
-
// JUX Component
|
|
193
|
-
else if (child && typeof child
|
|
194
|
-
|
|
195
|
-
child.render(tempContainer);
|
|
196
|
-
childElement = child.container || tempContainer.firstElementChild;
|
|
192
|
+
// ✅ Priority 2: Check if it's a JUX Component (has container property)
|
|
193
|
+
else if (child && typeof child === 'object' && 'container' in child && child.container instanceof HTMLElement) {
|
|
194
|
+
childElement = child.container;
|
|
197
195
|
}
|
|
198
|
-
//
|
|
196
|
+
// ✅ Priority 3: Check if it's already a DOM element
|
|
199
197
|
else if (child instanceof HTMLElement) {
|
|
200
198
|
childElement = child;
|
|
201
199
|
}
|
|
202
|
-
//
|
|
200
|
+
// ✅ Priority 4: String - wrap in div
|
|
201
|
+
else if (typeof child === 'string') {
|
|
202
|
+
childElement = document.createElement('div');
|
|
203
|
+
childElement.innerHTML = child;
|
|
204
|
+
}
|
|
205
|
+
// ✅ Fallback - stringify
|
|
203
206
|
else {
|
|
204
207
|
childElement = document.createElement('div');
|
|
205
208
|
childElement.textContent = String(child);
|
|
@@ -241,6 +244,8 @@ export class BaseStack extends BaseComponent {
|
|
|
241
244
|
wrapper.appendChild(childEl);
|
|
242
245
|
});
|
|
243
246
|
this._wireStandardEvents(wrapper);
|
|
247
|
+
// ✅ Store wrapper as _container for nesting
|
|
248
|
+
this._container = wrapper;
|
|
244
249
|
container.appendChild(wrapper);
|
|
245
250
|
return this;
|
|
246
251
|
}
|
|
@@ -34,6 +34,7 @@ interface StackState extends BaseState {
|
|
|
34
34
|
export abstract class BaseStack extends BaseComponent<StackState> {
|
|
35
35
|
protected abstract baseClassName: string;
|
|
36
36
|
private _inlineStyles: Map<string, string> = new Map();
|
|
37
|
+
public _container: HTMLElement | null = null; // ✅ Make public for nesting
|
|
37
38
|
|
|
38
39
|
constructor(id: string, children: Record<string, any> | any[], options: StackOptions = {}) {
|
|
39
40
|
const childArray = Array.isArray(children)
|
|
@@ -244,22 +245,24 @@ export abstract class BaseStack extends BaseComponent<StackState> {
|
|
|
244
245
|
protected renderChild(child: any, index: number): HTMLElement {
|
|
245
246
|
let childElement: HTMLElement;
|
|
246
247
|
|
|
247
|
-
//
|
|
248
|
-
if (typeof child === '
|
|
249
|
-
childElement =
|
|
250
|
-
childElement.textContent = child;
|
|
248
|
+
// ✅ Priority 1: Check if it's a Stack (has _container property)
|
|
249
|
+
if (child && typeof child === 'object' && '_container' in child && child._container instanceof HTMLElement) {
|
|
250
|
+
childElement = child._container;
|
|
251
251
|
}
|
|
252
|
-
// JUX Component
|
|
253
|
-
else if (child && typeof child
|
|
254
|
-
|
|
255
|
-
child.render(tempContainer);
|
|
256
|
-
childElement = child.container || tempContainer.firstElementChild as HTMLElement;
|
|
252
|
+
// ✅ Priority 2: Check if it's a JUX Component (has container property)
|
|
253
|
+
else if (child && typeof child === 'object' && 'container' in child && child.container instanceof HTMLElement) {
|
|
254
|
+
childElement = child.container;
|
|
257
255
|
}
|
|
258
|
-
//
|
|
256
|
+
// ✅ Priority 3: Check if it's already a DOM element
|
|
259
257
|
else if (child instanceof HTMLElement) {
|
|
260
258
|
childElement = child;
|
|
261
259
|
}
|
|
262
|
-
//
|
|
260
|
+
// ✅ Priority 4: String - wrap in div
|
|
261
|
+
else if (typeof child === 'string') {
|
|
262
|
+
childElement = document.createElement('div');
|
|
263
|
+
childElement.innerHTML = child;
|
|
264
|
+
}
|
|
265
|
+
// ✅ Fallback - stringify
|
|
263
266
|
else {
|
|
264
267
|
childElement = document.createElement('div');
|
|
265
268
|
childElement.textContent = String(child);
|
|
@@ -313,6 +316,10 @@ export abstract class BaseStack extends BaseComponent<StackState> {
|
|
|
313
316
|
});
|
|
314
317
|
|
|
315
318
|
this._wireStandardEvents(wrapper);
|
|
319
|
+
|
|
320
|
+
// ✅ Store wrapper as _container for nesting
|
|
321
|
+
this._container = wrapper;
|
|
322
|
+
|
|
316
323
|
container.appendChild(wrapper);
|
|
317
324
|
|
|
318
325
|
return this;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HStack.d.ts","sourceRoot":"","sources":["HStack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEzD,qBAAa,MAAO,SAAQ,SAAS;IACjC,SAAS,CAAC,aAAa,SAAgB;gBAE3B,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,OAAO,GAAE,YAAiB;IAIzF,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM;
|
|
1
|
+
{"version":3,"file":"HStack.d.ts","sourceRoot":"","sources":["HStack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEzD,qBAAa,MAAO,SAAQ,SAAS;IACjC,SAAS,CAAC,aAAa,SAAgB;gBAE3B,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,OAAO,GAAE,YAAiB;IAIzF,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM;CAa3F;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAE5G"}
|
|
@@ -7,8 +7,9 @@ export class HStack extends BaseStack {
|
|
|
7
7
|
static create(children, options = {}) {
|
|
8
8
|
const id = `hstack-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
9
9
|
const instance = new HStack(id, children, options);
|
|
10
|
+
// ✅ Auto-render to 'app' on next tick
|
|
10
11
|
queueMicrotask(() => {
|
|
11
|
-
if (!instance.
|
|
12
|
+
if (!instance._container) {
|
|
12
13
|
instance.render('app');
|
|
13
14
|
}
|
|
14
15
|
});
|
|
@@ -11,8 +11,9 @@ export class HStack extends BaseStack {
|
|
|
11
11
|
const id = `hstack-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
12
12
|
const instance = new HStack(id, children, options);
|
|
13
13
|
|
|
14
|
+
// ✅ Auto-render to 'app' on next tick
|
|
14
15
|
queueMicrotask(() => {
|
|
15
|
-
if (!instance.
|
|
16
|
+
if (!instance._container) {
|
|
16
17
|
instance.render('app');
|
|
17
18
|
}
|
|
18
19
|
});
|
|
@@ -10,9 +10,9 @@ export class VStack extends BaseStack {
|
|
|
10
10
|
static create(children, options = {}) {
|
|
11
11
|
const id = `vstack-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
12
12
|
const instance = new VStack(id, children, options);
|
|
13
|
-
// ✅ Auto-render
|
|
13
|
+
// ✅ Auto-render to 'app' on next tick
|
|
14
14
|
queueMicrotask(() => {
|
|
15
|
-
if (!instance.
|
|
15
|
+
if (!instance._container) {
|
|
16
16
|
instance.render('app');
|
|
17
17
|
}
|
|
18
18
|
});
|
|
@@ -14,9 +14,9 @@ export class VStack extends BaseStack {
|
|
|
14
14
|
const id = `vstack-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
15
15
|
const instance = new VStack(id, children, options);
|
|
16
16
|
|
|
17
|
-
// ✅ Auto-render
|
|
17
|
+
// ✅ Auto-render to 'app' on next tick
|
|
18
18
|
queueMicrotask(() => {
|
|
19
|
-
if (!instance.
|
|
19
|
+
if (!instance._container) {
|
|
20
20
|
instance.render('app');
|
|
21
21
|
}
|
|
22
22
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ZStack.d.ts","sourceRoot":"","sources":["ZStack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEzD,qBAAa,MAAO,SAAQ,SAAS;IACjC,SAAS,CAAC,aAAa,SAAgB;gBAE3B,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,OAAO,GAAE,YAAiB;IAIzF,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM;
|
|
1
|
+
{"version":3,"file":"ZStack.d.ts","sourceRoot":"","sources":["ZStack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEzD,qBAAa,MAAO,SAAQ,SAAS;IACjC,SAAS,CAAC,aAAa,SAAgB;gBAE3B,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,OAAO,GAAE,YAAiB;IAIzF,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM;CAa3F;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAE5G"}
|
|
@@ -7,8 +7,9 @@ export class ZStack extends BaseStack {
|
|
|
7
7
|
static create(children, options = {}) {
|
|
8
8
|
const id = `zstack-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
9
9
|
const instance = new ZStack(id, children, options);
|
|
10
|
+
// ✅ Auto-render to 'app' on next tick
|
|
10
11
|
queueMicrotask(() => {
|
|
11
|
-
if (!instance.
|
|
12
|
+
if (!instance._container) {
|
|
12
13
|
instance.render('app');
|
|
13
14
|
}
|
|
14
15
|
});
|
|
@@ -11,8 +11,9 @@ export class ZStack extends BaseStack {
|
|
|
11
11
|
const id = `zstack-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
12
12
|
const instance = new ZStack(id, children, options);
|
|
13
13
|
|
|
14
|
+
// ✅ Auto-render to 'app' on next tick
|
|
14
15
|
queueMicrotask(() => {
|
|
15
|
-
if (!instance.
|
|
16
|
+
if (!instance._container) {
|
|
16
17
|
instance.render('app');
|
|
17
18
|
}
|
|
18
19
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=VStack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VStack.d.ts","sourceRoot":"","sources":["VStack.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=ZStack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ZStack.d.ts","sourceRoot":"","sources":["ZStack.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
File without changes
|
package/machinery/compiler3.js
CHANGED
|
@@ -250,18 +250,56 @@ export class JuxCompiler {
|
|
|
250
250
|
const sourceSnapshot = {};
|
|
251
251
|
|
|
252
252
|
const juxImports = new Set();
|
|
253
|
+
const layoutImports = new Set();
|
|
254
|
+
let needsJuxFacade = false; // ✅ Track if jux facade is needed
|
|
255
|
+
|
|
253
256
|
[...views, ...dataModules, ...sharedModules].forEach(m => {
|
|
257
|
+
// ✅ Detect jux.something() usage
|
|
258
|
+
if (/jux\s*\.\s*\w+\s*\(/g.test(m.content)) {
|
|
259
|
+
needsJuxFacade = true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Detect layout usage (both import and direct usage)
|
|
263
|
+
const layoutPattern = /(VStack|HStack|ZStack)\s*\.\s*create/g;
|
|
264
|
+
const layoutMatches = m.content.match(layoutPattern);
|
|
265
|
+
if (layoutMatches) {
|
|
266
|
+
layoutMatches.forEach(match => {
|
|
267
|
+
const layoutName = match.split('.')[0].trim();
|
|
268
|
+
layoutImports.add(layoutName);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Regular juxscript imports
|
|
254
273
|
for (const match of m.content.matchAll(/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]juxscript['"]/g)) {
|
|
255
274
|
match[1].split(',').map(s => s.trim()).forEach(imp => {
|
|
256
|
-
if (imp)
|
|
275
|
+
if (imp) {
|
|
276
|
+
if (imp === 'VStack' || imp === 'HStack' || imp === 'ZStack') {
|
|
277
|
+
layoutImports.add(imp);
|
|
278
|
+
} else {
|
|
279
|
+
juxImports.add(imp);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
257
282
|
});
|
|
258
283
|
}
|
|
259
284
|
});
|
|
260
285
|
|
|
286
|
+
// ✅ Import layouts
|
|
287
|
+
if (layoutImports.size > 0) {
|
|
288
|
+
entry += `import { ${[...layoutImports].sort().join(', ')} } from 'juxscript';\n`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ✅ Import regular components
|
|
261
292
|
if (juxImports.size > 0) {
|
|
262
293
|
entry += `import { ${[...juxImports].sort().join(', ')} } from 'juxscript';\n\n`;
|
|
263
294
|
}
|
|
264
295
|
|
|
296
|
+
// ✅ Import jux facade if needed
|
|
297
|
+
if (needsJuxFacade) {
|
|
298
|
+
entry += `import { jux } from 'juxscript';\n`;
|
|
299
|
+
entry += `window.jux = jux;\n`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Data and shared modules
|
|
265
303
|
dataModules.forEach(m => {
|
|
266
304
|
entry += `import * as ${this.sanitizeName(m.name)}Data from './jux/${m.file}';\n`;
|
|
267
305
|
});
|
|
@@ -273,8 +311,18 @@ export class JuxCompiler {
|
|
|
273
311
|
dataModules.forEach(m => entry += `Object.assign(window, ${this.sanitizeName(m.name)}Data);\n`);
|
|
274
312
|
sharedModules.forEach(m => entry += `Object.assign(window, ${this.sanitizeName(m.name)}Shared);\n`);
|
|
275
313
|
|
|
314
|
+
// ✅ Expose layouts to window (critical for nested usage)
|
|
315
|
+
if (layoutImports.size > 0) {
|
|
316
|
+
entry += `\n// Expose layout components\n`;
|
|
317
|
+
layoutImports.forEach(layout => {
|
|
318
|
+
entry += `window.${layout} = ${layout};\n`;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ✅ Expose regular components
|
|
276
323
|
if (juxImports.size > 0) {
|
|
277
|
-
entry += `\
|
|
324
|
+
entry += `\n// Expose components\n`;
|
|
325
|
+
entry += `Object.assign(window, { ${[...juxImports].join(', ')} });\n`;
|
|
278
326
|
}
|
|
279
327
|
|
|
280
328
|
entry += `\n// --- VIEW FUNCTIONS ---\n`;
|
|
@@ -582,52 +630,43 @@ navigate(location.pathname);
|
|
|
582
630
|
target: 'es2020',
|
|
583
631
|
sourcemap: true,
|
|
584
632
|
external: [],
|
|
585
|
-
// ✅ Tell esbuild how to resolve .jux
|
|
586
|
-
loader: {
|
|
633
|
+
// ✅ Tell esbuild how to resolve .jux filesharset="UTF-8">
|
|
634
|
+
loader: { <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
587
635
|
'.jux': 'js' // Treat .jux files as JavaScript
|
|
588
|
-
}
|
|
636
|
+
} <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/juxscript@latest/lib/layouts/default.css">
|
|
589
637
|
});
|
|
590
638
|
|
|
591
|
-
// Generate index.html
|
|
639
|
+
// Generate index.html <div id="app"></div>
|
|
592
640
|
console.log('📄 Generating index.html...');
|
|
593
641
|
const indexHtml = `<!DOCTYPE html>
|
|
594
642
|
<html lang="en">
|
|
595
|
-
<head>
|
|
596
|
-
<meta charset="UTF-8">
|
|
597
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
598
|
-
<title>JUX App</title>
|
|
599
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/juxscript@latest/lib/layouts/default.css">
|
|
600
|
-
</head>
|
|
601
|
-
<body>
|
|
602
|
-
<div id="app"></div>
|
|
603
|
-
<script type="module" src="/bundle.js"></script>
|
|
604
643
|
</body>
|
|
605
|
-
</html>`;
|
|
644
|
+
</html>`;this.distDir, 'index.html'), indexHtml);
|
|
606
645
|
|
|
607
646
|
fs.writeFileSync(path.join(this.distDir, 'index.html'), indexHtml);
|
|
608
|
-
|
|
609
|
-
// Copy public folder
|
|
610
647
|
this.copyPublicFolder();
|
|
611
|
-
|
|
612
|
-
// Write source snapshot for error overlay
|
|
648
|
+
// Copy public folder
|
|
649
|
+
this.copyPublicFolder(); // Write source snapshot for error overlay
|
|
650
|
+
apshotPath = path.join(this.distDir, '__jux_sources.json');
|
|
651
|
+
// Write source snapshot for error overlaynapshotPath, JSON.stringify(this._sourceSnapshot, null, 2));
|
|
613
652
|
const snapshotPath = path.join(this.distDir, '__jux_sources.json');
|
|
614
|
-
fs.writeFileSync(snapshotPath, JSON.stringify(this._sourceSnapshot, null, 2));
|
|
615
|
-
|
|
616
|
-
const endTime = Date.now();
|
|
653
|
+
fs.writeFileSync(snapshotPath, JSON.stringify(this._sourceSnapshot, null, 2)););
|
|
654
|
+
nsole.log(`\n✅ Build complete in ${endTime - startTime}ms`);
|
|
655
|
+
const endTime = Date.now(); console.log(` Output: ${this.distDir}`);
|
|
617
656
|
console.log(`\n✅ Build complete in ${endTime - startTime}ms`);
|
|
618
657
|
console.log(` Output: ${this.distDir}`);
|
|
619
658
|
|
|
620
659
|
this.reportValidationIssues();
|
|
621
660
|
|
|
622
|
-
return {
|
|
623
|
-
success: true,
|
|
624
|
-
errors: this._validationIssues || [],
|
|
661
|
+
return {ionIssues || [],
|
|
662
|
+
success: true, distDir: this.distDir
|
|
663
|
+
errors: this._validationIssues || [], };
|
|
625
664
|
distDir: this.distDir
|
|
626
|
-
};
|
|
627
|
-
|
|
665
|
+
}; } catch (err) {
|
|
666
|
+
console.error('❌ Build failed:', err.message);
|
|
628
667
|
} catch (err) {
|
|
629
|
-
console.error('❌ Build failed:', err.message);
|
|
630
|
-
console.error(err.stack);
|
|
668
|
+
console.error('❌ Build failed:', err.message); return {
|
|
669
|
+
console.error(err.stack); false,
|
|
631
670
|
return {
|
|
632
671
|
success: false,
|
|
633
672
|
errors: [err.message]
|
|
@@ -636,77 +675,86 @@ navigate(location.pathname);
|
|
|
636
675
|
}
|
|
637
676
|
|
|
638
677
|
/**
|
|
639
|
-
* Copy public folder contents to dist
|
|
640
|
-
*/
|
|
641
|
-
copyPublicFolder() {
|
|
642
|
-
// ✅ Use configured public path or resolve from paths object
|
|
678
|
+
* Copy public folder contents to dist copyPublicFolder() {
|
|
679
|
+
*/from paths object
|
|
680
|
+
copyPublicFolder() { const publicSrc = this.paths.public
|
|
681
|
+
// ✅ Use configured public path or resolve from paths object this.paths.public
|
|
643
682
|
const publicSrc = this.paths.public
|
|
644
683
|
? this.paths.public
|
|
645
|
-
: path.resolve(process.cwd(), this.publicDir);
|
|
684
|
+
: path.resolve(process.cwd(), this.publicDir);ync(publicSrc)) {
|
|
646
685
|
|
|
647
686
|
if (!fs.existsSync(publicSrc)) {
|
|
648
687
|
return; // No public folder, skip
|
|
649
|
-
}
|
|
688
|
+
} console.log('📦 Copying public assets...');
|
|
650
689
|
|
|
651
690
|
console.log('📦 Copying public assets...');
|
|
652
|
-
|
|
653
|
-
try {
|
|
691
|
+
this._copyDirRecursive(publicSrc, this.distDir, 0);
|
|
692
|
+
try {ed');
|
|
654
693
|
this._copyDirRecursive(publicSrc, this.distDir, 0);
|
|
655
|
-
console.log('✅ Public assets copied');
|
|
694
|
+
console.log('✅ Public assets copied'); console.warn('⚠️ Error copying public folder:', err.message);
|
|
656
695
|
} catch (err) {
|
|
657
696
|
console.warn('⚠️ Error copying public folder:', err.message);
|
|
658
697
|
}
|
|
659
|
-
}
|
|
698
|
+
} /**
|
|
660
699
|
|
|
661
700
|
/**
|
|
662
|
-
* Recursively copy directory contents
|
|
663
|
-
*/
|
|
701
|
+
* Recursively copy directory contents _copyDirRecursive(src, dest, depth = 0) {
|
|
702
|
+
*/(src, { withFileTypes: true });
|
|
664
703
|
_copyDirRecursive(src, dest, depth = 0) {
|
|
665
704
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
666
705
|
|
|
667
|
-
entries.forEach(entry => {
|
|
706
|
+
entries.forEach(entry => {ntry.name.startsWith('.')) return;
|
|
668
707
|
// Skip hidden files and directories
|
|
669
|
-
if (entry.name.startsWith('.')) return;
|
|
670
|
-
|
|
708
|
+
if (entry.name.startsWith('.')) return;cPath = path.join(src, entry.name);
|
|
709
|
+
path.join(dest, entry.name);
|
|
671
710
|
const srcPath = path.join(src, entry.name);
|
|
672
|
-
const destPath = path.join(dest, entry.name);
|
|
711
|
+
const destPath = path.join(dest, entry.name); if (entry.isDirectory()) {
|
|
673
712
|
|
|
674
|
-
if (entry.isDirectory()) {
|
|
675
|
-
// Create directory and
|
|
713
|
+
if (entry.isDirectory()) {(destPath)) {
|
|
714
|
+
// Create directory and recurserue });
|
|
676
715
|
if (!fs.existsSync(destPath)) {
|
|
677
|
-
fs.mkdirSync(destPath, { recursive: true });
|
|
678
|
-
}
|
|
679
|
-
this._copyDirRecursive(srcPath, destPath, depth + 1);
|
|
680
|
-
} else {
|
|
716
|
+
fs.mkdirSync(destPath, { recursive: true });pth + 1);
|
|
717
|
+
}e {
|
|
718
|
+
this._copyDirRecursive(srcPath, destPath, depth + 1); // Copy file
|
|
719
|
+
} else { fs.copyFileSync(srcPath, destPath);
|
|
681
720
|
// Copy file
|
|
682
|
-
fs.copyFileSync(srcPath, destPath);
|
|
683
|
-
|
|
684
|
-
// Log files at root level only
|
|
685
|
-
if (depth === 0) {
|
|
686
|
-
const ext = path.extname(entry.name);
|
|
721
|
+
fs.copyFileSync(srcPath, destPath); // Log files at root level only
|
|
722
|
+
if (depth === 0) {
|
|
723
|
+
// Log files at root level only = path.extname(entry.name);
|
|
724
|
+
if (depth === 0) { const icon = this._getFileIcon(ext);
|
|
725
|
+
const ext = path.extname(entry.name);sole.log(` ${icon} ${entry.name}`);
|
|
687
726
|
const icon = this._getFileIcon(ext);
|
|
688
727
|
console.log(` ${icon} ${entry.name}`);
|
|
689
728
|
}
|
|
690
729
|
}
|
|
691
730
|
});
|
|
692
731
|
}
|
|
693
|
-
|
|
732
|
+
ype
|
|
694
733
|
/**
|
|
695
734
|
* Get icon for file type
|
|
696
735
|
*/
|
|
697
|
-
_getFileIcon(ext) {
|
|
736
|
+
_getFileIcon(ext) {,
|
|
698
737
|
const icons = {
|
|
699
738
|
'.html': '📄',
|
|
700
|
-
'.css': '🎨'
|
|
701
|
-
'.js': '📜',
|
|
702
|
-
'.json': '📋',
|
|
739
|
+
'.css': '🎨',,
|
|
740
|
+
'.js': '📜',',
|
|
741
|
+
'.json': '📋', '.jpg': '🖼️',
|
|
703
742
|
'.png': '🖼️',
|
|
704
|
-
'.jpg': '🖼️',
|
|
705
|
-
'.jpeg': '🖼️',
|
|
706
|
-
'.gif': '🖼️',
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
743
|
+
'.jpg': '🖼️', '.gif': '🖼️',
|
|
744
|
+
'.jpeg': '🖼️', '.svg': '🎨',
|
|
745
|
+
'.gif': '🖼️', '.ico': '🔖',
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
} } return icons[ext.toLowerCase()] || '📦'; }; '.eot': '🔤' '.ttf': '🔤', '.woff2': '🔤', '.woff': '🔤', '.ico': '🔖', '.svg': '🎨', '.woff': '🔤',
|
|
710
758
|
'.woff2': '🔤',
|
|
711
759
|
'.ttf': '🔤',
|
|
712
760
|
'.eot': '🔤'
|