create-nativecore 0.1.1 → 0.2.1
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 +6 -14
- package/bin/index.mjs +403 -431
- package/package.json +3 -2
- package/template/.env.example +28 -0
- package/template/.htmlhintrc +14 -0
- package/template/api/data/dashboard.json +11 -0
- package/template/api/data/users.json +18 -0
- package/template/api/mockApi.js +161 -0
- package/template/assets/icon.svg +13 -0
- package/template/assets/logo.svg +25 -0
- package/template/eslint.config.js +94 -0
- package/template/index.html +137 -0
- package/template/manifest.json +19 -0
- package/template/public/.well-known/security.txt +9 -0
- package/template/public/_headers +24 -0
- package/template/public/_redirects +14 -0
- package/template/public/assets/icon.svg +13 -0
- package/template/public/assets/logo.svg +25 -0
- package/template/public/manifest.json +19 -0
- package/template/public/robots.txt +13 -0
- package/template/public/sitemap.xml +27 -0
- package/template/scripts/build-for-bots.mjs +121 -0
- package/template/scripts/convert-to-ts.mjs +106 -0
- package/template/scripts/fix-encoding.mjs +38 -0
- package/template/scripts/fix-svg-paths.mjs +32 -0
- package/template/scripts/generate-cf-router.mjs +52 -0
- package/template/scripts/inject-dev-tools.mjs +41 -0
- package/template/scripts/inject-version.mjs +65 -0
- package/template/scripts/make-component.mjs +445 -0
- package/template/scripts/make-component.mjs.backup +432 -0
- package/template/scripts/make-controller.mjs +119 -0
- package/template/scripts/make-core-component.mjs +303 -0
- package/template/scripts/make-view.mjs +346 -0
- package/template/scripts/minify.mjs +71 -0
- package/template/scripts/prepare-static-assets.mjs +141 -0
- package/template/scripts/prompt-bot-build.mjs +223 -0
- package/template/scripts/remove-component.mjs +170 -0
- package/template/scripts/remove-core-component.mjs +156 -0
- package/template/scripts/remove-dev.mjs +13 -0
- package/template/scripts/remove-view.mjs +200 -0
- package/template/scripts/strip-dev-blocks.mjs +30 -0
- package/template/scripts/watch-compile.mjs +69 -0
- package/template/server.js +1066 -0
- package/template/src/app.ts +115 -0
- package/template/src/components/appRegistry.ts +8 -0
- package/template/src/components/core/app-footer.ts +27 -0
- package/template/src/components/core/app-header.ts +175 -0
- package/template/src/components/core/app-sidebar.ts +238 -0
- package/template/src/components/core/loading-spinner.ts +25 -0
- package/template/src/components/core/nc-a.ts +313 -0
- package/template/src/components/core/nc-accordion.ts +186 -0
- package/template/src/components/core/nc-alert.ts +153 -0
- package/template/src/components/core/nc-animation.ts +1150 -0
- package/template/src/components/core/nc-autocomplete.ts +271 -0
- package/template/src/components/core/nc-avatar-group.ts +113 -0
- package/template/src/components/core/nc-avatar.ts +148 -0
- package/template/src/components/core/nc-badge.ts +86 -0
- package/template/src/components/core/nc-bottom-nav.ts +214 -0
- package/template/src/components/core/nc-breadcrumb.ts +96 -0
- package/template/src/components/core/nc-button.ts +307 -0
- package/template/src/components/core/nc-card.ts +160 -0
- package/template/src/components/core/nc-checkbox.ts +282 -0
- package/template/src/components/core/nc-chip.ts +115 -0
- package/template/src/components/core/nc-code.ts +314 -0
- package/template/src/components/core/nc-collapsible.ts +154 -0
- package/template/src/components/core/nc-color-picker.ts +268 -0
- package/template/src/components/core/nc-copy-button.ts +119 -0
- package/template/src/components/core/nc-date-picker.ts +443 -0
- package/template/src/components/core/nc-div.ts +280 -0
- package/template/src/components/core/nc-divider.ts +81 -0
- package/template/src/components/core/nc-drawer.ts +230 -0
- package/template/src/components/core/nc-dropdown.ts +178 -0
- package/template/src/components/core/nc-empty-state.ts +134 -0
- package/template/src/components/core/nc-file-upload.ts +354 -0
- package/template/src/components/core/nc-form.ts +312 -0
- package/template/src/components/core/nc-image.ts +184 -0
- package/template/src/components/core/nc-input.ts +383 -0
- package/template/src/components/core/nc-kbd.ts +48 -0
- package/template/src/components/core/nc-menu-item.ts +193 -0
- package/template/src/components/core/nc-menu.ts +376 -0
- package/template/src/components/core/nc-modal.ts +238 -0
- package/template/src/components/core/nc-nav-item.ts +151 -0
- package/template/src/components/core/nc-number-input.ts +350 -0
- package/template/src/components/core/nc-otp-input.ts +235 -0
- package/template/src/components/core/nc-pagination.ts +178 -0
- package/template/src/components/core/nc-popover.ts +260 -0
- package/template/src/components/core/nc-progress-circular.ts +119 -0
- package/template/src/components/core/nc-progress.ts +134 -0
- package/template/src/components/core/nc-radio.ts +235 -0
- package/template/src/components/core/nc-rating.ts +266 -0
- package/template/src/components/core/nc-rich-text.ts +283 -0
- package/template/src/components/core/nc-scroll-top.ts +116 -0
- package/template/src/components/core/nc-select.ts +452 -0
- package/template/src/components/core/nc-skeleton.ts +107 -0
- package/template/src/components/core/nc-slider.ts +285 -0
- package/template/src/components/core/nc-snackbar.ts +230 -0
- package/template/src/components/core/nc-splash.ts +343 -0
- package/template/src/components/core/nc-stepper.ts +247 -0
- package/template/src/components/core/nc-switch.ts +281 -0
- package/template/src/components/core/nc-tab-item.ts +138 -0
- package/template/src/components/core/nc-table.ts +279 -0
- package/template/src/components/core/nc-tabs.ts +554 -0
- package/template/src/components/core/nc-tag-input.ts +279 -0
- package/template/src/components/core/nc-textarea.ts +216 -0
- package/template/src/components/core/nc-time-picker.ts +438 -0
- package/template/src/components/core/nc-timeline.ts +186 -0
- package/template/src/components/core/nc-tooltip.ts +143 -0
- package/template/src/components/frameworkRegistry.ts +68 -0
- package/template/src/components/preloadRegistry.ts +28 -0
- package/template/src/components/registry.ts +8 -0
- package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
- package/template/src/constants/apiEndpoints.ts +27 -0
- package/template/src/constants/errorMessages.ts +23 -0
- package/template/src/constants/index.ts +8 -0
- package/template/src/constants/routePaths.ts +15 -0
- package/template/src/constants/storageKeys.ts +18 -0
- package/template/src/controllers/dashboard.controller.ts +200 -0
- package/template/src/controllers/home.controller.ts +21 -0
- package/template/src/controllers/index.ts +11 -0
- package/template/src/controllers/login.controller.ts +131 -0
- package/template/src/core/component.ts +354 -0
- package/template/src/core/errorHandler.ts +85 -0
- package/template/src/core/gpu-animation.ts +604 -0
- package/template/src/core/http.ts +173 -0
- package/template/src/core/lazyComponents.ts +90 -0
- package/template/src/core/router.ts +653 -0
- package/template/src/core/signals.ts +146 -0
- package/template/src/core/state.ts +248 -0
- package/template/src/dev/component-editor.ts +1363 -0
- package/template/src/dev/component-overlay.ts +278 -0
- package/template/src/dev/context-menu.ts +223 -0
- package/template/src/dev/denc-tools.ts +250 -0
- package/template/src/dev/hmr.ts +189 -0
- package/template/src/dev/nfbs.code-workspace +27 -0
- package/template/src/dev/outline-panel.ts +1247 -0
- package/template/src/middleware/auth.middleware.ts +23 -0
- package/template/src/routes/routes.ts +38 -0
- package/template/src/services/api.service.ts +394 -0
- package/template/src/services/auth.service.ts +176 -0
- package/template/src/services/index.ts +8 -0
- package/template/src/services/logger.service.ts +74 -0
- package/template/src/services/storage.service.ts +88 -0
- package/template/src/stores/appStore.ts +57 -0
- package/template/src/stores/uiStore.ts +36 -0
- package/template/src/styles/core-variables.css +219 -0
- package/template/src/styles/core.css +710 -0
- package/template/src/styles/main.css +3164 -0
- package/template/src/styles/variables.css +152 -0
- package/template/src/types/global.d.ts +47 -0
- package/template/src/utils/cacheBuster.ts +20 -0
- package/template/src/utils/dom.ts +149 -0
- package/template/src/utils/events.ts +203 -0
- package/template/src/utils/form.ts +176 -0
- package/template/src/utils/formatters.ts +169 -0
- package/template/src/utils/helpers.ts +195 -0
- package/template/src/utils/markdown.ts +307 -0
- package/template/src/utils/sidebar.ts +96 -0
- package/template/src/utils/smoothScroll.ts +85 -0
- package/template/src/utils/templates.ts +23 -0
- package/template/src/utils/validation.ts +73 -0
- package/template/src/views/protected/dashboard.html +293 -0
- package/template/src/views/public/home.html +150 -0
- package/template/src/views/public/login.html +102 -0
- package/template/tests/unit/component.test.ts +87 -0
- package/template/tests/unit/computed.test.ts +79 -0
- package/template/tests/unit/form.test.ts +68 -0
- package/template/tests/unit/formatters.test.ts +49 -0
- package/template/tests/unit/lazy-components.test.ts +59 -0
- package/template/tests/unit/markdown.test.ts +62 -0
- package/template/tests/unit/router.test.ts +112 -0
- package/template/tests/unit/signals.test.ts +54 -0
- package/template/tests/unit/validation.test.ts +50 -0
- package/template/tsconfig.build.json +21 -0
- package/template/tsconfig.json +51 -0
- package/template/vitest.config.ts +36 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Component Class for Web Components
|
|
3
|
+
* Extend this to create reusable custom HTML elements
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Types
|
|
7
|
+
export interface ComponentState {
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ComponentConstructor {
|
|
12
|
+
useShadowDOM?: boolean;
|
|
13
|
+
observedAttributes?: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type RenderContainer = HTMLElement | ShadowRoot | Element | DocumentFragment;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Base Component class for creating Web Components
|
|
20
|
+
*/
|
|
21
|
+
export class Component extends HTMLElement {
|
|
22
|
+
state: ComponentState;
|
|
23
|
+
protected _mounted: boolean;
|
|
24
|
+
shadowRoot!: ShadowRoot | null;
|
|
25
|
+
|
|
26
|
+
static useShadowDOM?: boolean;
|
|
27
|
+
static observedAttributes?: string[];
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
super();
|
|
31
|
+
this.state = {};
|
|
32
|
+
this._mounted = false;
|
|
33
|
+
|
|
34
|
+
// Attach shadow root only if component opts-in via static property
|
|
35
|
+
const constructor = this.constructor as typeof Component;
|
|
36
|
+
if (constructor.useShadowDOM) {
|
|
37
|
+
this.attachShadow({ mode: 'open' });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Called when component is added to DOM
|
|
43
|
+
*/
|
|
44
|
+
connectedCallback(): void {
|
|
45
|
+
try {
|
|
46
|
+
this.render();
|
|
47
|
+
this.onMount();
|
|
48
|
+
this._mounted = true;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`Error rendering ${this.tagName.toLowerCase()}:`, error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Called when component is removed from DOM
|
|
56
|
+
*/
|
|
57
|
+
disconnectedCallback(): void {
|
|
58
|
+
this.onUnmount();
|
|
59
|
+
this._mounted = false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Watch for attribute changes
|
|
64
|
+
*/
|
|
65
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {
|
|
66
|
+
if (oldValue !== newValue && this._mounted) {
|
|
67
|
+
this.onAttributeChange(name, oldValue, newValue);
|
|
68
|
+
this.render();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Update component state and re-render
|
|
74
|
+
*/
|
|
75
|
+
setState(newState: Partial<ComponentState>): void {
|
|
76
|
+
this.state = { ...this.state, ...newState };
|
|
77
|
+
if (this._mounted) {
|
|
78
|
+
this.render();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get component state
|
|
84
|
+
*/
|
|
85
|
+
getState(): ComponentState {
|
|
86
|
+
return { ...this.state };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Render the component
|
|
91
|
+
*/
|
|
92
|
+
render(): void {
|
|
93
|
+
const html = this.template();
|
|
94
|
+
const target = this.shadowRoot ?? this;
|
|
95
|
+
patchHTML(target, html);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Template method - Override to return HTML string
|
|
100
|
+
*/
|
|
101
|
+
template(): string {
|
|
102
|
+
return '';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Lifecycle hook - called when component mounts
|
|
107
|
+
*/
|
|
108
|
+
onMount(): void {
|
|
109
|
+
// Override in child class
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Lifecycle hook - called when component unmounts
|
|
114
|
+
*/
|
|
115
|
+
onUnmount(): void {
|
|
116
|
+
// Override in child class
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Lifecycle hook - called when attributes change
|
|
121
|
+
*/
|
|
122
|
+
onAttributeChange(_name: string, _oldValue: string | null, _newValue: string | null): void {
|
|
123
|
+
// Override in child class
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Helper: Query selector within component's shadow DOM or light DOM
|
|
128
|
+
*/
|
|
129
|
+
$<E extends Element = Element>(selector: string): E | null {
|
|
130
|
+
return this.shadowRoot
|
|
131
|
+
? this.shadowRoot.querySelector<E>(selector)
|
|
132
|
+
: this.querySelector<E>(selector);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Helper: Query selector all within component's shadow DOM or light DOM
|
|
137
|
+
*/
|
|
138
|
+
$$<E extends Element = Element>(selector: string): NodeListOf<E> {
|
|
139
|
+
return this.shadowRoot
|
|
140
|
+
? this.shadowRoot.querySelectorAll<E>(selector)
|
|
141
|
+
: this.querySelectorAll<E>(selector);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Helper: Add event listener with auto cleanup
|
|
146
|
+
*/
|
|
147
|
+
on<K extends keyof HTMLElementEventMap>(
|
|
148
|
+
event: K,
|
|
149
|
+
handler: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any
|
|
150
|
+
): void;
|
|
151
|
+
on<K extends keyof HTMLElementEventMap>(
|
|
152
|
+
event: K,
|
|
153
|
+
selector: string,
|
|
154
|
+
handler: (this: Element, ev: HTMLElementEventMap[K]) => any
|
|
155
|
+
): void;
|
|
156
|
+
on<K extends keyof HTMLElementEventMap>(
|
|
157
|
+
event: K,
|
|
158
|
+
selectorOrHandler: string | ((this: HTMLElement, ev: HTMLElementEventMap[K]) => any),
|
|
159
|
+
handler?: (this: Element, ev: HTMLElementEventMap[K]) => any
|
|
160
|
+
): void {
|
|
161
|
+
if (typeof selectorOrHandler === 'function') {
|
|
162
|
+
this.addEventListener(event, selectorOrHandler as EventListener);
|
|
163
|
+
} else if (handler) {
|
|
164
|
+
this.addEventListener(event, ((e: Event) => {
|
|
165
|
+
const target = e.target as Element;
|
|
166
|
+
if (target.matches(selectorOrHandler)) {
|
|
167
|
+
handler.call(target, e as any);
|
|
168
|
+
}
|
|
169
|
+
}) as EventListener);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Helper: Emit custom event
|
|
175
|
+
*/
|
|
176
|
+
emitEvent<T = any>(
|
|
177
|
+
eventName: string,
|
|
178
|
+
detail: T = {} as T,
|
|
179
|
+
options: Partial<CustomEventInit<T>> = {}
|
|
180
|
+
): boolean {
|
|
181
|
+
const event = new CustomEvent(eventName, {
|
|
182
|
+
detail,
|
|
183
|
+
bubbles: options.bubbles !== undefined ? options.bubbles : true,
|
|
184
|
+
composed: options.composed !== undefined ? options.composed : true,
|
|
185
|
+
cancelable: options.cancelable !== undefined ? options.cancelable : false
|
|
186
|
+
});
|
|
187
|
+
return this.dispatchEvent(event);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Helper: Get attribute or default value
|
|
192
|
+
*/
|
|
193
|
+
attr(name: string, defaultValue: string | null = null): string | null {
|
|
194
|
+
return this.getAttribute(name) || defaultValue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Helper: Set attribute
|
|
199
|
+
*/
|
|
200
|
+
setAttr(name: string, value: string): void {
|
|
201
|
+
this.setAttribute(name, value);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Parse the latest template HTML into a fragment and reconcile it against the
|
|
207
|
+
* existing DOM so stable nodes, focus state, and form control state survive updates.
|
|
208
|
+
*/
|
|
209
|
+
function patchHTML(target: HTMLElement | ShadowRoot, html: string): void {
|
|
210
|
+
const range = document.createRange();
|
|
211
|
+
range.selectNodeContents(target);
|
|
212
|
+
const fragment = range.createContextualFragment(html);
|
|
213
|
+
reconcileChildren(target, fragment);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Reconcile child nodes by index and patch matching nodes in place when possible.
|
|
218
|
+
*/
|
|
219
|
+
function reconcileChildren(target: RenderContainer, source: RenderContainer): void {
|
|
220
|
+
const currentNodes = Array.from(target.childNodes);
|
|
221
|
+
const nextNodes = Array.from(source.childNodes);
|
|
222
|
+
const maxLength = Math.max(currentNodes.length, nextNodes.length);
|
|
223
|
+
|
|
224
|
+
for (let index = 0; index < maxLength; index++) {
|
|
225
|
+
const currentNode = currentNodes[index];
|
|
226
|
+
const nextNode = nextNodes[index];
|
|
227
|
+
|
|
228
|
+
if (!currentNode && nextNode) {
|
|
229
|
+
target.appendChild(nextNode);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (currentNode && !nextNode) {
|
|
234
|
+
currentNode.remove();
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (currentNode && nextNode) {
|
|
239
|
+
reconcileNode(currentNode, nextNode);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function reconcileNode(currentNode: Node, nextNode: Node): void {
|
|
245
|
+
const parent = currentNode.parentNode;
|
|
246
|
+
if (!parent) return;
|
|
247
|
+
|
|
248
|
+
if (!canPatchNode(currentNode, nextNode)) {
|
|
249
|
+
parent.replaceChild(nextNode, currentNode);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (currentNode.nodeType === Node.TEXT_NODE || currentNode.nodeType === Node.COMMENT_NODE) {
|
|
254
|
+
if (currentNode.textContent !== nextNode.textContent) {
|
|
255
|
+
currentNode.textContent = nextNode.textContent;
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const currentElement = currentNode as Element;
|
|
261
|
+
const nextElement = nextNode as Element;
|
|
262
|
+
|
|
263
|
+
syncAttributes(currentElement, nextElement);
|
|
264
|
+
syncFormControlState(currentElement, nextElement);
|
|
265
|
+
reconcileChildren(currentElement, nextElement);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function canPatchNode(currentNode: Node, nextNode: Node): boolean {
|
|
269
|
+
if (currentNode.nodeType !== nextNode.nodeType) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (currentNode.nodeType !== Node.ELEMENT_NODE) {
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return (currentNode as Element).tagName === (nextNode as Element).tagName;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function syncAttributes(currentElement: Element, nextElement: Element): void {
|
|
281
|
+
const currentAttributes = Array.from(currentElement.attributes);
|
|
282
|
+
const nextAttributes = Array.from(nextElement.attributes);
|
|
283
|
+
|
|
284
|
+
currentAttributes.forEach(attribute => {
|
|
285
|
+
if (!nextElement.hasAttribute(attribute.name)) {
|
|
286
|
+
currentElement.removeAttribute(attribute.name);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
nextAttributes.forEach(attribute => {
|
|
291
|
+
if (currentElement.getAttribute(attribute.name) !== attribute.value) {
|
|
292
|
+
currentElement.setAttribute(attribute.name, attribute.value);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Preserve input values, checked state, and focus where possible while syncing template updates.
|
|
299
|
+
*/
|
|
300
|
+
function syncFormControlState(currentElement: Element, nextElement: Element): void {
|
|
301
|
+
if (currentElement instanceof HTMLInputElement && nextElement instanceof HTMLInputElement) {
|
|
302
|
+
syncInputElement(currentElement, nextElement);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (currentElement instanceof HTMLTextAreaElement && nextElement instanceof HTMLTextAreaElement) {
|
|
307
|
+
if (!isFocusedElement(currentElement) && currentElement.value !== nextElement.value) {
|
|
308
|
+
currentElement.value = nextElement.value;
|
|
309
|
+
}
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (currentElement instanceof HTMLSelectElement && nextElement instanceof HTMLSelectElement) {
|
|
314
|
+
if (!isFocusedElement(currentElement) && currentElement.value !== nextElement.value) {
|
|
315
|
+
currentElement.value = nextElement.value;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function syncInputElement(currentElement: HTMLInputElement, nextElement: HTMLInputElement): void {
|
|
321
|
+
if (currentElement.type === 'checkbox' || currentElement.type === 'radio') {
|
|
322
|
+
currentElement.checked = nextElement.checked;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!isFocusedElement(currentElement) && currentElement.value !== nextElement.value) {
|
|
326
|
+
currentElement.value = nextElement.value;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function isFocusedElement(element: Element): boolean {
|
|
331
|
+
const root = element.getRootNode();
|
|
332
|
+
|
|
333
|
+
if (root instanceof ShadowRoot) {
|
|
334
|
+
return root.activeElement === element;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return element.ownerDocument?.activeElement === element;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Helper function to define custom element
|
|
342
|
+
*/
|
|
343
|
+
export function defineComponent<T extends CustomElementConstructor>(
|
|
344
|
+
tagName: string,
|
|
345
|
+
componentClass: T
|
|
346
|
+
): void {
|
|
347
|
+
try {
|
|
348
|
+
if (!customElements.get(tagName)) {
|
|
349
|
+
customElements.define(tagName, componentClass);
|
|
350
|
+
}
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.error(`Error defining ${tagName}:`, error);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Error Handler
|
|
3
|
+
*/
|
|
4
|
+
import { ERROR_MESSAGES } from '../constants/errorMessages.js';
|
|
5
|
+
|
|
6
|
+
export interface ErrorInfo {
|
|
7
|
+
message: string;
|
|
8
|
+
stack?: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
context: Record<string, any>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type ErrorListener = (errorInfo: ErrorInfo) => void;
|
|
14
|
+
|
|
15
|
+
class ErrorHandler {
|
|
16
|
+
private errorListeners = new Set<ErrorListener>();
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
this.setupGlobalHandlers();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private setupGlobalHandlers(): void {
|
|
23
|
+
window.addEventListener('error', (event) => {
|
|
24
|
+
console.error('Unhandled error:', event.error);
|
|
25
|
+
this.handleError(event.error);
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
30
|
+
console.error('Unhandled promise rejection:', event.reason);
|
|
31
|
+
this.handleError(event.reason);
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
handleError(error: Error | string, context: Record<string, any> = {}): void {
|
|
37
|
+
const errorInfo: ErrorInfo = {
|
|
38
|
+
message: this.getErrorMessage(error),
|
|
39
|
+
stack: (error as Error)?.stack,
|
|
40
|
+
timestamp: new Date().toISOString(),
|
|
41
|
+
context,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (this.isDevelopment()) {
|
|
45
|
+
console.error('Error handled:', errorInfo);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.notifyListeners(errorInfo);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private getErrorMessage(error: Error | string): string {
|
|
52
|
+
if (typeof error === 'string') return error;
|
|
53
|
+
|
|
54
|
+
if (error?.message?.includes('fetch') || error?.message?.includes('network')) {
|
|
55
|
+
return ERROR_MESSAGES.NETWORK_ERROR;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (error?.message?.includes('timeout')) {
|
|
59
|
+
return ERROR_MESSAGES.TIMEOUT;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return error?.message || ERROR_MESSAGES.UNKNOWN_ERROR;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
onError(listener: ErrorListener): () => void {
|
|
66
|
+
this.errorListeners.add(listener);
|
|
67
|
+
return () => this.errorListeners.delete(listener);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private notifyListeners(errorInfo: ErrorInfo): void {
|
|
71
|
+
this.errorListeners.forEach(listener => {
|
|
72
|
+
try {
|
|
73
|
+
listener(errorInfo);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error('Error in error listener:', err);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private isDevelopment(): boolean {
|
|
81
|
+
return window.location.hostname === 'localhost';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default new ErrorHandler();
|