@ait-co/devtools 0.1.10 → 0.1.11
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 +16 -0
- package/dist/panel/index.d.ts +10 -1
- package/dist/panel/index.d.ts.map +1 -1
- package/dist/panel/index.js +71 -16
- package/dist/panel/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -302,6 +302,22 @@ saveUserPreset('My QA scenario', {
|
|
|
302
302
|
});
|
|
303
303
|
```
|
|
304
304
|
|
|
305
|
+
### Panel mount / dispose
|
|
306
|
+
|
|
307
|
+
`@ait-co/devtools/panel`을 import하면 DOM ready 시 자동으로 마운트됩니다. 마운트는 idempotent — 같은 페이지에 여러 번 import되거나 `mount()`를 다시 불러도 토글 버튼은 하나만 떠 있습니다.
|
|
308
|
+
|
|
309
|
+
HMR이나 SPA 라우팅에서 패널을 명시적으로 떼어내야 하는 경우 `disposePanel()`을 사용하세요:
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
import { disposePanel, mount } from '@ait-co/devtools/panel';
|
|
313
|
+
|
|
314
|
+
disposePanel(); // 토글 / 패널 / inject된 <style> / 모든 listener 제거.
|
|
315
|
+
// 호출 전이거나 두 번 호출해도 안전.
|
|
316
|
+
mount(); // 깨끗한 상태로 다시 마운트. 중복 <style>·listener 없음.
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
내부에서 `disposeViewport()`도 함께 호출하므로 viewport 시뮬레이션도 함께 원복됩니다.
|
|
320
|
+
|
|
305
321
|
## Device simulation (Viewport 탭)
|
|
306
322
|
|
|
307
323
|
데스크탑 브라우저에서 모바일 미니앱을 개발할 때, 실제 디바이스 해상도/safe area/노치/홈 인디케이터/앱인토스 nav bar를 반영해 레이아웃을 검증할 수 있습니다.
|
package/dist/panel/index.d.ts
CHANGED
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
* 외부 의존성 없이 vanilla DOM으로 구현.
|
|
7
7
|
*/
|
|
8
8
|
declare function mount(): void;
|
|
9
|
+
/**
|
|
10
|
+
* Pairs with `mount()` (and the existing `disposeViewport()`).
|
|
11
|
+
* Idempotent — safe to call before mount or twice in a row.
|
|
12
|
+
*
|
|
13
|
+
* Removes panel DOM (toggle + panel root), the injected `<style>`, all
|
|
14
|
+
* window/aitState listeners, and resets module-level state. After dispose,
|
|
15
|
+
* `mount()` can be called again to re-mount cleanly.
|
|
16
|
+
*/
|
|
17
|
+
declare function disposePanel(): void;
|
|
9
18
|
//#endregion
|
|
10
|
-
export { mount };
|
|
19
|
+
export { disposePanel, mount };
|
|
11
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/panel/index.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/panel/index.ts"],"mappings":";;;;;;;iBAoNS,KAAA,CAAA;;;;;;;;;iBAkJA,YAAA,CAAA"}
|
package/dist/panel/index.js
CHANGED
|
@@ -2566,6 +2566,23 @@ function renderHomeIndicator() {
|
|
|
2566
2566
|
document.body.appendChild(el);
|
|
2567
2567
|
}
|
|
2568
2568
|
/**
|
|
2569
|
+
* 모든 viewport DOM mutation을 원복하고 aitState 구독도 해제한다.
|
|
2570
|
+
* 외부 consumer가 패널을 동적으로 제거할 때 호출. 호출 후에는 aitState 변경이
|
|
2571
|
+
* DOM에 반영되지 않으므로 안전하게 panel을 떼어낼 수 있다.
|
|
2572
|
+
*/
|
|
2573
|
+
function disposeViewport() {
|
|
2574
|
+
if (typeof document === "undefined") return;
|
|
2575
|
+
if (viewportUnsubscribe) viewportUnsubscribe();
|
|
2576
|
+
const html = document.documentElement;
|
|
2577
|
+
html.classList.remove("ait-viewport-active");
|
|
2578
|
+
html.classList.remove("ait-viewport-framed");
|
|
2579
|
+
removeById(STYLE_ELEMENT_ID);
|
|
2580
|
+
removeNotchElement();
|
|
2581
|
+
removeHomeIndicator();
|
|
2582
|
+
removeNavBarElement();
|
|
2583
|
+
bodyScrollHintEmitted = false;
|
|
2584
|
+
}
|
|
2585
|
+
/**
|
|
2569
2586
|
* DOM에 뷰포트 제약을 적용한다.
|
|
2570
2587
|
* - `html.ait-viewport-active` 클래스로 정적 CSS(styles.ts) 활성화
|
|
2571
2588
|
* - body의 width/height는 preset 값으로, navbar top offset은 safeAreaTop으로 인라인 주입
|
|
@@ -3041,6 +3058,11 @@ let isOpen = false;
|
|
|
3041
3058
|
let panelEl = null;
|
|
3042
3059
|
let bodyEl = null;
|
|
3043
3060
|
let tabsEl = null;
|
|
3061
|
+
let toggleEl = null;
|
|
3062
|
+
let injectedStyle = null;
|
|
3063
|
+
let panelSwitchTabHandler = null;
|
|
3064
|
+
let resizeHandler = null;
|
|
3065
|
+
let aitStateUnsubscribe = null;
|
|
3044
3066
|
let tabRenderers = null;
|
|
3045
3067
|
function refreshPanel() {
|
|
3046
3068
|
if (!bodyEl || !tabsEl) return;
|
|
@@ -3056,26 +3078,19 @@ function refreshPanel() {
|
|
|
3056
3078
|
el.classList.toggle("active", el.getAttribute("data-tab") === currentTab);
|
|
3057
3079
|
});
|
|
3058
3080
|
}
|
|
3059
|
-
if (typeof window !== "undefined") window.addEventListener("__ait:panel-switch-tab", (e) => {
|
|
3060
|
-
currentTab = e.detail.tab;
|
|
3061
|
-
if (panelEl && !panelEl.classList.contains("open")) {
|
|
3062
|
-
isOpen = true;
|
|
3063
|
-
panelEl.classList.add("open");
|
|
3064
|
-
}
|
|
3065
|
-
refreshPanel();
|
|
3066
|
-
});
|
|
3067
3081
|
function mount() {
|
|
3068
3082
|
if (typeof document === "undefined") return;
|
|
3069
3083
|
if (document.querySelector(".ait-panel-toggle")) return;
|
|
3070
3084
|
setDeviceRefreshPanel(refreshPanel);
|
|
3071
3085
|
initViewport();
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
document.head.appendChild(
|
|
3086
|
+
injectedStyle = document.createElement("style");
|
|
3087
|
+
injectedStyle.textContent = PANEL_STYLES;
|
|
3088
|
+
document.head.appendChild(injectedStyle);
|
|
3075
3089
|
const toggle = h("button", {
|
|
3076
3090
|
className: "ait-panel-toggle",
|
|
3077
3091
|
title: "AIT DevTools"
|
|
3078
3092
|
}, "AIT");
|
|
3093
|
+
toggleEl = toggle;
|
|
3079
3094
|
restoreButtonPosition(toggle);
|
|
3080
3095
|
panelEl = h("div", { className: "ait-panel" });
|
|
3081
3096
|
const closeBtn = h("button", {
|
|
@@ -3096,7 +3111,7 @@ function mount() {
|
|
|
3096
3111
|
mockBadge.textContent = aitState.state.panelEditable ? "EDIT" : "READ-ONLY";
|
|
3097
3112
|
refreshPanel();
|
|
3098
3113
|
});
|
|
3099
|
-
const headerRight = h("span", { style: "display:flex;align-items:center;gap:6px" }, mockBadge, h("span", { style: "font-size:11px;color:#666;font-weight:400" }, `v0.1.
|
|
3114
|
+
const headerRight = h("span", { style: "display:flex;align-items:center;gap:6px" }, mockBadge, h("span", { style: "font-size:11px;color:#666;font-weight:400" }, `v0.1.11`), closeBtn);
|
|
3100
3115
|
const header = h("div", { className: "ait-panel-header" }, h("span", {}, "AIT DevTools"), headerRight);
|
|
3101
3116
|
tabsEl = h("div", { className: "ait-panel-tabs" });
|
|
3102
3117
|
for (const tab of TABS) {
|
|
@@ -3124,7 +3139,7 @@ function mount() {
|
|
|
3124
3139
|
}
|
|
3125
3140
|
});
|
|
3126
3141
|
let resizeRaf = 0;
|
|
3127
|
-
|
|
3142
|
+
resizeHandler = () => {
|
|
3128
3143
|
if (resizeRaf) return;
|
|
3129
3144
|
resizeRaf = requestAnimationFrame(() => {
|
|
3130
3145
|
resizeRaf = 0;
|
|
@@ -3132,16 +3147,56 @@ function mount() {
|
|
|
3132
3147
|
saveButtonPosition(toggle);
|
|
3133
3148
|
if (isOpen) updatePanelPosition(toggle);
|
|
3134
3149
|
});
|
|
3135
|
-
}
|
|
3136
|
-
|
|
3150
|
+
};
|
|
3151
|
+
window.addEventListener("resize", resizeHandler);
|
|
3152
|
+
aitStateUnsubscribe = aitState.subscribe(() => {
|
|
3137
3153
|
try {
|
|
3138
3154
|
if (isOpen && (currentTab === "analytics" || currentTab === "storage" || currentTab === "device" || currentTab === "viewport" || currentTab === "iap" || currentTab === "ads" || currentTab === "presets")) refreshPanel();
|
|
3139
3155
|
} catch (err) {
|
|
3140
3156
|
console.error("[@ait-co/devtools] Error in subscribe callback:", err);
|
|
3141
3157
|
}
|
|
3142
3158
|
});
|
|
3159
|
+
panelSwitchTabHandler = (e) => {
|
|
3160
|
+
currentTab = e.detail.tab;
|
|
3161
|
+
if (panelEl && !panelEl.classList.contains("open")) {
|
|
3162
|
+
isOpen = true;
|
|
3163
|
+
panelEl.classList.add("open");
|
|
3164
|
+
}
|
|
3165
|
+
refreshPanel();
|
|
3166
|
+
};
|
|
3167
|
+
window.addEventListener("__ait:panel-switch-tab", panelSwitchTabHandler);
|
|
3143
3168
|
refreshPanel();
|
|
3144
3169
|
}
|
|
3170
|
+
/**
|
|
3171
|
+
* Pairs with `mount()` (and the existing `disposeViewport()`).
|
|
3172
|
+
* Idempotent — safe to call before mount or twice in a row.
|
|
3173
|
+
*
|
|
3174
|
+
* Removes panel DOM (toggle + panel root), the injected `<style>`, all
|
|
3175
|
+
* window/aitState listeners, and resets module-level state. After dispose,
|
|
3176
|
+
* `mount()` can be called again to re-mount cleanly.
|
|
3177
|
+
*/
|
|
3178
|
+
function disposePanel() {
|
|
3179
|
+
if (typeof document === "undefined") return;
|
|
3180
|
+
if (panelSwitchTabHandler && typeof window !== "undefined") window.removeEventListener("__ait:panel-switch-tab", panelSwitchTabHandler);
|
|
3181
|
+
if (resizeHandler && typeof window !== "undefined") window.removeEventListener("resize", resizeHandler);
|
|
3182
|
+
if (aitStateUnsubscribe) aitStateUnsubscribe();
|
|
3183
|
+
toggleEl?.remove();
|
|
3184
|
+
panelEl?.remove();
|
|
3185
|
+
injectedStyle?.remove();
|
|
3186
|
+
disposeViewport();
|
|
3187
|
+
setDeviceRefreshPanel(() => {});
|
|
3188
|
+
panelSwitchTabHandler = null;
|
|
3189
|
+
resizeHandler = null;
|
|
3190
|
+
aitStateUnsubscribe = null;
|
|
3191
|
+
toggleEl = null;
|
|
3192
|
+
panelEl = null;
|
|
3193
|
+
bodyEl = null;
|
|
3194
|
+
tabsEl = null;
|
|
3195
|
+
injectedStyle = null;
|
|
3196
|
+
tabRenderers = null;
|
|
3197
|
+
currentTab = "env";
|
|
3198
|
+
isOpen = false;
|
|
3199
|
+
}
|
|
3145
3200
|
if (typeof document !== "undefined") {
|
|
3146
3201
|
const safeMount = () => {
|
|
3147
3202
|
try {
|
|
@@ -3154,6 +3209,6 @@ if (typeof document !== "undefined") {
|
|
|
3154
3209
|
else safeMount();
|
|
3155
3210
|
}
|
|
3156
3211
|
//#endregion
|
|
3157
|
-
export { mount };
|
|
3212
|
+
export { disposePanel, mount };
|
|
3158
3213
|
|
|
3159
3214
|
//# sourceMappingURL=index.js.map
|