@nine-lab/nine-mu 0.1.334 → 0.1.335
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/dist/css/nine-mu.css +15 -5
- package/dist/nine-mu.js +87 -75
- package/dist/nine-mu.js.map +1 -1
- package/dist/nine-mu.umd.js +3 -3
- package/dist/nine-mu.umd.js.map +1 -1
- package/package.json +1 -1
- package/public/css/nine-mu.css +15 -5
- package/src/components/exception/ExceptionHook.js +36 -41
package/package.json
CHANGED
package/public/css/nine-mu.css
CHANGED
|
@@ -946,6 +946,16 @@
|
|
|
946
946
|
display: block;
|
|
947
947
|
width: 100%;
|
|
948
948
|
height: 100%;
|
|
949
|
+
color: inherit;
|
|
950
|
+
font-size: inherit;
|
|
951
|
+
line-height: inherit;
|
|
952
|
+
|
|
953
|
+
/* 내부 모든 엘리먼트의 여백 계산법을 어드민 기본형에 동기화 */
|
|
954
|
+
.nine-error-container *,
|
|
955
|
+
.nine-error-container *::before,
|
|
956
|
+
.nine-error-container *::after {
|
|
957
|
+
box-sizing: border-box;
|
|
958
|
+
}
|
|
949
959
|
|
|
950
960
|
.nine-error-container {
|
|
951
961
|
padding: 30px;
|
|
@@ -1012,10 +1022,10 @@
|
|
|
1012
1022
|
font-size: 14px;
|
|
1013
1023
|
color: #718096;
|
|
1014
1024
|
}
|
|
1025
|
+
}
|
|
1015
1026
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
}
|
|
1027
|
+
@keyframes pulse {
|
|
1028
|
+
0% { opacity: 0.6; }
|
|
1029
|
+
50% { opacity: 1; }
|
|
1030
|
+
100% { opacity: 0.6; }
|
|
1021
1031
|
}
|
|
@@ -1,34 +1,32 @@
|
|
|
1
1
|
// src/components/exception/ExceptionHook.js
|
|
2
2
|
export class ExceptionHook extends HTMLElement {
|
|
3
|
+
// 💡 [핵심] 비공개 인스턴스 필드는 클래스 최상단에 먼저 선언해야 합니다.
|
|
4
|
+
#lastPath = window.location.pathname;
|
|
5
|
+
#hasError = false;
|
|
6
|
+
#cssPath = "/css/nine-exception-hook.css";
|
|
7
|
+
|
|
3
8
|
constructor() {
|
|
4
9
|
super();
|
|
5
|
-
|
|
6
|
-
this.hasError = false;
|
|
7
|
-
this.cssPath = "/css/nine-exception-hook.css";
|
|
8
|
-
|
|
9
|
-
// 💡 1. Shadow DOM(open 모드) 활성화 및 슬롯 공간 준비
|
|
10
|
+
// Shadow DOM(open 모드) 활성화
|
|
10
11
|
this.attachShadow({ mode: 'open' });
|
|
11
|
-
|
|
12
|
-
// 기본 구조 선언: 자식 엘리먼트들이 위치할 <slot>을 배치합니다.
|
|
13
|
-
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
connectedCallback() {
|
|
17
|
-
|
|
15
|
+
// 기본 스켈레톤 마크업 렌더링
|
|
18
16
|
this.#renderer();
|
|
19
17
|
|
|
20
|
-
//
|
|
18
|
+
// 정적 외부 스타일시트 링크 안전하게 결합
|
|
21
19
|
this.#injectShadowStylesheet();
|
|
22
20
|
|
|
23
|
-
window.addEventListener('error', this.#handleRuntimeError
|
|
24
|
-
window.addEventListener('unhandledrejection', this.#handlePromiseError
|
|
25
|
-
window.addEventListener('popstate', this.#handleLocationChange
|
|
26
|
-
document.addEventListener('click', this.#handleLocationChange
|
|
21
|
+
window.addEventListener('error', this.#handleRuntimeError, true);
|
|
22
|
+
window.addEventListener('unhandledrejection', this.#handlePromiseError, true);
|
|
23
|
+
window.addEventListener('popstate', this.#handleLocationChange);
|
|
24
|
+
document.addEventListener('click', this.#handleLocationChange, true);
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
disconnectedCallback() {
|
|
30
|
-
window.removeEventListener('error', this.#handleRuntimeError);
|
|
31
|
-
window.removeEventListener('unhandledrejection', this.#handlePromiseError);
|
|
28
|
+
window.removeEventListener('error', this.#handleRuntimeError, true);
|
|
29
|
+
window.removeEventListener('unhandledrejection', this.#handlePromiseError, true);
|
|
32
30
|
window.removeEventListener('popstate', this.#handleLocationChange);
|
|
33
31
|
document.removeEventListener('click', this.#handleLocationChange, true);
|
|
34
32
|
}
|
|
@@ -45,52 +43,50 @@ export class ExceptionHook extends HTMLElement {
|
|
|
45
43
|
<div class="nine-hook-core">
|
|
46
44
|
<slot></slot>
|
|
47
45
|
</div>
|
|
46
|
+
<div class="nine-exception-board"></div>
|
|
48
47
|
`;
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
// 💡 2. Shadow Root 내부 전용 스타일 링크 주입 함수
|
|
52
50
|
#injectShadowStylesheet() {
|
|
53
|
-
|
|
51
|
+
// 💡 내부 필드 호출 시에도 모두 this.#형식으로 매핑합니다.
|
|
52
|
+
if (this.shadowRoot.querySelector(`link[href="${this.#cssPath}"]`)) return;
|
|
54
53
|
|
|
55
54
|
const link = document.createElement('link');
|
|
56
55
|
link.rel = 'stylesheet';
|
|
57
|
-
link.href = this
|
|
58
|
-
// head가 아닌 shadowRoot에 집어넣어 글로벌 오염을 완벽히 격리합니다.
|
|
56
|
+
link.href = this.#cssPath;
|
|
59
57
|
this.shadowRoot.appendChild(link);
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
#handleRuntimeError(event) {
|
|
63
|
-
|
|
60
|
+
#handleRuntimeError = (event) => {
|
|
61
|
+
if (this.#hasError) return;
|
|
62
|
+
|
|
64
63
|
if (this.contains(event.target) || event.error) {
|
|
65
64
|
this.#renderError(event.error?.message || event.message, event.error?.stack);
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
#handlePromiseError = (event) => {
|
|
69
|
+
if (this.#hasError) return;
|
|
71
70
|
this.#renderError(event.reason?.message || String(event.reason), event.reason?.stack);
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
#handleLocationChange() {
|
|
73
|
+
#handleLocationChange = () => {
|
|
75
74
|
setTimeout(() => {
|
|
76
|
-
if (this
|
|
75
|
+
if (this.#hasError && this.#lastPath !== window.location.pathname) {
|
|
77
76
|
console.log("✨ [nine-exception-hook] Shadow DOM 내부 예외 청소 및 정상 복구");
|
|
78
|
-
this
|
|
79
|
-
this
|
|
77
|
+
this.#lastPath = window.location.pathname;
|
|
78
|
+
this.#hasError = false;
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (errorBox) errorBox.remove();
|
|
80
|
+
const exceptionBoard = this.shadowRoot.querySelector('.nine-exception-board');
|
|
81
|
+
if (exceptionBoard) exceptionBoard.innerHTML = '';
|
|
84
82
|
|
|
85
|
-
// 숨겨두었던 원래 본문 슬롯 구역을 다시 노출
|
|
86
83
|
const coreContainer = this.shadowRoot.querySelector('.nine-hook-core');
|
|
87
84
|
if (coreContainer) coreContainer.style.display = '';
|
|
88
85
|
}
|
|
89
86
|
}, 10);
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
async
|
|
93
|
-
// 💡 4. DOM 쿼리 탐색을 모두 this가 아닌 this.shadowRoot로 전환
|
|
89
|
+
#sendToAiAction = async (errorMessage, errorStack) => {
|
|
94
90
|
const btn = this.shadowRoot.querySelector('.nine-ai-btn');
|
|
95
91
|
const footer = this.shadowRoot.querySelector('.nine-error-footer');
|
|
96
92
|
if (!btn) return;
|
|
@@ -121,14 +117,15 @@ export class ExceptionHook extends HTMLElement {
|
|
|
121
117
|
}
|
|
122
118
|
|
|
123
119
|
#renderError(errorMessage, errorStack) {
|
|
124
|
-
if (this
|
|
125
|
-
this
|
|
120
|
+
if (this.#hasError) return;
|
|
121
|
+
this.#hasError = true;
|
|
126
122
|
|
|
127
|
-
// 💡 5. 리액트의 원본 마크업을 건드리지 않고, 슬롯이 감싸진 껍데기만 숨김 처리
|
|
128
123
|
const coreContainer = this.shadowRoot.querySelector('.nine-hook-core');
|
|
129
124
|
if (coreContainer) coreContainer.style.display = 'none';
|
|
130
125
|
|
|
131
|
-
|
|
126
|
+
const exceptionBoard = this.shadowRoot.querySelector('.nine-exception-board');
|
|
127
|
+
if (!exceptionBoard) return;
|
|
128
|
+
|
|
132
129
|
const errorHTML = `
|
|
133
130
|
<div class="nine-error-container">
|
|
134
131
|
<h3 class="nine-error-title">⚠️ 시스템 예외 감지 (${this.tagName.toLowerCase()})</h3>
|
|
@@ -144,10 +141,8 @@ export class ExceptionHook extends HTMLElement {
|
|
|
144
141
|
</div>
|
|
145
142
|
`;
|
|
146
143
|
|
|
147
|
-
|
|
148
|
-
this.shadowRoot.insertAdjacentHTML('beforeend', errorHTML);
|
|
144
|
+
exceptionBoard.innerHTML = errorHTML;
|
|
149
145
|
|
|
150
|
-
// 버튼 이벤트 바인딩도 shadowRoot 기준 처리
|
|
151
146
|
const aiBtn = this.shadowRoot.querySelector('.nine-ai-btn');
|
|
152
147
|
if (aiBtn) {
|
|
153
148
|
aiBtn.addEventListener('click', () => this.#sendToAiAction(errorMessage, errorStack));
|