juxscript 1.0.62 → 1.0.64
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/bin/cli.js +161 -293
- package/docs/v2comps/HEADLESS.md +83 -0
- package/docs/v2comps/ISOMORPHISM.md +10 -0
- package/juxconfig.example.js +63 -58
- package/lib/componentsv2/base/BaseEngine.js +258 -0
- package/lib/componentsv2/base/BaseEngine.js.map +1 -0
- package/lib/componentsv2/base/BaseEngine.ts +303 -0
- package/lib/componentsv2/base/BaseSkin.js +108 -0
- package/lib/componentsv2/base/BaseSkin.js.map +1 -0
- package/lib/componentsv2/base/BaseSkin.ts +137 -0
- package/lib/componentsv2/base/GlobalBus.js +56 -0
- package/lib/componentsv2/base/GlobalBus.js.map +1 -0
- package/lib/componentsv2/base/GlobalBus.ts +60 -0
- package/lib/componentsv2/base/State.js +68 -0
- package/lib/componentsv2/base/State.js.map +1 -0
- package/lib/componentsv2/base/State.ts +62 -0
- package/lib/componentsv2/grid/component.js +41 -0
- package/lib/componentsv2/grid/component.js.map +1 -0
- package/lib/componentsv2/grid/component.ts +67 -0
- package/lib/componentsv2/grid/engine.js +73 -0
- package/lib/componentsv2/grid/engine.js.map +1 -0
- package/lib/componentsv2/grid/engine.ts +110 -0
- package/lib/componentsv2/grid/skin.js +95 -0
- package/lib/componentsv2/grid/skin.js.map +1 -0
- package/lib/componentsv2/grid/skin.ts +105 -0
- package/lib/componentsv2/grid/structure.css +58 -0
- package/lib/componentsv2/index.js +218 -0
- package/lib/componentsv2/index.js.map +1 -0
- package/lib/componentsv2/index.ts +253 -0
- package/lib/componentsv2/input/component.js +21 -0
- package/lib/componentsv2/input/component.js.map +1 -0
- package/lib/componentsv2/input/component.ts +28 -0
- package/lib/componentsv2/input/engine.js +50 -0
- package/lib/componentsv2/input/engine.js.map +1 -0
- package/lib/componentsv2/input/engine.ts +76 -0
- package/lib/componentsv2/input/skin.js +91 -0
- package/lib/componentsv2/input/skin.js.map +1 -0
- package/lib/componentsv2/input/skin.ts +91 -0
- package/lib/componentsv2/input/structure.css +47 -0
- package/lib/componentsv2/list/component.js +83 -0
- package/lib/componentsv2/list/component.js.map +1 -0
- package/lib/componentsv2/list/component.ts +97 -0
- package/lib/componentsv2/list/engine.js +261 -0
- package/lib/componentsv2/list/engine.js.map +1 -0
- package/lib/componentsv2/list/engine.ts +345 -0
- package/lib/componentsv2/list/skin.js +343 -0
- package/lib/componentsv2/list/skin.js.map +1 -0
- package/lib/componentsv2/list/skin.ts +367 -0
- package/lib/componentsv2/list/structure.css +359 -0
- package/lib/componentsv2/plugins/ClientSQLitePlugin.js +130 -0
- package/lib/componentsv2/plugins/ClientSQLitePlugin.js.map +1 -0
- package/lib/componentsv2/plugins/ClientSQLitePlugin.ts +154 -0
- package/lib/componentsv2/plugins/IndexedDBPlugin.js +75 -0
- package/lib/componentsv2/plugins/IndexedDBPlugin.js.map +1 -0
- package/lib/componentsv2/plugins/IndexedDBPlugin.ts +96 -0
- package/lib/componentsv2/plugins/LocalStoragePlugin.js +65 -0
- package/lib/componentsv2/plugins/LocalStoragePlugin.js.map +1 -0
- package/lib/componentsv2/plugins/LocalStoragePlugin.ts +86 -0
- package/lib/componentsv2/plugins/ServerSQLitePlugin.js +70 -0
- package/lib/componentsv2/plugins/ServerSQLitePlugin.js.map +1 -0
- package/lib/componentsv2/plugins/ServerSQLitePlugin.ts +99 -0
- package/lib/componentsv2/stubs/ComponentComposition.ts.stub +32 -0
- package/lib/componentsv2/stubs/ComponentEngine.ts.stub +36 -0
- package/lib/componentsv2/stubs/ComponentSkin.ts.stub +34 -0
- package/lib/componentsv2/stubs/ComponentStructure.css.stub +13 -0
- package/lib/componentsv2/tools/CreateSkin.js +62 -0
- package/lib/componentsv2/tools/DocSpam.js +134 -0
- package/lib/componentsv2/tools/FluencyAudit.js +141 -0
- package/lib/componentsv2/tools/OptionsAudit.js +177 -0
- package/lib/componentsv2/tools/Scaffold.js +140 -0
- package/lib/utils/fetch.js +428 -0
- package/lib/utils/fetch.js.map +1 -0
- package/machinery/build.js +2 -1
- package/machinery/compiler.js +200 -37
- package/machinery/config.js +93 -6
- package/machinery/diagnose.js +72 -0
- package/machinery/jux-module-pattern.md +118 -0
- package/machinery/server.js +23 -7
- package/machinery/verifier.js +143 -0
- package/machinery/watcher.js +53 -64
- package/package.json +11 -2
- package/lib/components/alert.ts +0 -200
- package/lib/components/app.ts +0 -258
- package/lib/components/badge.ts +0 -101
- package/lib/components/base/BaseComponent.ts +0 -417
- package/lib/components/base/FormInput.ts +0 -227
- package/lib/components/button.ts +0 -178
- package/lib/components/card.ts +0 -173
- package/lib/components/chart.ts +0 -231
- package/lib/components/checkbox.ts +0 -242
- package/lib/components/code.ts +0 -123
- package/lib/components/container.ts +0 -140
- package/lib/components/data.ts +0 -135
- package/lib/components/datepicker.ts +0 -234
- package/lib/components/dialog.ts +0 -172
- package/lib/components/divider.ts +0 -100
- package/lib/components/dropdown.ts +0 -186
- package/lib/components/element.ts +0 -267
- package/lib/components/error-handler.ts +0 -285
- package/lib/components/fileupload.ts +0 -309
- package/lib/components/grid.ts +0 -291
- package/lib/components/guard.ts +0 -92
- package/lib/components/heading.ts +0 -96
- package/lib/components/helpers.ts +0 -41
- package/lib/components/hero.ts +0 -224
- package/lib/components/icon.ts +0 -160
- package/lib/components/icons.ts +0 -175
- package/lib/components/include.ts +0 -440
- package/lib/components/input.ts +0 -457
- package/lib/components/list.ts +0 -419
- package/lib/components/loading.ts +0 -100
- package/lib/components/menu.ts +0 -260
- package/lib/components/modal.ts +0 -239
- package/lib/components/nav.ts +0 -257
- package/lib/components/paragraph.ts +0 -97
- package/lib/components/progress.ts +0 -139
- package/lib/components/radio.ts +0 -278
- package/lib/components/req.ts +0 -302
- package/lib/components/script.ts +0 -43
- package/lib/components/select.ts +0 -252
- package/lib/components/sidebar.ts +0 -167
- package/lib/components/style.ts +0 -43
- package/lib/components/switch.ts +0 -246
- package/lib/components/table.ts +0 -1249
- package/lib/components/tabs.ts +0 -250
- package/lib/components/theme-toggle.ts +0 -300
- package/lib/components/token-calculator.ts +0 -313
- package/lib/components/tooltip.ts +0 -144
- package/lib/components/view.ts +0 -190
- package/lib/components/write.ts +0 -272
- package/lib/jux.ts +0 -365
- package/lib/layouts/default.css +0 -260
- package/lib/layouts/figma.css +0 -334
- package/lib/reactivity/state.ts +0 -78
- package/machinery/bundleAssets.js +0 -0
- package/machinery/bundleJux.js +0 -0
- package/machinery/bundleVendors.js +0 -0
- package/presets/default/all.jux +0 -343
- package/presets/default/index.jux +0 -90
- package/presets/default/layout.jux +0 -57
- package/presets/default/style.css +0 -1612
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import { BaseComponent } from './base/BaseComponent.js';
|
|
2
|
-
|
|
3
|
-
// Event definitions
|
|
4
|
-
const TRIGGER_EVENTS = [] as const; // Element has no trigger events
|
|
5
|
-
const CALLBACK_EVENTS = [] as const; // Element has no callback events
|
|
6
|
-
|
|
7
|
-
export interface ElementOptions {
|
|
8
|
-
tagType?: string;
|
|
9
|
-
className?: string;
|
|
10
|
-
text?: string;
|
|
11
|
-
innerHTML?: string;
|
|
12
|
-
attributes?: Record<string, string>;
|
|
13
|
-
styles?: Record<string, string>;
|
|
14
|
-
style?: string;
|
|
15
|
-
class?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
type ElementState = {
|
|
19
|
-
tagType: string;
|
|
20
|
-
className: string;
|
|
21
|
-
text: string;
|
|
22
|
-
innerHTML: string;
|
|
23
|
-
attributes: Record<string, string>;
|
|
24
|
-
styles: Record<string, string>;
|
|
25
|
-
style: string;
|
|
26
|
-
class: string;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export class Element extends BaseComponent<ElementState> {
|
|
30
|
-
private _element: HTMLElement | null = null;
|
|
31
|
-
|
|
32
|
-
constructor(id: string, options: ElementOptions = {}) {
|
|
33
|
-
super(id, {
|
|
34
|
-
tagType: options.tagType ?? 'div',
|
|
35
|
-
className: options.className ?? '',
|
|
36
|
-
text: options.text ?? '',
|
|
37
|
-
innerHTML: options.innerHTML ?? '',
|
|
38
|
-
attributes: options.attributes ?? {},
|
|
39
|
-
styles: options.styles ?? {},
|
|
40
|
-
style: options.style ?? '',
|
|
41
|
-
class: options.class ?? ''
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
protected getTriggerEvents(): readonly string[] {
|
|
46
|
-
return TRIGGER_EVENTS;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
protected getCallbackEvents(): readonly string[] {
|
|
50
|
-
return CALLBACK_EVENTS;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
54
|
-
* FLUENT API
|
|
55
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
56
|
-
|
|
57
|
-
// ✅ Inherited from BaseComponent:
|
|
58
|
-
// - style(), class()
|
|
59
|
-
// - addClass(), removeClass(), toggleClass()
|
|
60
|
-
// - visible(), show(), hide(), toggleVisibility()
|
|
61
|
-
// - attr(), attrs(), removeAttr()
|
|
62
|
-
// - disabled(), enable(), disable()
|
|
63
|
-
// - loading()
|
|
64
|
-
// - focus(), blur()
|
|
65
|
-
// - remove()
|
|
66
|
-
// - bind(), sync(), renderTo()
|
|
67
|
-
|
|
68
|
-
tagType(value: string): Element {
|
|
69
|
-
this.state.tagType = value;
|
|
70
|
-
return this;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
className(value: string): Element {
|
|
74
|
-
this.state.className = value;
|
|
75
|
-
return this;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
text(value: string): Element {
|
|
79
|
-
this.state.text = value;
|
|
80
|
-
this.state.innerHTML = '';
|
|
81
|
-
return this;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
innerHTML(value: string): Element {
|
|
85
|
-
this.state.innerHTML = value;
|
|
86
|
-
this.state.text = '';
|
|
87
|
-
return this;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
write(content: string, asHTML: boolean = false, newLine: boolean = false): Element {
|
|
91
|
-
if (!this._element) {
|
|
92
|
-
console.warn(`Element.write: Element "${this._id}" not yet rendered`);
|
|
93
|
-
return this;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const contentWithBreak = newLine ? (asHTML ? content + '<br>' : content + '\n') : content;
|
|
97
|
-
|
|
98
|
-
if (asHTML) {
|
|
99
|
-
this._element.insertAdjacentHTML('beforeend', contentWithBreak);
|
|
100
|
-
} else {
|
|
101
|
-
const textNode = document.createTextNode(contentWithBreak);
|
|
102
|
-
this._element.appendChild(textNode);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return this;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
prepend(content: string, asHTML: boolean = false, newLine: boolean = false): Element {
|
|
109
|
-
if (!this._element) {
|
|
110
|
-
console.warn(`Element.prepend: Element "${this._id}" not yet rendered`);
|
|
111
|
-
return this;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const contentWithBreak = newLine ? (asHTML ? content + '<br>' : content + '\n') : content;
|
|
115
|
-
|
|
116
|
-
if (asHTML) {
|
|
117
|
-
this._element.insertAdjacentHTML('afterbegin', contentWithBreak);
|
|
118
|
-
} else {
|
|
119
|
-
const textNode = document.createTextNode(contentWithBreak);
|
|
120
|
-
this._element.insertBefore(textNode, this._element.firstChild);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return this;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
clear(): Element {
|
|
127
|
-
if (!this._element) {
|
|
128
|
-
console.warn(`Element.clear: Element "${this._id}" not yet rendered`);
|
|
129
|
-
return this;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
this._element.innerHTML = '';
|
|
133
|
-
return this;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Append child component or HTML element
|
|
138
|
-
*/
|
|
139
|
-
append(child: any): Element {
|
|
140
|
-
if (!this._element) {
|
|
141
|
-
console.warn(`Element.append: Element "${this._id}" not yet rendered`);
|
|
142
|
-
return this;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (child instanceof HTMLElement) {
|
|
146
|
-
this._element.appendChild(child);
|
|
147
|
-
} else if (child?._element) {
|
|
148
|
-
// Another Jux component
|
|
149
|
-
this._element.appendChild(child._element);
|
|
150
|
-
} else if (typeof child === 'string') {
|
|
151
|
-
const div = document.createElement('div');
|
|
152
|
-
div.innerHTML = child;
|
|
153
|
-
this._element.appendChild(div.firstChild || div);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return this;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
render(targetId?: string): this {
|
|
160
|
-
const container = this._setupContainer(targetId);
|
|
161
|
-
|
|
162
|
-
const { tagType, className, text, innerHTML, attributes, styles, style, class: classValue } = this.state;
|
|
163
|
-
|
|
164
|
-
// Build element
|
|
165
|
-
const element = document.createElement(tagType);
|
|
166
|
-
element.id = this._id;
|
|
167
|
-
|
|
168
|
-
if (className) element.className = className;
|
|
169
|
-
if (classValue) element.className = `${element.className} ${classValue}`.trim();
|
|
170
|
-
|
|
171
|
-
if (text) {
|
|
172
|
-
element.textContent = text;
|
|
173
|
-
} else if (innerHTML) {
|
|
174
|
-
element.innerHTML = innerHTML;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
Object.entries(attributes).forEach(([key, value]) => {
|
|
178
|
-
element.setAttribute(key, value);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const styleString = Object.entries(styles)
|
|
182
|
-
.map(([key, value]) => `${key}: ${value}`)
|
|
183
|
-
.join('; ');
|
|
184
|
-
|
|
185
|
-
if (styleString || style) {
|
|
186
|
-
element.setAttribute('style', `${styleString}; ${style}`.trim());
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Wire events using inherited method
|
|
190
|
-
this._wireStandardEvents(element);
|
|
191
|
-
|
|
192
|
-
// Wire sync bindings
|
|
193
|
-
this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
|
|
194
|
-
const transform = toComponent || ((v: any) => v);
|
|
195
|
-
|
|
196
|
-
stateObj.subscribe((val: any) => {
|
|
197
|
-
const transformed = transform(val);
|
|
198
|
-
|
|
199
|
-
if (property === 'text') {
|
|
200
|
-
element.textContent = String(transformed);
|
|
201
|
-
this.state.text = String(transformed);
|
|
202
|
-
} else if (property === 'innerHTML') {
|
|
203
|
-
element.innerHTML = String(transformed);
|
|
204
|
-
this.state.innerHTML = String(transformed);
|
|
205
|
-
} else if (property === 'class') {
|
|
206
|
-
element.className = String(transformed);
|
|
207
|
-
this.state.class = String(transformed);
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
container.appendChild(element);
|
|
213
|
-
this._element = element;
|
|
214
|
-
|
|
215
|
-
return this;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export function element(id: string, options: ElementOptions = {}): Element {
|
|
220
|
-
return new Element(id, options);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Semantic HTML element factories with default classes
|
|
224
|
-
export function header(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
|
|
225
|
-
const defaultClass = 'jux-header';
|
|
226
|
-
const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
|
|
227
|
-
return new Element(id, { ...options, tagType: 'header', class: mergedClass });
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export function footer(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
|
|
231
|
-
const defaultClass = 'jux-footer';
|
|
232
|
-
const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
|
|
233
|
-
return new Element(id, { ...options, tagType: 'footer', class: mergedClass });
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export function main(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
|
|
237
|
-
const defaultClass = 'jux-main';
|
|
238
|
-
const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
|
|
239
|
-
return new Element(id, { ...options, tagType: 'main', class: mergedClass });
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export function aside(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
|
|
243
|
-
const defaultClass = 'jux-aside';
|
|
244
|
-
const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
|
|
245
|
-
return new Element(id, { ...options, tagType: 'aside', class: mergedClass });
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export function section(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
|
|
249
|
-
const defaultClass = 'jux-section';
|
|
250
|
-
const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
|
|
251
|
-
return new Element(id, { ...options, tagType: 'section', class: mergedClass });
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export function article(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
|
|
255
|
-
const defaultClass = 'jux-article';
|
|
256
|
-
const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
|
|
257
|
-
return new Element(id, { ...options, tagType: 'article', class: mergedClass });
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export function div(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
|
|
261
|
-
return new Element(id, { ...options, tagType: 'div' });
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export function span(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
|
|
265
|
-
return new Element(id, { ...options, tagType: 'span' });
|
|
266
|
-
}
|
|
267
|
-
|
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ErrorHandler - Global error handler for JUX components
|
|
3
|
-
* Displays errors visually in the DOM for easy debugging
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface ComponentError {
|
|
7
|
-
component: string;
|
|
8
|
-
method: string;
|
|
9
|
-
message: string;
|
|
10
|
-
stack?: string;
|
|
11
|
-
timestamp: Date;
|
|
12
|
-
context?: any;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class ErrorHandler {
|
|
16
|
-
private static errors: ComponentError[] = [];
|
|
17
|
-
private static errorContainer: HTMLDivElement | null = null;
|
|
18
|
-
private static enabled: boolean = true;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Initialize error handler
|
|
22
|
-
*/
|
|
23
|
-
static init() {
|
|
24
|
-
if (typeof document === 'undefined') return;
|
|
25
|
-
|
|
26
|
-
// Create error container
|
|
27
|
-
this.errorContainer = document.createElement('div');
|
|
28
|
-
this.errorContainer.id = 'jux-error-container';
|
|
29
|
-
this.errorContainer.style.cssText = `
|
|
30
|
-
position: fixed;
|
|
31
|
-
bottom: 20px;
|
|
32
|
-
right: 20px;
|
|
33
|
-
max-width: 500px;
|
|
34
|
-
max-height: 600px;
|
|
35
|
-
overflow-y: auto;
|
|
36
|
-
z-index: 999999;
|
|
37
|
-
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
|
38
|
-
font-size: 12px;
|
|
39
|
-
`;
|
|
40
|
-
|
|
41
|
-
document.body.appendChild(this.errorContainer);
|
|
42
|
-
|
|
43
|
-
// Listen for unhandled errors
|
|
44
|
-
window.addEventListener('error', (event) => {
|
|
45
|
-
this.captureError({
|
|
46
|
-
component: 'Global',
|
|
47
|
-
method: 'window.onerror',
|
|
48
|
-
message: event.message,
|
|
49
|
-
stack: event.error?.stack,
|
|
50
|
-
timestamp: new Date(),
|
|
51
|
-
context: { filename: event.filename, lineno: event.lineno, colno: event.colno }
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Listen for unhandled promise rejections
|
|
56
|
-
window.addEventListener('unhandledrejection', (event) => {
|
|
57
|
-
this.captureError({
|
|
58
|
-
component: 'Global',
|
|
59
|
-
method: 'unhandledrejection',
|
|
60
|
-
message: event.reason?.message || String(event.reason),
|
|
61
|
-
stack: event.reason?.stack,
|
|
62
|
-
timestamp: new Date(),
|
|
63
|
-
context: { promise: event.promise }
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Capture and display an error
|
|
70
|
-
*/
|
|
71
|
-
static captureError(error: ComponentError) {
|
|
72
|
-
if (!this.enabled) return;
|
|
73
|
-
|
|
74
|
-
this.errors.push(error);
|
|
75
|
-
this.renderError(error);
|
|
76
|
-
console.error(`[JUX Error] ${error.component}.${error.method}:`, error.message);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Render an error card in the DOM
|
|
81
|
-
*/
|
|
82
|
-
private static renderError(error: ComponentError) {
|
|
83
|
-
if (!this.errorContainer) return;
|
|
84
|
-
|
|
85
|
-
const errorCard = document.createElement('div');
|
|
86
|
-
errorCard.style.cssText = `
|
|
87
|
-
background: linear-gradient(135deg, #1a1a1a 0%, #2a1a1a 100%);
|
|
88
|
-
border-left: 4px solid #ff4757;
|
|
89
|
-
border-radius: 8px;
|
|
90
|
-
padding: 16px;
|
|
91
|
-
margin-bottom: 12px;
|
|
92
|
-
box-shadow: 0 4px 12px rgba(255, 71, 87, 0.3);
|
|
93
|
-
animation: slideIn 0.3s ease-out;
|
|
94
|
-
`;
|
|
95
|
-
|
|
96
|
-
const time = error.timestamp.toLocaleTimeString();
|
|
97
|
-
|
|
98
|
-
errorCard.innerHTML = `
|
|
99
|
-
<style>
|
|
100
|
-
@keyframes slideIn {
|
|
101
|
-
from {
|
|
102
|
-
transform: translateX(100%);
|
|
103
|
-
opacity: 0;
|
|
104
|
-
}
|
|
105
|
-
to {
|
|
106
|
-
transform: translateX(0);
|
|
107
|
-
opacity: 1;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
.jux-error-header {
|
|
111
|
-
display: flex;
|
|
112
|
-
justify-content: space-between;
|
|
113
|
-
align-items: center;
|
|
114
|
-
margin-bottom: 8px;
|
|
115
|
-
}
|
|
116
|
-
.jux-error-title {
|
|
117
|
-
color: #ff4757;
|
|
118
|
-
font-weight: bold;
|
|
119
|
-
font-size: 13px;
|
|
120
|
-
}
|
|
121
|
-
.jux-error-time {
|
|
122
|
-
color: #666;
|
|
123
|
-
font-size: 11px;
|
|
124
|
-
}
|
|
125
|
-
.jux-error-location {
|
|
126
|
-
color: #ffa502;
|
|
127
|
-
font-size: 12px;
|
|
128
|
-
margin-bottom: 8px;
|
|
129
|
-
}
|
|
130
|
-
.jux-error-message {
|
|
131
|
-
color: #e0e0e0;
|
|
132
|
-
margin-bottom: 8px;
|
|
133
|
-
line-height: 1.4;
|
|
134
|
-
}
|
|
135
|
-
.jux-error-stack {
|
|
136
|
-
background: #0a0a0a;
|
|
137
|
-
padding: 8px;
|
|
138
|
-
border-radius: 4px;
|
|
139
|
-
color: #888;
|
|
140
|
-
font-size: 10px;
|
|
141
|
-
max-height: 150px;
|
|
142
|
-
overflow-y: auto;
|
|
143
|
-
margin-bottom: 8px;
|
|
144
|
-
}
|
|
145
|
-
.jux-error-context {
|
|
146
|
-
background: #0a0a0a;
|
|
147
|
-
padding: 8px;
|
|
148
|
-
border-radius: 4px;
|
|
149
|
-
color: #888;
|
|
150
|
-
font-size: 10px;
|
|
151
|
-
}
|
|
152
|
-
.jux-error-close {
|
|
153
|
-
background: #ff4757;
|
|
154
|
-
color: white;
|
|
155
|
-
border: none;
|
|
156
|
-
border-radius: 4px;
|
|
157
|
-
padding: 4px 12px;
|
|
158
|
-
cursor: pointer;
|
|
159
|
-
font-size: 11px;
|
|
160
|
-
float: right;
|
|
161
|
-
}
|
|
162
|
-
.jux-error-close:hover {
|
|
163
|
-
background: #ff6b7a;
|
|
164
|
-
}
|
|
165
|
-
</style>
|
|
166
|
-
<div class="jux-error-header">
|
|
167
|
-
<div class="jux-error-title">⚠️ Component Error</div>
|
|
168
|
-
<div class="jux-error-time">${time}</div>
|
|
169
|
-
</div>
|
|
170
|
-
<div class="jux-error-location">
|
|
171
|
-
${error.component}.${error.method}()
|
|
172
|
-
</div>
|
|
173
|
-
<div class="jux-error-message">
|
|
174
|
-
${this.escapeHtml(error.message)}
|
|
175
|
-
</div>
|
|
176
|
-
${error.stack ? `
|
|
177
|
-
<details>
|
|
178
|
-
<summary style="color: #888; cursor: pointer; margin-bottom: 8px;">Stack Trace</summary>
|
|
179
|
-
<div class="jux-error-stack">${this.escapeHtml(error.stack)}</div>
|
|
180
|
-
</details>
|
|
181
|
-
` : ''}
|
|
182
|
-
${error.context ? `
|
|
183
|
-
<details>
|
|
184
|
-
<summary style="color: #888; cursor: pointer; margin-bottom: 8px;">Context</summary>
|
|
185
|
-
<div class="jux-error-context">${this.escapeHtml(JSON.stringify(error.context, null, 2))}</div>
|
|
186
|
-
</details>
|
|
187
|
-
` : ''}
|
|
188
|
-
<button class="jux-error-close">Dismiss</button>
|
|
189
|
-
`;
|
|
190
|
-
|
|
191
|
-
// Add close button handler
|
|
192
|
-
const closeBtn = errorCard.querySelector('.jux-error-close');
|
|
193
|
-
closeBtn?.addEventListener('click', () => {
|
|
194
|
-
errorCard.style.animation = 'slideOut 0.3s ease-out';
|
|
195
|
-
setTimeout(() => errorCard.remove(), 300);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
this.errorContainer.appendChild(errorCard);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Escape HTML to prevent XSS
|
|
203
|
-
*/
|
|
204
|
-
private static escapeHtml(str: string): string {
|
|
205
|
-
const div = document.createElement('div');
|
|
206
|
-
div.textContent = str;
|
|
207
|
-
return div.innerHTML;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Clear all errors
|
|
212
|
-
*/
|
|
213
|
-
static clear() {
|
|
214
|
-
this.errors = [];
|
|
215
|
-
if (this.errorContainer) {
|
|
216
|
-
this.errorContainer.innerHTML = '';
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Enable/disable error handler
|
|
222
|
-
*/
|
|
223
|
-
static setEnabled(enabled: boolean) {
|
|
224
|
-
this.enabled = enabled;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Get all captured errors
|
|
229
|
-
*/
|
|
230
|
-
static getErrors(): ComponentError[] {
|
|
231
|
-
return [...this.errors];
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Utility function to wrap component methods with error handling
|
|
237
|
-
*/
|
|
238
|
-
export function withErrorHandling<T extends (...args: any[]) => any>(
|
|
239
|
-
component: string,
|
|
240
|
-
method: string,
|
|
241
|
-
fn: T,
|
|
242
|
-
context?: any
|
|
243
|
-
): T {
|
|
244
|
-
return ((...args: any[]) => {
|
|
245
|
-
try {
|
|
246
|
-
const result = fn(...args);
|
|
247
|
-
|
|
248
|
-
// Handle async functions
|
|
249
|
-
if (result instanceof Promise) {
|
|
250
|
-
return result.catch((error) => {
|
|
251
|
-
ErrorHandler.captureError({
|
|
252
|
-
component,
|
|
253
|
-
method,
|
|
254
|
-
message: error.message || String(error),
|
|
255
|
-
stack: error.stack,
|
|
256
|
-
timestamp: new Date(),
|
|
257
|
-
context
|
|
258
|
-
});
|
|
259
|
-
throw error;
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return result;
|
|
264
|
-
} catch (error: any) {
|
|
265
|
-
ErrorHandler.captureError({
|
|
266
|
-
component,
|
|
267
|
-
method,
|
|
268
|
-
message: error.message || String(error),
|
|
269
|
-
stack: error.stack,
|
|
270
|
-
timestamp: new Date(),
|
|
271
|
-
context
|
|
272
|
-
});
|
|
273
|
-
throw error;
|
|
274
|
-
}
|
|
275
|
-
}) as T;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Initialize on import
|
|
279
|
-
if (typeof document !== 'undefined') {
|
|
280
|
-
if (document.readyState === 'loading') {
|
|
281
|
-
document.addEventListener('DOMContentLoaded', () => ErrorHandler.init());
|
|
282
|
-
} else {
|
|
283
|
-
ErrorHandler.init();
|
|
284
|
-
}
|
|
285
|
-
}
|