juxscript 1.0.3 → 1.0.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.
- package/README.md +37 -92
- package/bin/cli.js +57 -56
- package/lib/components/alert.ts +240 -0
- package/lib/components/app.ts +216 -82
- package/lib/components/badge.ts +164 -0
- package/lib/components/button.ts +188 -53
- package/lib/components/card.ts +75 -61
- package/lib/components/chart.ts +17 -15
- package/lib/components/checkbox.ts +228 -0
- package/lib/components/code.ts +66 -152
- package/lib/components/container.ts +104 -208
- package/lib/components/data.ts +1 -3
- package/lib/components/datepicker.ts +226 -0
- package/lib/components/dialog.ts +258 -0
- package/lib/components/docs-data.json +1697 -388
- package/lib/components/dropdown.ts +244 -0
- package/lib/components/element.ts +271 -0
- package/lib/components/fileupload.ts +319 -0
- package/lib/components/footer.ts +37 -18
- package/lib/components/header.ts +53 -33
- package/lib/components/heading.ts +119 -0
- package/lib/components/helpers.ts +34 -0
- package/lib/components/hero.ts +57 -31
- package/lib/components/include.ts +292 -0
- package/lib/components/input.ts +166 -78
- package/lib/components/layout.ts +144 -18
- package/lib/components/list.ts +83 -74
- package/lib/components/loading.ts +263 -0
- package/lib/components/main.ts +43 -17
- package/lib/components/menu.ts +108 -24
- package/lib/components/modal.ts +50 -21
- package/lib/components/nav.ts +60 -18
- package/lib/components/paragraph.ts +111 -0
- package/lib/components/progress.ts +276 -0
- package/lib/components/radio.ts +236 -0
- package/lib/components/req.ts +300 -0
- package/lib/components/script.ts +33 -74
- package/lib/components/select.ts +247 -0
- package/lib/components/sidebar.ts +86 -36
- package/lib/components/style.ts +47 -70
- package/lib/components/switch.ts +261 -0
- package/lib/components/table.ts +47 -24
- package/lib/components/tabs.ts +105 -63
- package/lib/components/theme-toggle.ts +361 -0
- package/lib/components/token-calculator.ts +380 -0
- package/lib/components/tooltip.ts +244 -0
- package/lib/components/view.ts +36 -20
- package/lib/components/write.ts +284 -0
- package/lib/globals.d.ts +21 -0
- package/lib/jux.ts +172 -68
- package/lib/presets/notion.css +521 -0
- package/lib/presets/notion.jux +27 -0
- package/lib/reactivity/state.ts +364 -0
- package/machinery/compiler.js +126 -38
- package/machinery/generators/html.js +2 -3
- package/machinery/server.js +2 -2
- package/package.json +29 -3
- package/lib/components/import.ts +0 -430
- package/lib/components/node.ts +0 -200
- package/lib/components/reactivity.js +0 -104
- package/lib/components/theme.ts +0 -97
- package/lib/layouts/notion.css +0 -258
- package/lib/styles/base-theme.css +0 -186
- package/lib/styles/dark-theme.css +0 -144
- package/lib/styles/light-theme.css +0 -144
- package/lib/styles/tokens/dark.css +0 -86
- package/lib/styles/tokens/light.css +0 -86
- package/lib/templates/index.juxt +0 -33
- package/lib/themes/dark.css +0 -86
- package/lib/themes/light.css +0 -86
- /package/lib/{styles → presets}/global.css +0 -0
package/lib/components/input.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getOrCreateContainer } from './helpers.js';
|
|
2
|
+
import { State } from '../reactivity/state.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Input component options
|
|
5
6
|
*/
|
|
6
7
|
export interface InputOptions {
|
|
7
|
-
type?:
|
|
8
|
-
label?: string;
|
|
9
|
-
placeholder?: string;
|
|
8
|
+
type?: string;
|
|
10
9
|
value?: string;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
label?: string;
|
|
11
12
|
required?: boolean;
|
|
12
13
|
disabled?: boolean;
|
|
14
|
+
name?: string;
|
|
15
|
+
rows?: number;
|
|
13
16
|
onChange?: (value: string) => void;
|
|
17
|
+
style?: string;
|
|
18
|
+
class?: string;
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
/**
|
|
@@ -18,43 +23,57 @@ export interface InputOptions {
|
|
|
18
23
|
*/
|
|
19
24
|
type InputState = {
|
|
20
25
|
type: string;
|
|
21
|
-
label: string;
|
|
22
|
-
placeholder: string;
|
|
23
26
|
value: string;
|
|
27
|
+
placeholder: string;
|
|
28
|
+
label: string;
|
|
24
29
|
required: boolean;
|
|
25
30
|
disabled: boolean;
|
|
26
|
-
|
|
31
|
+
name: string;
|
|
32
|
+
rows: number;
|
|
33
|
+
style: string;
|
|
34
|
+
class: string;
|
|
27
35
|
};
|
|
28
36
|
|
|
29
37
|
/**
|
|
30
|
-
* Input component
|
|
38
|
+
* Input component - text input and textarea
|
|
31
39
|
*
|
|
32
40
|
* Usage:
|
|
33
|
-
*
|
|
34
|
-
* label
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* input
|
|
41
|
+
* jux.input('username')
|
|
42
|
+
* .label('Username')
|
|
43
|
+
* .placeholder('Enter username')
|
|
44
|
+
* .required(true)
|
|
45
|
+
* .render('#form');
|
|
46
|
+
*
|
|
47
|
+
* jux.input('bio')
|
|
48
|
+
* .type('textarea')
|
|
49
|
+
* .rows(5)
|
|
50
|
+
* .render('#form');
|
|
40
51
|
*/
|
|
41
|
-
export class Input
|
|
42
|
-
state
|
|
52
|
+
export class Input {
|
|
53
|
+
state: InputState;
|
|
43
54
|
container: HTMLElement | null = null;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
_id: string;
|
|
56
|
+
id: string;
|
|
57
|
+
private _onChange?: (value: string) => void;
|
|
58
|
+
private _boundState?: State<string>;
|
|
59
|
+
|
|
60
|
+
constructor(id: string, options: InputOptions = {}) {
|
|
61
|
+
this._id = id;
|
|
62
|
+
this.id = id;
|
|
63
|
+
this._onChange = options.onChange;
|
|
64
|
+
|
|
65
|
+
this.state = {
|
|
50
66
|
type: options.type ?? 'text',
|
|
51
|
-
label: options.label ?? '',
|
|
52
|
-
placeholder: options.placeholder ?? '',
|
|
53
67
|
value: options.value ?? '',
|
|
68
|
+
placeholder: options.placeholder ?? '',
|
|
69
|
+
label: options.label ?? '',
|
|
54
70
|
required: options.required ?? false,
|
|
55
71
|
disabled: options.disabled ?? false,
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
name: options.name ?? id,
|
|
73
|
+
rows: options.rows ?? 3,
|
|
74
|
+
style: options.style ?? '',
|
|
75
|
+
class: options.class ?? ''
|
|
76
|
+
};
|
|
58
77
|
}
|
|
59
78
|
|
|
60
79
|
/* -------------------------
|
|
@@ -66,8 +85,9 @@ export class Input extends Reactive {
|
|
|
66
85
|
return this;
|
|
67
86
|
}
|
|
68
87
|
|
|
69
|
-
|
|
70
|
-
this.state.
|
|
88
|
+
value(value: string): this {
|
|
89
|
+
this.state.value = value;
|
|
90
|
+
this._updateElement();
|
|
71
91
|
return this;
|
|
72
92
|
}
|
|
73
93
|
|
|
@@ -76,8 +96,8 @@ export class Input extends Reactive {
|
|
|
76
96
|
return this;
|
|
77
97
|
}
|
|
78
98
|
|
|
79
|
-
|
|
80
|
-
this.state.
|
|
99
|
+
label(value: string): this {
|
|
100
|
+
this.state.label = value;
|
|
81
101
|
return this;
|
|
82
102
|
}
|
|
83
103
|
|
|
@@ -88,21 +108,76 @@ export class Input extends Reactive {
|
|
|
88
108
|
|
|
89
109
|
disabled(value: boolean): this {
|
|
90
110
|
this.state.disabled = value;
|
|
111
|
+
this._updateElement();
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
name(value: string): this {
|
|
116
|
+
this.state.name = value;
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
rows(value: number): this {
|
|
121
|
+
this.state.rows = value;
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
style(value: string): this {
|
|
126
|
+
this.state.style = value;
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
class(value: string): this {
|
|
131
|
+
this.state.class = value;
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
onChange(handler: (value: string) => void): this {
|
|
136
|
+
this._onChange = handler;
|
|
91
137
|
return this;
|
|
92
138
|
}
|
|
93
139
|
|
|
94
|
-
|
|
95
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Two-way binding to state
|
|
142
|
+
*/
|
|
143
|
+
bind(stateObj: State<string>): this {
|
|
144
|
+
this._boundState = stateObj;
|
|
145
|
+
|
|
146
|
+
// Subscribe to state changes
|
|
147
|
+
stateObj.subscribe((val) => {
|
|
148
|
+
this.state.value = val;
|
|
149
|
+
this._updateElement();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Update state on input change
|
|
153
|
+
this.onChange((value) => stateObj.set(value));
|
|
154
|
+
|
|
96
155
|
return this;
|
|
97
156
|
}
|
|
98
157
|
|
|
158
|
+
/* -------------------------
|
|
159
|
+
* Helpers
|
|
160
|
+
* ------------------------- */
|
|
161
|
+
|
|
162
|
+
private _updateElement(): void {
|
|
163
|
+
const input = document.getElementById(`${this._id}-input`) as HTMLInputElement | HTMLTextAreaElement;
|
|
164
|
+
if (input) {
|
|
165
|
+
input.value = this.state.value;
|
|
166
|
+
input.disabled = this.state.disabled;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
getValue(): string {
|
|
171
|
+
return this.state.value;
|
|
172
|
+
}
|
|
173
|
+
|
|
99
174
|
/* -------------------------
|
|
100
175
|
* Render
|
|
101
176
|
* ------------------------- */
|
|
102
177
|
|
|
103
178
|
render(targetId?: string): this {
|
|
104
179
|
let container: HTMLElement;
|
|
105
|
-
|
|
180
|
+
|
|
106
181
|
if (targetId) {
|
|
107
182
|
const target = document.querySelector(targetId);
|
|
108
183
|
if (!target || !(target instanceof HTMLElement)) {
|
|
@@ -110,66 +185,79 @@ export class Input extends Reactive {
|
|
|
110
185
|
}
|
|
111
186
|
container = target;
|
|
112
187
|
} else {
|
|
113
|
-
container = getOrCreateContainer(this.
|
|
188
|
+
container = getOrCreateContainer(this._id);
|
|
114
189
|
}
|
|
115
|
-
|
|
190
|
+
|
|
116
191
|
this.container = container;
|
|
117
|
-
const { type,
|
|
118
|
-
|
|
192
|
+
const { type, value, placeholder, label, required, disabled, name, rows, style, class: className } = this.state;
|
|
193
|
+
|
|
119
194
|
const wrapper = document.createElement('div');
|
|
120
|
-
wrapper.className = 'jux-input
|
|
121
|
-
wrapper.id = this.
|
|
122
|
-
|
|
195
|
+
wrapper.className = 'jux-input';
|
|
196
|
+
wrapper.id = this._id;
|
|
197
|
+
|
|
198
|
+
if (className) {
|
|
199
|
+
wrapper.className += ` ${className}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (style) {
|
|
203
|
+
wrapper.setAttribute('style', style);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Label
|
|
123
207
|
if (label) {
|
|
124
208
|
const labelEl = document.createElement('label');
|
|
125
209
|
labelEl.className = 'jux-input-label';
|
|
210
|
+
labelEl.htmlFor = `${this._id}-input`;
|
|
126
211
|
labelEl.textContent = label;
|
|
127
|
-
|
|
212
|
+
if (required) {
|
|
213
|
+
const requiredSpan = document.createElement('span');
|
|
214
|
+
requiredSpan.className = 'jux-input-required';
|
|
215
|
+
requiredSpan.textContent = ' *';
|
|
216
|
+
labelEl.appendChild(requiredSpan);
|
|
217
|
+
}
|
|
128
218
|
wrapper.appendChild(labelEl);
|
|
129
219
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
wrapper.appendChild(input);
|
|
141
|
-
container.appendChild(wrapper);
|
|
142
|
-
|
|
143
|
-
// Event binding - onChange
|
|
144
|
-
if (onChange) {
|
|
145
|
-
input.addEventListener('input', (e) => {
|
|
146
|
-
const target = e.target as HTMLInputElement;
|
|
147
|
-
onChange(target.value);
|
|
148
|
-
});
|
|
220
|
+
|
|
221
|
+
// Input/Textarea
|
|
222
|
+
let inputEl: HTMLInputElement | HTMLTextAreaElement;
|
|
223
|
+
|
|
224
|
+
if (type === 'textarea') {
|
|
225
|
+
inputEl = document.createElement('textarea');
|
|
226
|
+
inputEl.rows = rows;
|
|
227
|
+
} else {
|
|
228
|
+
inputEl = document.createElement('input');
|
|
229
|
+
inputEl.type = type;
|
|
149
230
|
}
|
|
150
|
-
|
|
231
|
+
|
|
232
|
+
inputEl.className = 'jux-input-element';
|
|
233
|
+
inputEl.id = `${this._id}-input`;
|
|
234
|
+
inputEl.name = name;
|
|
235
|
+
inputEl.value = value;
|
|
236
|
+
inputEl.placeholder = placeholder;
|
|
237
|
+
inputEl.required = required;
|
|
238
|
+
inputEl.disabled = disabled;
|
|
239
|
+
|
|
240
|
+
inputEl.addEventListener('input', (e) => {
|
|
241
|
+
const target = e.target as HTMLInputElement | HTMLTextAreaElement;
|
|
242
|
+
this.state.value = target.value;
|
|
243
|
+
if (this._onChange) {
|
|
244
|
+
this._onChange(target.value);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
wrapper.appendChild(inputEl);
|
|
249
|
+
container.appendChild(wrapper);
|
|
151
250
|
return this;
|
|
152
251
|
}
|
|
153
252
|
|
|
154
|
-
/**
|
|
155
|
-
* Render to another Jux component's container
|
|
156
|
-
*/
|
|
157
253
|
renderTo(juxComponent: any): this {
|
|
158
|
-
if (!juxComponent
|
|
159
|
-
throw new Error('Input.renderTo: Invalid component
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (!juxComponent._componentId || typeof juxComponent._componentId !== 'string') {
|
|
163
|
-
throw new Error('Input.renderTo: Invalid component - missing _componentId (not a Jux component)');
|
|
254
|
+
if (!juxComponent?._id) {
|
|
255
|
+
throw new Error('Input.renderTo: Invalid component');
|
|
164
256
|
}
|
|
165
|
-
|
|
166
|
-
return this.render(`#${juxComponent._componentId}`);
|
|
257
|
+
return this.render(`#${juxComponent._id}`);
|
|
167
258
|
}
|
|
168
259
|
}
|
|
169
260
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
*/
|
|
173
|
-
export function input(componentId: string, options: InputOptions = {}): Input {
|
|
174
|
-
return new Input(componentId, options);
|
|
261
|
+
export function input(id: string, options: InputOptions = {}): Input {
|
|
262
|
+
return new Input(id, options);
|
|
175
263
|
}
|
package/lib/components/layout.ts
CHANGED
|
@@ -1,19 +1,45 @@
|
|
|
1
1
|
import { ErrorHandler } from './error-handler.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Layout
|
|
5
|
-
*
|
|
4
|
+
* Layout component interface
|
|
5
|
+
* Represents any Jux component that can be rendered
|
|
6
|
+
*/
|
|
7
|
+
interface LayoutComponent {
|
|
8
|
+
render: (target?: string) => any;
|
|
9
|
+
_componentId?: string;
|
|
10
|
+
_id?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Layout - Compose and render multiple components
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* // As a composition function
|
|
18
|
+
* jux.layout(
|
|
19
|
+
* jux.include('/lib/presets/notion/notion.css'),
|
|
20
|
+
* jux.header('appheader').render('#app'),
|
|
21
|
+
* jux.sidebar('appsidebar').render('#app'),
|
|
22
|
+
* jux.main('appmain').render('#app')
|
|
23
|
+
* );
|
|
24
|
+
*
|
|
25
|
+
* // Or load from a .jux file
|
|
26
|
+
* jux.layout('/lib/presets/notion/main.jux');
|
|
6
27
|
*/
|
|
7
28
|
export class Layout {
|
|
8
29
|
private _juxFile: string;
|
|
9
30
|
private _loaded: boolean = false;
|
|
31
|
+
private _components: any[] = [];
|
|
10
32
|
|
|
11
|
-
constructor(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (juxFile) {
|
|
33
|
+
constructor(...args: any[]) {
|
|
34
|
+
// If first arg is string, it's a file path
|
|
35
|
+
if (args.length === 1 && typeof args[0] === 'string' && args[0].endsWith('.jux')) {
|
|
36
|
+
this._juxFile = args[0];
|
|
16
37
|
this.load();
|
|
38
|
+
} else {
|
|
39
|
+
// Otherwise, it's a list of components to compose
|
|
40
|
+
this._juxFile = '';
|
|
41
|
+
this._components = args;
|
|
42
|
+
this._compose();
|
|
17
43
|
}
|
|
18
44
|
}
|
|
19
45
|
|
|
@@ -34,6 +60,41 @@ export class Layout {
|
|
|
34
60
|
return this._juxFile;
|
|
35
61
|
}
|
|
36
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Compose components immediately
|
|
65
|
+
* This handles the case where components are passed directly
|
|
66
|
+
*/
|
|
67
|
+
private _compose(): this {
|
|
68
|
+
try {
|
|
69
|
+
// Components are already rendered via chaining
|
|
70
|
+
// This just ensures they're tracked
|
|
71
|
+
this._components.forEach((component, index) => {
|
|
72
|
+
// Components that return themselves from .render() are already in DOM
|
|
73
|
+
// Just log for debugging
|
|
74
|
+
if (component && typeof component === 'object') {
|
|
75
|
+
const id = component._componentId || component._id || component.id || `component-${index}`;
|
|
76
|
+
console.log(`✓ Layout component composed: ${id}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
this._loaded = true;
|
|
81
|
+
} catch (error: any) {
|
|
82
|
+
ErrorHandler.captureError({
|
|
83
|
+
component: 'Layout',
|
|
84
|
+
method: '_compose',
|
|
85
|
+
message: `Failed to compose layout: ${error.message}`,
|
|
86
|
+
stack: error.stack,
|
|
87
|
+
timestamp: new Date(),
|
|
88
|
+
context: {
|
|
89
|
+
componentCount: this._components.length,
|
|
90
|
+
error: 'composition_failed'
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
37
98
|
/**
|
|
38
99
|
* Normalize path to absolute URL from site root
|
|
39
100
|
*/
|
|
@@ -42,22 +103,22 @@ export class Layout {
|
|
|
42
103
|
if (path.startsWith('http://') || path.startsWith('https://')) {
|
|
43
104
|
return path;
|
|
44
105
|
}
|
|
45
|
-
|
|
106
|
+
|
|
46
107
|
// Convert relative path to absolute
|
|
47
108
|
// Remove leading './' if present
|
|
48
109
|
let cleanPath = path.replace(/^\.\//, '');
|
|
49
|
-
|
|
110
|
+
|
|
50
111
|
// Ensure it starts with /
|
|
51
112
|
if (!cleanPath.startsWith('/')) {
|
|
52
113
|
cleanPath = '/' + cleanPath;
|
|
53
114
|
}
|
|
54
|
-
|
|
115
|
+
|
|
55
116
|
// Return absolute URL with origin
|
|
56
117
|
return new URL(cleanPath, window.location.origin).href;
|
|
57
118
|
}
|
|
58
119
|
|
|
59
120
|
/**
|
|
60
|
-
* Load the layout
|
|
121
|
+
* Load the layout from a .jux file
|
|
61
122
|
* This will dynamically import the compiled JS file
|
|
62
123
|
*/
|
|
63
124
|
async load(): Promise<this> {
|
|
@@ -65,25 +126,33 @@ export class Layout {
|
|
|
65
126
|
return this;
|
|
66
127
|
}
|
|
67
128
|
|
|
129
|
+
if (!this._juxFile) {
|
|
130
|
+
console.warn('Layout: No file specified to load');
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
|
|
68
134
|
try {
|
|
69
135
|
// Convert .jux to .js for the compiled output
|
|
70
136
|
let jsFile = this._juxFile.replace(/\.jux$/, '.js');
|
|
71
|
-
|
|
137
|
+
|
|
72
138
|
// Normalize to absolute URL for browser import
|
|
73
139
|
jsFile = this.normalizePath(jsFile);
|
|
74
|
-
|
|
140
|
+
|
|
75
141
|
console.log(`Loading layout: ${jsFile}`);
|
|
76
|
-
|
|
142
|
+
|
|
77
143
|
// Dynamic import of the layout module
|
|
78
144
|
const layoutModule = await import(jsFile);
|
|
79
|
-
|
|
145
|
+
|
|
80
146
|
// If the module has an init or default export, call it
|
|
81
147
|
if (typeof layoutModule.default === 'function') {
|
|
82
148
|
await layoutModule.default();
|
|
83
149
|
} else if (typeof layoutModule.init === 'function') {
|
|
84
150
|
await layoutModule.init();
|
|
151
|
+
} else if (layoutModule.default && typeof layoutModule.default === 'object') {
|
|
152
|
+
// If default export is the layout object itself
|
|
153
|
+
console.log('✓ Layout module loaded (object export)');
|
|
85
154
|
}
|
|
86
|
-
|
|
155
|
+
|
|
87
156
|
this._loaded = true;
|
|
88
157
|
console.log(`✓ Layout loaded: ${this._juxFile}`);
|
|
89
158
|
} catch (error: any) {
|
|
@@ -93,7 +162,7 @@ export class Layout {
|
|
|
93
162
|
message: `Failed to load layout: ${error.message}`,
|
|
94
163
|
stack: error.stack,
|
|
95
164
|
timestamp: new Date(),
|
|
96
|
-
context: {
|
|
165
|
+
context: {
|
|
97
166
|
juxFile: this._juxFile,
|
|
98
167
|
jsFile: this._juxFile.replace(/\.jux$/, '.js'),
|
|
99
168
|
errorCode: error.code
|
|
@@ -110,4 +179,61 @@ export class Layout {
|
|
|
110
179
|
isLoaded(): boolean {
|
|
111
180
|
return this._loaded;
|
|
112
181
|
}
|
|
113
|
-
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get all composed components
|
|
185
|
+
*/
|
|
186
|
+
getComponents(): any[] {
|
|
187
|
+
return this._components;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Add a component to the layout
|
|
192
|
+
*/
|
|
193
|
+
add(component: any): this {
|
|
194
|
+
this._components.push(component);
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Render all components
|
|
200
|
+
* Useful if components weren't already rendered
|
|
201
|
+
*/
|
|
202
|
+
render(target?: string): this {
|
|
203
|
+
this._components.forEach((component) => {
|
|
204
|
+
if (component && typeof component.render === 'function') {
|
|
205
|
+
try {
|
|
206
|
+
component.render(target);
|
|
207
|
+
} catch (error: any) {
|
|
208
|
+
ErrorHandler.captureError({
|
|
209
|
+
component: 'Layout',
|
|
210
|
+
method: 'render',
|
|
211
|
+
message: `Failed to render component: ${error.message}`,
|
|
212
|
+
stack: error.stack,
|
|
213
|
+
timestamp: new Date(),
|
|
214
|
+
context: {
|
|
215
|
+
componentId: component._componentId || component._id || 'unknown',
|
|
216
|
+
target
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Factory helper
|
|
229
|
+
* Accepts either:
|
|
230
|
+
* - A file path: layout('/lib/presets/notion/main.jux')
|
|
231
|
+
* - A list of components: layout(header, sidebar, main)
|
|
232
|
+
*/
|
|
233
|
+
export function layout(...args: any[]): Layout {
|
|
234
|
+
return new Layout(...args);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* -------------------------
|
|
238
|
+
* Module Exports
|
|
239
|
+
* ------------------------- */
|