@solhun/feedback-kit-web 0.2.2 → 0.2.3
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/index.cjs +43 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +43 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -74,6 +74,44 @@ var import_feedback_kit_core5 = require("@solhun/feedback-kit-core");
|
|
|
74
74
|
var import_feedback_kit_core6 = require("@solhun/feedback-kit-core");
|
|
75
75
|
|
|
76
76
|
// src/react-tree.ts
|
|
77
|
+
var FRAMEWORK_INTERNALS = /* @__PURE__ */ new Set([
|
|
78
|
+
// Next.js App Router
|
|
79
|
+
"AppRouter",
|
|
80
|
+
"InnerLayoutRouter",
|
|
81
|
+
"OuterLayoutRouter",
|
|
82
|
+
"LayoutRouterContext",
|
|
83
|
+
"RenderFromTemplateContext",
|
|
84
|
+
"TemplateContext",
|
|
85
|
+
"SegmentStateProvider",
|
|
86
|
+
"SegmentViewNode",
|
|
87
|
+
"ScrollAndMaybeFocusHandler",
|
|
88
|
+
"InnerScrollAndFocusHandler",
|
|
89
|
+
"InnerScrollAndFocusHandlerOld",
|
|
90
|
+
"RedirectBoundary",
|
|
91
|
+
"RedirectErrorBoundary",
|
|
92
|
+
"NotFoundBoundary",
|
|
93
|
+
"NotFoundErrorBoundary",
|
|
94
|
+
"HTTPAccessFallbackBoundary",
|
|
95
|
+
"HTTPAccessFallbackErrorBoundary",
|
|
96
|
+
"DevRootHTTPAccessFallbackBoundary",
|
|
97
|
+
"LoadingBoundary",
|
|
98
|
+
"ErrorBoundaryHandler",
|
|
99
|
+
"MetadataBoundary",
|
|
100
|
+
"ViewportBoundary",
|
|
101
|
+
"OutletBoundary",
|
|
102
|
+
// 개발 전용 오버레이 — 프로덕션엔 없지만 dev 제보에는 항상 낀다
|
|
103
|
+
"HotReload",
|
|
104
|
+
"AppDevOverlay",
|
|
105
|
+
"AppDevOverlayErrorBoundary",
|
|
106
|
+
"ReactDevOverlay",
|
|
107
|
+
"DevOverlay",
|
|
108
|
+
"Router",
|
|
109
|
+
"ServerRoot",
|
|
110
|
+
"RSCComponent"
|
|
111
|
+
]);
|
|
112
|
+
function isFrameworkName(name) {
|
|
113
|
+
return FRAMEWORK_INTERNALS.has(name) || name.startsWith("__next");
|
|
114
|
+
}
|
|
77
115
|
function componentName(type) {
|
|
78
116
|
if (typeof type === "function") {
|
|
79
117
|
const fn = type;
|
|
@@ -96,13 +134,15 @@ function fiberOf(node) {
|
|
|
96
134
|
}
|
|
97
135
|
return null;
|
|
98
136
|
}
|
|
99
|
-
function reactComponentPath(node, limit =
|
|
137
|
+
function reactComponentPath(node, limit = 5) {
|
|
100
138
|
try {
|
|
101
139
|
let fiber = fiberOf(node);
|
|
102
140
|
const names = [];
|
|
103
|
-
|
|
141
|
+
let depth = 0;
|
|
142
|
+
while (fiber && names.length < limit && depth < 200) {
|
|
143
|
+
depth += 1;
|
|
104
144
|
const name = componentName(fiber.elementType ?? fiber.type);
|
|
105
|
-
if (name && names[0] !== name) names.unshift(name);
|
|
145
|
+
if (name && !isFrameworkName(name) && names[0] !== name) names.unshift(name);
|
|
106
146
|
fiber = fiber.return ?? null;
|
|
107
147
|
}
|
|
108
148
|
return names;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/react-tree.ts","../src/element-info.ts","../src/marker-store.ts","../src/picking.ts","../src/screenshot.ts","../src/widget.ts","../src/feedback-kit.tsx","../src/storage.ts","../src/providers.ts"],"sourcesContent":["// @solhun/feedback-kit-web\n//\n// 웹용 피드백 위젯 엔트리.\n//\n// 화면 지도·리포트 모달·핀 좌표 계산은 전부 코어에 있다. 여기 있는 건 웹에서만 뜻이 있는 것들 —\n// DOM 요소를 제보에 실을 형태로 옮기고(`element-info`), 지목 모드와 마커를 브라우저 저장소에\n// 남기고(`marker-store`), 페이지 클릭을 가로채 주석을 다는 것(`picking`) — 뿐이다.\n\nexport type {\n FeedbackReport,\n SubmitResult,\n FeedbackContext,\n FeedbackUser,\n FeedbackStorage,\n FeedbackAdapter,\n ElementInfo,\n FeedbackPin,\n} from \"@solhun/feedback-kit-core\";\n\n// 설정은 코어가 정의한다. 웹/앱이 각자 다른 설정 타입을 갖지 않는 게 중요해서\n// (같은 설정을 두 곳에 적으면 갈라진다) 여기서는 다시 내보내기만 한다.\nexport type {\n FeedbackConfig,\n ResolvedConfig,\n Visibility,\n VisibilityEnv,\n VisibilityFn,\n WidgetPosition,\n} from \"@solhun/feedback-kit-core\";\nexport { resolveConfig, shouldShowWidget } from \"@solhun/feedback-kit-core\";\n\n// 빌드 플러그인이 심은 소스 경로를 읽는 쪽. 플러그인이 없으면 조용히 null 이다.\nexport { parseSourceAttr, SOURCE_ATTR, sourceFromElement } from \"@solhun/feedback-kit-core\";\n\n// 코어의 헤드리스 위젯 계층. 웹 컴포넌트가 이 상태기계를 그대로 그린다.\nexport type {\n ModalSubmitStatus,\n ReportModalState,\n ScreenshotStatus,\n WidgetScreen,\n WidgetState,\n} from \"@solhun/feedback-kit-core\";\nexport {\n COMMENT_MAX_CHARS,\n COMMENT_REQUIRED_MESSAGE,\n COMMENT_TOO_LONG_MESSAGE,\n denormalizePin,\n FLOATING_BUTTON_ID,\n MODAL_ACTION_PICK,\n normalizePin,\n ReportModalController,\n SCREENSHOT_FAILED_MESSAGE,\n SUBMIT_DONE_MESSAGE,\n SUBMIT_PENDING_MESSAGE,\n WidgetController,\n} from \"@solhun/feedback-kit-core\";\n\n// ── 웹 전용 ────────────────────────────────────────────────────────────────\n\nexport {\n cssSelectorPath,\n describeElement,\n ELEMENT_TEXT_MAX_CHARS,\n SELECTOR_MAX_DEPTH,\n visibleText,\n} from \"./element-info.js\";\n\nexport type { MarkerStatus, StorageLike, StoredMarker } from \"./marker-store.js\";\nexport {\n MARKER_KEY_PREFIX,\n MarkerStore,\n MAX_MARKERS_PER_PATH,\n PICKING_MODE_KEY,\n} from \"./marker-store.js\";\n\nexport type {\n AnnotationPopupState,\n ElementPickingOpts,\n PickingListener,\n PickingQueueLike,\n PickingState,\n} from \"./picking.js\";\nexport { ElementPickingController, OWN_UI_ATTR } from \"./picking.js\";\n\nexport { captureWebScreenshot, isOwnUi, reencodeWebScreenshot } from \"./screenshot.js\";\n\n// 지목한 요소의 React 컴포넌트 경로. 제보에서 코드를 찾는 시간을 가장 크게 줄인다.\nexport { reactComponentPath, reactComponentSummary } from \"./react-tree.js\";\n\nexport type { WebWidget, WebWidgetOpts } from \"./widget.js\";\nexport { createWebWidget } from \"./widget.js\";\n\nexport type { FeedbackKitProps } from \"./feedback-kit.js\";\nexport { FeedbackKit } from \"./feedback-kit.js\";\n\n// 웹 기본 저장소(localStorage). 앱마다 다시 쓰지 않게 패키지가 제공한다.\nexport { createWebStorage } from \"./storage.js\";\n\n// 웹 기본 컨텍스트 제공자(getUrl 등). 붙이는 쪽이 잊으면 제보가 버려진다.\nexport { webContextProviders } from \"./providers.js\";\n","// 지목한 요소가 **어느 React 컴포넌트에 속하는지** 알아낸다.\n//\n// 왜 필요한가: `div.flex.items-center` 같은 선택자만 오면 고치는 사람이 그걸\n// 코드에서 다시 찾아야 한다. `LoginForm > SubmitButton` 이 오면 바로 파일이 열린다.\n// 제보 하나당 이 한 줄이 탐색 시간을 가장 크게 줄인다.\n//\n// 방법: React 는 DOM 노드에 `__reactFiber$<랜덤>` 프로퍼티를 심는다(공개 API 가 아니다).\n// 거기서 fiber 를 잡아 부모로 거슬러 올라가며 컴포넌트 이름을 모은다.\n//\n// 한계를 미리 적어둔다:\n// - **프로덕션 빌드에서는 이름이 minify 되어 `t`·`Xe` 처럼 나올 수 있다.** 그래도 트리\n// 모양은 단서가 되므로 버리지 않는다. 이름을 살리려면 빌드에서 displayName 을 남긴다.\n// - React 19 도 같은 프로퍼티를 쓰지만 내부 구조라 언제든 바뀔 수 있다. 못 읽으면\n// **조용히 빈 배열**을 돌려준다 — 제보 자체를 막지 않는다.\n\n/** 호스트 요소(div, span…)는 건너뛴다. 우리가 원하는 건 사람이 지은 컴포넌트 이름이다. */\nfunction componentName(type: unknown): string | null {\n if (typeof type === \"function\") {\n const fn = type as { displayName?: string; name?: string };\n return fn.displayName || fn.name || null;\n }\n if (type !== null && typeof type === \"object\") {\n // memo/forwardRef 등 래퍼. 한 겹 안쪽을 본다.\n const wrapped = type as { displayName?: string; render?: unknown; type?: unknown };\n if (wrapped.displayName) return wrapped.displayName;\n if (wrapped.render) return componentName(wrapped.render);\n if (wrapped.type) return componentName(wrapped.type);\n }\n return null;\n}\n\nfunction fiberOf(node: Element): Record<string, unknown> | null {\n // Object.keys 가 아니라 getOwnPropertyNames — React 는 열거 가능하게 심지만,\n // 그렇지 않은 환경/버전에서도 찾을 수 있어야 한다.\n for (const key of Object.getOwnPropertyNames(node)) {\n if (key.startsWith(\"__reactFiber$\") || key.startsWith(\"__reactInternalInstance$\")) {\n const value = (node as unknown as Record<string, unknown>)[key];\n if (value !== null && typeof value === \"object\") return value as Record<string, unknown>;\n }\n }\n return null;\n}\n\n/**\n * 바깥 → 안쪽 순서의 컴포넌트 이름 목록.\n * 예: `[\"App\", \"LoginPage\", \"LoginForm\", \"SubmitButton\"]`\n *\n * @param limit 너무 깊은 트리는 자른다. 앱 루트까지 20 겹이면 이미 충분한 단서다.\n */\nexport function reactComponentPath(node: Element, limit = 20): string[] {\n try {\n let fiber = fiberOf(node);\n const names: string[] = [];\n while (fiber && names.length < limit) {\n const name = componentName(fiber.elementType ?? fiber.type);\n // 같은 이름이 연속으로 나오는 건(래퍼) 한 번만 남긴다.\n if (name && names[0] !== name) names.unshift(name);\n fiber = (fiber.return ?? null) as Record<string, unknown> | null;\n }\n return names;\n } catch {\n return [];\n }\n}\n\n/** 제보 본문에 실을 한 줄. 컴포넌트를 못 찾으면 null(빈 줄을 만들지 않는다). */\nexport function reactComponentSummary(node: Element): string | null {\n const path = reactComponentPath(node);\n return path.length > 0 ? path.join(\" > \") : null;\n}\n","import { reactComponentSummary } from \"./react-tree.js\";\n// 지목한 DOM 요소를 제보에 실을 수 있는 형태로 옮긴다.\n//\n// 목표는 \"나중에 이 요소를 다시 찾을 수 있게\" 하는 것이다. 그래서 태그·id·클래스만이 아니라\n// 형제 순서까지 담은 CSS 선택자 경로를 함께 만든다. 못 만들어도 제보는 성립해야 하므로\n// 이 파일의 함수들은 절대 던지지 않는다.\n\nimport type { ElementInfo } from \"@solhun/feedback-kit-core\";\n\n/** 요소 텍스트를 담을 때의 상한. 본문 전체가 실려 페이로드가 붓는 걸 막는다. */\nexport const ELEMENT_TEXT_MAX_CHARS = 200;\n\n/** 선택자 경로를 몇 단계까지 거슬러 올라갈지. 너무 깊으면 오히려 잘 깨진다. */\nexport const SELECTOR_MAX_DEPTH = 8;\n\n/** 담아 둘 속성. 전부 담으면 인라인 style 같은 게 페이로드를 채운다. */\nconst KEPT_ATTRS = [\n \"id\",\n \"class\",\n \"name\",\n \"type\",\n \"role\",\n \"href\",\n \"aria-label\",\n \"data-testid\",\n \"data-fk-source\",\n];\n\nfunction cssEscape(value: string): string {\n const fn = (globalThis as { CSS?: { escape?: (v: string) => string } }).CSS?.escape;\n if (typeof fn === \"function\") return fn(value);\n // CSS.escape 가 없는 환경 대비 최소 폴백. 식별자에 쓰이면 곤란한 문자만 막는다.\n return value.replace(/[^a-zA-Z0-9_-]/g, (ch) => `\\\\${ch}`);\n}\n\n/** 요소가 형제들 사이에서 몇 번째 같은 태그인지. 1부터 센다. */\nfunction nthOfType(el: Element): number {\n const parent = el.parentElement;\n if (!parent) return 1;\n let index = 0;\n for (const child of Array.from(parent.children)) {\n if (child.tagName === el.tagName) {\n index += 1;\n if (child === el) return index;\n }\n }\n return 1;\n}\n\n/** 같은 태그 형제가 둘 이상인가. 하나뿐이면 `:nth-of-type` 을 붙일 이유가 없다. */\nfunction hasTypeSiblings(el: Element): boolean {\n const parent = el.parentElement;\n if (!parent) return false;\n let count = 0;\n for (const child of Array.from(parent.children)) {\n if (child.tagName === el.tagName) {\n count += 1;\n if (count > 1) return true;\n }\n }\n return false;\n}\n\n/**\n * 요소까지의 CSS 선택자 경로.\n *\n * id 를 만나면 거기서 멈춘다 — 문서에서 유일하다고 가정할 수 있는 지점이라 그 위는 군더더기다.\n * 클래스는 쓰지 않는다: 유틸리티 클래스(`px-4` 같은 것)가 섞이면 선택자가 길기만 하고\n * 재현성은 오히려 떨어진다.\n */\nexport function cssSelectorPath(el: Element | null | undefined): string | null {\n if (!el || typeof el.tagName !== \"string\") return null;\n try {\n const parts: string[] = [];\n let node: Element | null = el;\n let depth = 0;\n\n while (node && depth < SELECTOR_MAX_DEPTH) {\n const tag = node.tagName.toLowerCase();\n if (tag === \"html\" || tag === \"body\") {\n parts.unshift(tag);\n break;\n }\n\n const id = node.getAttribute(\"id\");\n if (id) {\n parts.unshift(`#${cssEscape(id)}`);\n break; // id 위로는 올라갈 필요가 없다.\n }\n\n parts.unshift(hasTypeSiblings(node) ? `${tag}:nth-of-type(${nthOfType(node)})` : tag);\n node = node.parentElement;\n depth += 1;\n }\n\n return parts.length > 0 ? parts.join(\" > \") : null;\n } catch {\n return null;\n }\n}\n\n/** 눈에 보이는 텍스트를 한 줄로 눌러 담는다. */\nexport function visibleText(el: Element): string | null {\n const raw = (el as HTMLElement).innerText ?? el.textContent ?? \"\";\n const collapsed = raw.replace(/\\s+/g, \" \").trim();\n if (collapsed === \"\") return null;\n return collapsed.length > ELEMENT_TEXT_MAX_CHARS\n ? collapsed.slice(0, ELEMENT_TEXT_MAX_CHARS)\n : collapsed;\n}\n\n/** 지목된 요소를 `ElementInfo` 로 옮긴다. 실패해도 던지지 않는다. */\nexport function describeElement(el: Element | null | undefined): ElementInfo | null {\n if (!el || typeof el.tagName !== \"string\") return null;\n\n const attributes: Record<string, string> = {};\n for (const name of KEPT_ATTRS) {\n const value = el.getAttribute(name);\n if (value !== null) attributes[name] = value;\n }\n\n let boundingBox: ElementInfo[\"boundingBox\"] = null;\n try {\n const rect = el.getBoundingClientRect();\n boundingBox = { x: rect.left, y: rect.top, width: rect.width, height: rect.height };\n } catch {\n boundingBox = null;\n }\n\n const react = reactComponentSummary(el);\n\n return {\n tag: el.tagName.toLowerCase(),\n id: el.getAttribute(\"id\"),\n className: el.getAttribute(\"class\"),\n text: visibleText(el),\n boundingBox,\n attributes: {\n ...attributes,\n // 지목한 요소가 어느 컴포넌트에 속하는지. `div.flex.items-center` 만 오면\n // 고치는 사람이 코드에서 다시 찾아야 하지만, `LoginForm > SubmitButton` 이\n // 오면 바로 파일이 열린다. React 가 아니면 이 키 자체가 없다.\n ...(react ? { \"react-components\": react } : {}),\n },\n selector: cssSelectorPath(el),\n };\n}\n","// 요소 지목 모드와 마커를 브라우저 저장소에 남긴다.\n//\n// 왜 저장하나: 지목 모드는 페이지를 옮겨 다니며 쓰는 기능이라 새로고침·이동에 살아남아야 하고,\n// 찍은 마커는 같은 경로로 돌아왔을 때 원래 자리에 다시 보여야 한다.\n//\n// 키 이름은 `feedback-kit:` 로 시작하는 우리 고유 네임스페이스를 쓴다. 남의 위젯과 키가 겹치면\n// 서로의 상태를 지우게 된다.\n\n/** 지목 모드 on/off 플래그. */\nexport const PICKING_MODE_KEY = \"feedback-kit:picking-mode\";\n\n/** 경로별 마커 목록. 실제 키는 여기에 pathname 을 이어 붙인다. */\nexport const MARKER_KEY_PREFIX = \"feedback-kit:markers:\";\n\n/** 한 경로에 남겨 둘 마커 상한. 넘으면 오래된 것부터 버린다. */\nexport const MAX_MARKERS_PER_PATH = 50;\n\n/** 마커에 보이는 전송 상태. `sending` → `done`, 실패하면 `pending`. */\nexport type MarkerStatus = \"sending\" | \"done\" | \"pending\";\n\nexport interface StoredMarker {\n /** 제보의 `clientSubmissionId` 와 같다 — 전송 상태를 되짚어 갱신하려고 맞춰 둔다. */\n id: string;\n /** 뷰포트 기준 상대 좌표(0~1). 해상도가 달라도 같은 지점을 가리킨다. */\n x: number;\n y: number;\n selector: string | null;\n comment: string;\n status: MarkerStatus;\n /** 생성 시각(ISO). 상한을 넘겼을 때 무엇을 버릴지 정하는 기준. */\n at: string;\n}\n\n/** `localStorage` 가 만족하는 최소 계약. 테스트에서 갈아 끼운다. */\nexport interface StorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nfunction defaultStorage(): StorageLike | null {\n try {\n const ls = (globalThis as { localStorage?: StorageLike }).localStorage;\n return ls ?? null;\n } catch {\n // 사파리 프라이빗 모드처럼 접근 자체가 던지는 환경이 있다.\n return null;\n }\n}\n\nfunction markerKey(pathname: string): string {\n return `${MARKER_KEY_PREFIX}${pathname}`;\n}\n\n/**\n * 저장소 접근을 한 곳으로 모은다.\n *\n * 저장소가 없거나(SSR) 던지는 환경(프라이빗 모드·용량 초과)에서도 위젯이 죽으면 안 되므로\n * 모든 접근을 try/catch 로 감싸고 실패는 조용히 흘린다. 저장 실패는 \"마커가 안 남는다\" 정도의\n * 불편이지 제보 자체를 막을 이유가 아니다.\n */\nexport class MarkerStore {\n private readonly storage: StorageLike | null;\n\n constructor(storage?: StorageLike | null) {\n this.storage = storage === undefined ? defaultStorage() : storage;\n }\n\n private read(key: string): string | null {\n try {\n return this.storage?.getItem(key) ?? null;\n } catch {\n return null;\n }\n }\n\n private write(key: string, value: string): void {\n try {\n this.storage?.setItem(key, value);\n } catch {\n /* 저장 실패는 흘린다 */\n }\n }\n\n private drop(key: string): void {\n try {\n this.storage?.removeItem(key);\n } catch {\n /* 삭제 실패도 흘린다 */\n }\n }\n\n // ── 지목 모드 ─────────────────────────────────────────────────────────────\n\n isPickingActive(): boolean {\n return this.read(PICKING_MODE_KEY) === \"1\";\n }\n\n setPickingActive(active: boolean): void {\n if (active) this.write(PICKING_MODE_KEY, \"1\");\n else this.drop(PICKING_MODE_KEY);\n }\n\n // ── 마커 ──────────────────────────────────────────────────────────────────\n\n list(pathname: string): StoredMarker[] {\n const raw = this.read(markerKey(pathname));\n if (!raw) return [];\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n // 남이 쓴 값이나 옛 포맷이 섞여 있어도 위젯이 깨지지 않게 형태를 확인하고 거른다.\n return parsed.filter((item): item is StoredMarker => {\n if (typeof item !== \"object\" || item === null) return false;\n const m = item as Partial<StoredMarker>;\n return (\n typeof m.id === \"string\" && typeof m.x === \"number\" && typeof m.y === \"number\"\n );\n });\n } catch {\n return [];\n }\n }\n\n /** 같은 id 가 있으면 덮어쓰고, 없으면 뒤에 붙인다. */\n upsert(pathname: string, marker: StoredMarker): StoredMarker[] {\n const current = this.list(pathname);\n const index = current.findIndex((m) => m.id === marker.id);\n if (index >= 0) current[index] = marker;\n else current.push(marker);\n\n const trimmed =\n current.length > MAX_MARKERS_PER_PATH\n ? current.slice(current.length - MAX_MARKERS_PER_PATH)\n : current;\n this.write(markerKey(pathname), JSON.stringify(trimmed));\n return trimmed;\n }\n\n /** 전송 상태만 바꾼다. 없는 id 면 아무것도 하지 않는다. */\n updateStatus(pathname: string, id: string, status: MarkerStatus): StoredMarker[] {\n const current = this.list(pathname);\n const index = current.findIndex((m) => m.id === id);\n if (index < 0) return current;\n current[index] = { ...current[index], status };\n this.write(markerKey(pathname), JSON.stringify(current));\n return current;\n }\n\n clear(pathname: string): void {\n this.drop(markerKey(pathname));\n }\n}\n","// 요소 지목 모드 (웹 전용).\n//\n// 켜져 있는 동안 페이지의 아무 요소나 클릭하면 그 자리에 주석을 남긴다. 리포트 모달과 달리\n// \"열고 → 쓰고 → 닫는\" 절차가 없다 — 한 번 켜 두고 여러 곳을 연달아 지목하는 게 이 모드의 전부다.\n// 그래서 주석을 저장해도 모드는 꺼지지 않고, 페이지를 옮겨도 살아남는다.\n//\n// 페이지의 원래 클릭(링크 이동·버튼 동작)은 막아야 한다. 지목하려고 누른 링크가 실제로 이동해\n// 버리면 주석을 달 화면이 사라진다.\n\nimport {\n COMMENT_MAX_CHARS,\n normalizePin,\n type ElementInfo,\n type FeedbackPin,\n type FeedbackReport,\n type ReportParts,\n type QueueStatus,\n type SubmitOutcome,\n} from \"@solhun/feedback-kit-core\";\nimport { describeElement } from \"./element-info.js\";\nimport { MarkerStore, type StoredMarker } from \"./marker-store.js\";\n\n/** 위젯 자신의 UI 를 표시하는 속성. 이 안쪽 클릭은 가로채지 않는다. */\nexport const OWN_UI_ATTR = \"data-feedback-kit\";\n\n/** 지목 컨트롤러가 큐에게 요구하는 최소 계약. */\nexport interface PickingQueueLike {\n submit(report: FeedbackReport): Promise<SubmitOutcome>;\n subscribe?(listener: (status: QueueStatus) => void): () => void;\n getOutcome?(clientSubmissionId: string): Pick<SubmitOutcome, \"delivered\" | \"id\"> | null;\n}\n\nexport interface AnnotationPopupState {\n element: ElementInfo | null;\n /** 클릭한 지점의 뷰포트 상대 좌표(0~1). */\n point: FeedbackPin;\n comment: string;\n canSave: boolean;\n}\n\nexport interface PickingState {\n active: boolean;\n /** 지금 마우스가 올라가 있는 요소. 하이라이트를 그리는 근거. */\n hovered: ElementInfo | null;\n popup: AnnotationPopupState | null;\n /** 현재 경로에 남아 있는 마커들. */\n markers: readonly StoredMarker[];\n}\n\nexport type PickingListener = (state: PickingState) => void;\n\nexport interface ElementPickingOpts {\n queue: PickingQueueLike;\n createReport: (parts: ReportParts) => Promise<FeedbackReport>;\n store?: MarkerStore;\n /** 현재 경로. 마커는 경로별로 나눠 저장한다. */\n getPathname?: () => string;\n /** 이벤트를 들을 문서. 기본은 전역 `document`. */\n doc?: Document | null;\n /** 좌표를 정규화할 기준 크기. 기본은 현재 뷰포트. */\n getViewport?: () => { width: number; height: number };\n /**\n * 요소를 고르면 호출된다. 주면 **인라인 팝업 대신 이걸 부른다** —\n * 호스트가 리포트 모달을 열어 스크린샷·우선순위까지 받게 하기 위해서다.\n *\n * 모드는 켜진 채로 둔다. 한 번 켜고 여러 요소를 연달아 지목하는 게 이 모드의 목적이라,\n * 요소 하나 고를 때마다 모드가 꺼지면 매번 다시 켜야 한다.\n */\n onPick?: (element: ElementInfo | null, point: FeedbackPin) => void;\n}\n\nfunction defaultDocument(): Document | null {\n return (globalThis as { document?: Document }).document ?? null;\n}\n\nfunction defaultPathname(): string {\n const loc = (globalThis as { location?: { pathname?: string } }).location;\n return loc?.pathname ?? \"/\";\n}\n\nfunction defaultViewport(): { width: number; height: number } {\n const w = globalThis as { innerWidth?: number; innerHeight?: number };\n return { width: w.innerWidth ?? 0, height: w.innerHeight ?? 0 };\n}\n\n/** 위젯 자신의 UI 안에서 일어난 일인가. */\nfunction isOwnUi(target: EventTarget | null): boolean {\n const el = target as Element | null;\n if (!el || typeof el.closest !== \"function\") return false;\n return el.closest(`[${OWN_UI_ATTR}]`) !== null;\n}\n\nexport class ElementPickingController {\n private readonly queue: PickingQueueLike;\n private readonly createReport: (parts: ReportParts) => Promise<FeedbackReport>;\n private readonly onPick:\n | ((element: ElementInfo | null, point: FeedbackPin) => void)\n | null;\n private readonly store: MarkerStore;\n private readonly getPathname: () => string;\n private readonly doc: Document | null;\n private readonly getViewport: () => { width: number; height: number };\n private readonly unsubscribeQueue: () => void;\n\n private readonly listeners = new Set<PickingListener>();\n\n private active = false;\n private attached = false;\n private hovered: ElementInfo | null = null;\n private popup: AnnotationPopupState | null = null;\n private markers: StoredMarker[] = [];\n private pathWatch: ReturnType<typeof setInterval> | null = null;\n private lastPathname: string;\n private saving = false;\n\n private readonly onClick = (event: Event) => this.handleClick(event);\n private readonly onMouseOver = (event: Event) => this.handleMouseOver(event);\n\n constructor(opts: ElementPickingOpts) {\n this.queue = opts.queue;\n this.createReport = opts.createReport;\n this.store = opts.store ?? new MarkerStore();\n this.onPick = opts.onPick ?? null;\n this.getPathname = opts.getPathname ?? defaultPathname;\n this.doc = opts.doc === undefined ? defaultDocument() : opts.doc;\n this.getViewport = opts.getViewport ?? defaultViewport;\n this.lastPathname = this.getPathname();\n this.markers = this.store.list(this.lastPathname);\n this.unsubscribeQueue =\n this.queue.subscribe?.(() => {\n if (this.reconcileMarkerOutcomes()) this.emit();\n }) ?? (() => undefined);\n }\n\n // ── 조회 ──────────────────────────────────────────────────────────────────\n\n getState(): PickingState {\n return {\n active: this.active,\n hovered: this.hovered,\n popup: this.popup,\n markers: this.markers,\n };\n }\n\n get isActive(): boolean {\n return this.active;\n }\n\n subscribe(listener: PickingListener): () => void {\n this.listeners.add(listener);\n listener(this.getState());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n // ── 모드 on/off ───────────────────────────────────────────────────────────\n\n start(): void {\n if (this.active) return;\n this.active = true;\n this.store.setPickingActive(true);\n this.attach();\n this.startPathWatch();\n this.markers = this.store.list(this.getPathname());\n this.emit();\n }\n\n /** [지목 종료]. 저장된 플래그까지 지워서 새로고침해도 다시 켜지지 않게 한다. */\n stop(): void {\n this.active = false;\n this.store.setPickingActive(false);\n this.detach();\n this.stopPathWatch();\n this.hovered = null;\n this.popup = null;\n this.emit();\n }\n\n /**\n * 저장돼 있던 모드를 되살린다. 새로고침·페이지 이동 직후에 한 번 부른다.\n * @returns 되살아났으면 true.\n */\n restore(): boolean {\n if (!this.store.isPickingActive()) {\n this.markers = this.store.list(this.getPathname());\n this.emit();\n return false;\n }\n this.start();\n return true;\n }\n\n /** 경로가 바뀌었을 때 그 경로의 마커로 갈아 끼운다. */\n syncPath(): readonly StoredMarker[] {\n this.lastPathname = this.getPathname();\n this.markers = this.store.list(this.lastPathname);\n this.reconcileMarkerOutcomes();\n this.popup = null;\n this.hovered = null;\n this.emit();\n return this.markers;\n }\n\n dispose(): void {\n this.detach();\n this.stopPathWatch();\n this.unsubscribeQueue();\n this.listeners.clear();\n }\n\n // ── DOM 이벤트 ────────────────────────────────────────────────────────────\n\n private attach(): void {\n if (this.attached || !this.doc) return;\n // 캡처 단계에서 잡아야 페이지의 핸들러보다 먼저 막을 수 있다.\n this.doc.addEventListener(\"click\", this.onClick, true);\n this.doc.addEventListener(\"mouseover\", this.onMouseOver, true);\n this.attached = true;\n }\n\n private detach(): void {\n if (!this.attached || !this.doc) return;\n this.doc.removeEventListener(\"click\", this.onClick, true);\n this.doc.removeEventListener(\"mouseover\", this.onMouseOver, true);\n this.attached = false;\n }\n\n private handleMouseOver(event: Event): void {\n if (!this.active || isOwnUi(event.target)) return;\n this.hovered = describeElement(event.target as Element | null);\n this.emit();\n }\n\n private handleClick(event: Event): void {\n if (!this.active) return;\n if (isOwnUi(event.target)) return;\n\n // 페이지가 이 클릭을 보지 못하게 한다 — 링크 이동·버튼 동작을 전부 막는다.\n event.preventDefault();\n event.stopPropagation();\n if (typeof event.stopImmediatePropagation === \"function\") {\n event.stopImmediatePropagation();\n }\n\n const mouse = event as MouseEvent;\n this.openAnnotation(event.target as Element | null, {\n x: mouse.clientX ?? 0,\n y: mouse.clientY ?? 0,\n });\n }\n\n // ── 주석 팝업 ─────────────────────────────────────────────────────────────\n\n /** 클릭 지점 기준으로 주석 팝업을 연다. 좌표는 해상도 무관한 상대값으로 접어 둔다. */\n openAnnotation(element: Element | null, clientPoint: { x: number; y: number }): void {\n const point = normalizePin(clientPoint, this.getViewport());\n if (this.onPick) {\n // 호스트가 모달을 연다. 팝업은 만들지 않는다(둘이 동시에 뜨면 안 된다).\n this.onPick(describeElement(element), point);\n return;\n }\n this.popup = {\n element: describeElement(element),\n point,\n comment: \"\",\n canSave: false,\n };\n this.emit();\n }\n\n setAnnotationComment(value: string): void {\n if (!this.popup) return;\n this.popup = {\n ...this.popup,\n comment: value,\n canSave: value.trim().length > 0 && value.length <= COMMENT_MAX_CHARS,\n };\n this.emit();\n }\n\n /** 취소 — 마커도 제보도 남기지 않는다. 모드는 켜진 채로 둔다. */\n cancelAnnotation(): void {\n this.popup = null;\n this.emit();\n }\n\n /**\n * 제출된 제보를 마커로 남긴다. 모달 경로에서 쓴다 — 마커 생성이 팝업 저장 안에만\n * 있으면, 리포트 모달로 보낸 지목 제보는 화면에 흔적이 남지 않는다(\"보냈는지 알 수\n * 없다\"가 이 모드가 없애려던 불편이다).\n */\n noteSubmitted(report: FeedbackReport, delivered: boolean): void {\n if (!report.element && !report.pin) return;\n const pathname = this.getPathname();\n const marker: StoredMarker = {\n id: report.clientSubmissionId,\n x: report.pin?.x ?? 0,\n y: report.pin?.y ?? 0,\n selector: report.element?.selector ?? null,\n comment: report.comment,\n status: delivered ? \"done\" : \"pending\",\n at: report.createdAt,\n };\n this.markers = this.store.upsert(pathname, marker);\n this.emit();\n }\n\n /**\n * 주석 저장. 마커를 먼저 \"전송 중\"으로 찍고 나서 보낸다 — 사용자는 결과를 기다리지 않고\n * 다음 요소로 넘어갈 수 있어야 한다.\n *\n * @returns 검증에 걸려 아무것도 하지 않았으면 `null`.\n */\n async saveAnnotation(): Promise<SubmitOutcome | null> {\n const popup = this.popup;\n if (!popup || !popup.canSave || this.saving) return null;\n this.saving = true;\n\n const pathname = this.getPathname();\n const parts: ReportParts = {\n kind: \"annotation\",\n comment: popup.comment,\n priority: \"unset\",\n screenshot: null,\n pin: popup.point,\n element: popup.element,\n };\n\n let report: FeedbackReport;\n try {\n report = await this.createReport(parts);\n } catch {\n // 조립이 실패하면 남길 마커의 id 도 없다. 팝업만 닫고 조용히 물러난다.\n this.popup = null;\n this.emit();\n this.saving = false;\n return null;\n }\n\n const marker: StoredMarker = {\n id: report.clientSubmissionId,\n x: popup.point.x,\n y: popup.point.y,\n selector: popup.element?.selector ?? null,\n comment: popup.comment,\n status: \"sending\",\n at: report.createdAt,\n };\n this.markers = this.store.upsert(pathname, marker);\n this.popup = null; // 모드는 그대로 — 다음 요소를 바로 지목할 수 있다.\n this.emit();\n\n let outcome: SubmitOutcome;\n try {\n outcome = await this.queue.submit(report);\n } catch {\n outcome = { delivered: false, id: null, queued: true };\n }\n\n const next = this.store.updateStatus(\n pathname,\n marker.id,\n outcome.delivered ? \"done\" : \"pending\",\n );\n // 저장 중에 경로가 바뀌었으면 화면에 보이는 목록은 건드리지 않는다.\n if (pathname === this.getPathname()) this.markers = next;\n this.emit();\n\n this.saving = false;\n return outcome;\n }\n\n private startPathWatch(): void {\n if (this.pathWatch !== null || typeof setInterval !== \"function\") return;\n this.lastPathname = this.getPathname();\n this.pathWatch = setInterval(() => {\n const pathname = this.getPathname();\n if (pathname !== this.lastPathname) this.syncPath();\n }, 200);\n }\n\n private stopPathWatch(): void {\n if (this.pathWatch === null) return;\n clearInterval(this.pathWatch);\n this.pathWatch = null;\n }\n\n /** 전역 pending 수가 아니라 마커와 같은 clientSubmissionId의 확정 성공만 완료로 바꾼다. */\n private reconcileMarkerOutcomes(): boolean {\n if (!this.queue.getOutcome) return false;\n let changed = false;\n for (const marker of this.markers) {\n const outcome = this.queue.getOutcome(marker.id);\n if (marker.status !== \"done\" && outcome?.delivered) {\n this.markers = this.store.updateStatus(this.lastPathname, marker.id, \"done\");\n changed = true;\n }\n }\n return changed;\n }\n\n private emit(): void {\n const state = this.getState();\n for (const listener of this.listeners) listener(state);\n }\n}\n","import type { ScreenshotCapture, ScreenshotReencode } from \"@solhun/feedback-kit-core\";\nimport html2canvas from \"html2canvas-pro\";\n\nimport { OWN_UI_ATTR } from \"./picking.js\";\n\n/**\n * 왜 라이브러리를 쓰나 (직접 만든 SVG foreignObject 방식에서 갈아탄 이유):\n *\n * `<foreignObject>` 로 DOM 을 감싼 SVG 를 캔버스에 그리면 **캔버스가 tainted 되어**\n * `toDataURL()` 이 `SecurityError` 를 던진다. 즉 캡처가 항상 null 이 되고, 사용자는\n * 매번 \"캡처 실패 — 파일로 첨부할 수 있습니다\"만 본다. 실측으로 확인했다:\n * 같은 브라우저에서 일반 SVG(rect)는 오염되지 않고 `foreignObject` 만 오염된다.\n *\n * html2canvas-pro(MIT)는 foreignObject 를 쓰지 않는다. DOM 을 순회하며 캔버스에\n * 직접 그리므로 오염 자체가 생기지 않는다. 명세의 \"DOM 을 이미지로 변환하는 MIT\n * 라이브러리를 쓴다\"도 원래 이 뜻이었다.\n */\n\nfunction payload(dataUrl: string): string | null {\n const comma = dataUrl.indexOf(\",\");\n return comma >= 0 ? dataUrl.slice(comma + 1) : null;\n}\n\n/** 캡처에서 빼야 할 노드인가(위젯 자신의 UI). 결과에 자기 모달이 찍히면 안 된다. */\nexport function isOwnUi(node: Element): boolean {\n return node.hasAttribute?.(OWN_UI_ATTR) === true\n || node.closest?.(`[${OWN_UI_ATTR}]`) !== null;\n}\n\n/**\n * 웹 기본 캡처 — 현재 viewport 를 JPEG 로.\n *\n * 실패하면 **null 을 돌려준다**(던지지 않는다). 코어가 \"캡처 실패\" 상태로 바꾸고\n * 사용자는 파일로 첨부할 수 있다 — 그림 때문에 제보 자체를 잃지 않는다.\n */\n/**\n * 캔버스를 같은 크기의 <img> 로 바꾼다.\n *\n * html2canvas 는 <canvas> 의 픽셀은 가져오지만 **배치를 잘못 잡는 경우가 있다.**\n * 실측: KITS 홈의 Rive 캔버스(700×933, 왼쪽 x=0)가 캡처본에서는 가운데로 옮겨져\n * 본문 텍스트와 버튼을 덮었다. 원본 화면과 캡처를 나란히 놓고서야 보였다.\n *\n * 캔버스를 이미지로 바꾸면 일반 요소로 배치되므로 위치가 어긋나지 않는다.\n * 픽셀을 못 읽는 캔버스(WebGL 에서 preserveDrawingBuffer 미설정, 오염 등)는\n * 원본을 그대로 둔다 — 자리를 비우는 것보다 어긋나더라도 뭔가 보이는 편이 낫다.\n */\nfunction freezeCanvases(source: Document, clone: Document): void {\n const originals = source.querySelectorAll(\"canvas\");\n const clones = clone.querySelectorAll(\"canvas\");\n for (let index = 0; index < originals.length && index < clones.length; index++) {\n const from = originals[index];\n const to = clones[index];\n let dataUrl: string;\n try {\n dataUrl = from.toDataURL(\"image/png\");\n } catch {\n continue;\n }\n if (!dataUrl.startsWith(\"data:image\") || dataUrl.length < 64) continue;\n\n const box = from.getBoundingClientRect();\n if (box.width < 1 || box.height < 1) continue;\n\n const image = clone.createElement(\"img\");\n image.src = dataUrl;\n // 원래 캔버스가 받던 레이아웃을 그대로 물려받게 한다.\n image.setAttribute(\"style\", to.getAttribute(\"style\") ?? \"\");\n image.style.width = `${box.width}px`;\n image.style.height = `${box.height}px`;\n image.className = to.className;\n to.replaceWith(image);\n }\n}\n\nexport const captureWebScreenshot: ScreenshotCapture = async () => {\n const doc = globalThis.document;\n const view = doc?.defaultView;\n if (!doc?.documentElement || !view) return null;\n\n try {\n const canvas = await html2canvas(doc.documentElement, {\n // 현재 보이는 화면만. 문서 전체를 그리면 제보와 무관한 영역까지 커진다.\n width: view.innerWidth,\n height: view.innerHeight,\n x: view.scrollX,\n y: view.scrollY,\n windowWidth: view.innerWidth,\n windowHeight: view.innerHeight,\n // 배율은 2배까지만. 그 위는 용량만 커지고 8 MiB 한도에 먼저 걸린다.\n scale: Math.min(2, Math.max(1, view.devicePixelRatio || 1)),\n // 위젯 자신의 UI 는 결과에서 뺀다.\n ignoreElements: isOwnUi,\n // 캔버스(Rive·차트 등)는 이미지로 바꿔 넘긴다 — freezeCanvases 주석 참고.\n onclone: (cloned: Document) => freezeCanvases(doc, cloned),\n // 외부 이미지가 CORS 를 안 열어두면 그것 때문에 전체가 실패할 수 있다.\n // 못 가져오는 리소스는 건너뛰고 나머지를 그린다.\n useCORS: true,\n allowTaint: false,\n logging: false,\n backgroundColor: null,\n });\n return { base64: payload(canvas.toDataURL(\"image/jpeg\", 0.9)) ?? \"\", contentType: \"image/jpeg\" };\n } catch {\n return null;\n }\n};\n\n/** 8 MiB 정책이 요구할 때 기존 이미지를 JPEG 품질 단계로 다시 인코딩한다. */\nexport const reencodeWebScreenshot: ScreenshotReencode = async (shot, quality) => {\n const doc = globalThis.document;\n if (!doc) return null;\n try {\n const image = await new Promise<HTMLImageElement | null>((resolve) => {\n const img = new Image();\n img.onload = () => resolve(img);\n img.onerror = () => resolve(null);\n img.src = `data:${shot.contentType};base64,${shot.base64}`;\n });\n if (!image) return null;\n const canvas = doc.createElement(\"canvas\");\n canvas.width = image.naturalWidth || image.width;\n canvas.height = image.naturalHeight || image.height;\n const context = canvas.getContext(\"2d\");\n if (!context) return null;\n context.drawImage(image, 0, 0);\n const base64 = payload(canvas.toDataURL(\"image/jpeg\", quality));\n return base64 ? { base64, contentType: \"image/jpeg\" } : null;\n } catch {\n return null;\n }\n};\n","// 웹 위젯 조립.\n//\n// 코어의 화면 지도(`WidgetController`)와 웹 전용 지목 모드(`ElementPickingController`)를\n// 한 덩어리로 묶는다. 둘을 따로 두면 \"모달에서 [요소 지목]을 눌렀다\"와 \"지목 오버레이가 떴다\"가\n// 서로 모르는 상태가 되므로, 연결 지점을 여기 한 곳으로 모은다.\n//\n// 지목 모드 on/off 의 유일한 입구는 `widget.startPicking()` / `widget.stopPicking()` 이다.\n// 지목 컨트롤러를 직접 켜면 화면 지도가 그 사실을 모른다.\n\nimport { WidgetController, type WidgetControllerOpts } from \"@solhun/feedback-kit-core\";\nimport { MarkerStore } from \"./marker-store.js\";\nimport { ElementPickingController } from \"./picking.js\";\nimport { captureWebScreenshot, reencodeWebScreenshot } from \"./screenshot.js\";\n\nexport interface WebWidgetOpts\n extends Omit<WidgetControllerOpts, \"platform\" | \"initialPicking\" | \"onPickingChange\"> {\n /** 지목 모드 플래그와 마커를 담아 둘 저장소. 기본은 `localStorage`. */\n store?: MarkerStore;\n /** 현재 경로. 마커를 경로별로 나눠 담는 기준. */\n getPathname?: () => string;\n /** 지목 이벤트를 들을 문서. 기본은 전역 `document`. */\n doc?: Document | null;\n getViewport?: () => { width: number; height: number };\n}\n\nexport interface WebWidget {\n widget: WidgetController;\n picking: ElementPickingController;\n store: MarkerStore;\n dispose(): void;\n}\n\nexport function createWebWidget(opts: WebWidgetOpts): WebWidget {\n const store = opts.store ?? new MarkerStore();\n // widget 이 아래에서 만들어지므로, 콜백이 불릴 때 참조할 수 있게 늦게 채운다.\n let widgetRef: WidgetController | null = null;\n\n const picking = new ElementPickingController({\n queue: opts.queue,\n createReport: opts.createReport,\n store,\n getPathname: opts.getPathname,\n doc: opts.doc,\n getViewport: opts.getViewport,\n // 요소를 고르면 인라인 팝업이 아니라 **리포트 모달**을 연다.\n // 코멘트만 받던 팝업과 달리 스크린샷·우선순위까지 같은 화면에서 받는다.\n // 모드는 켜진 채로 둬서 한 번 켜고 여러 요소를 연달아 지목할 수 있다.\n onPick: (element, point) => {\n // modal.open() 을 직접 부르지 않는다 — 그러면 모달 상태만 열리고 화면은\n // picking 에 머물러 아무것도 안 보인다. 화면 전환은 컨트롤러가 한다.\n void widgetRef?.openReport({ element });\n widgetRef?.modal.setPin(point);\n },\n });\n\n const widget = new WidgetController({\n ...opts,\n capture: opts.capture === undefined ? captureWebScreenshot : opts.capture,\n reencode: opts.reencode === undefined ? reencodeWebScreenshot : opts.reencode,\n platform: \"web\",\n // 새로고침·페이지 이동 직후에도 켜져 있던 모드를 그대로 이어받는다.\n initialPicking: store.isPickingActive(),\n onPickingChange: (active) => {\n if (active) picking.start();\n else picking.stop();\n },\n });\n\n widgetRef = widget;\n\n // 모달로 보낸 지목 제보도 마커로 남긴다. 마커 생성이 옛 팝업 저장 경로에만 있으면\n // 모달로 보낸 제보는 화면에 흔적이 없다 — \"보냈는지 알 수 없다\"가 이 모드가\n // 없애려던 불편이라, 그게 되살아난다.\n const originalSubmit = widget.submitReport.bind(widget);\n widget.submitReport = async () => {\n const outcome = await originalSubmit();\n const report = widget.modal.getLastReport();\n if (report?.element) picking.noteSubmitted(report, outcome?.delivered === true);\n return outcome;\n };\n\n // 저장돼 있던 모드면 여기서 DOM 리스너까지 다시 붙인다. 꺼져 있으면 마커만 읽고 만다.\n picking.restore();\n\n return {\n widget,\n picking,\n store,\n dispose(): void {\n picking.dispose();\n widget.dispose();\n },\n };\n}\n","import {\n FLOATING_BUTTON_ID,\n MODAL_ACTION_CANCEL,\n MODAL_ACTION_PICK,\n MODAL_ACTION_REMOVE_SCREENSHOT,\n MODAL_ACTION_RETRY,\n MODAL_ACTION_SEND,\n MODAL_FIELD_COMMENT,\n MODAL_FIELD_PRIORITY,\n type FeedbackPriority,\n type FeedbackScreenshot,\n type WidgetState,\n} from \"@solhun/feedback-kit-core\";\nimport {\n type ChangeEvent,\n type CSSProperties,\n type FormEvent,\n type KeyboardEvent,\n type MouseEvent,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport type { PickingState } from \"./picking.js\";\nimport { OWN_UI_ATTR } from \"./picking.js\";\nimport { createWebWidget, type WebWidget, type WebWidgetOpts } from \"./widget.js\";\n\n/**\n * 패키지를 붙인 제품의 디자인 시스템을 침범하지 않는 중립 토큰이다.\n * 모든 값은 인라인 스타일로만 쓰여 호스트의 전역 CSS와 충돌하지 않는다.\n */\nconst TOKENS = {\n ink: \"#17202a\",\n muted: \"#667085\",\n surface: \"#ffffff\",\n subtle: \"#f4f6f8\",\n line: \"#d7dce2\",\n accent: \"#315d73\",\n accentHover: \"#274b5d\",\n danger: \"#b42318\",\n warning: \"#8a5a00\",\n success: \"#18794e\",\n shadow: \"0 18px 50px rgba(23, 32, 42, 0.2)\",\n radius: \"14px\",\n} as const;\n\nconst OWN_UI_PROPS = { [OWN_UI_ATTR]: \"\" } as Record<string, string>;\nconst FONT_STACK =\n \"ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\";\n\nexport type FeedbackKitProps = WebWidgetOpts;\n\nconst INITIAL_PICKING_STATE: PickingState = {\n active: false,\n hovered: null,\n popup: null,\n markers: [],\n};\n\nfunction screenshotSource(screenshot: FeedbackScreenshot): string {\n return `data:${screenshot.contentType};base64,${screenshot.base64}`;\n}\n\nfunction readScreenshotFile(file: File): Promise<FeedbackScreenshot | null> {\n if (file.type !== \"image/png\" && file.type !== \"image/jpeg\") {\n return Promise.resolve(null);\n }\n const contentType: FeedbackScreenshot[\"contentType\"] = file.type;\n\n return new Promise((resolve) => {\n const reader = new FileReader();\n reader.onerror = () => resolve(null);\n reader.onload = () => {\n const result = typeof reader.result === \"string\" ? reader.result : \"\";\n const comma = result.indexOf(\",\");\n if (comma < 0) {\n resolve(null);\n return;\n }\n resolve({\n base64: result.slice(comma + 1),\n contentType,\n });\n };\n reader.readAsDataURL(file);\n });\n}\n\nfunction nextPaint(): Promise<void> {\n return new Promise((resolve) => {\n if (typeof requestAnimationFrame === \"function\") {\n requestAnimationFrame(() => resolve());\n } else {\n setTimeout(resolve, 0);\n }\n });\n}\n\nfunction focusableElements(container: HTMLElement): HTMLElement[] {\n return Array.from(\n container.querySelectorAll<HTMLElement>(\n \"button:not([disabled]), textarea:not([disabled]), select:not([disabled]), input:not([disabled]):not([type='hidden']), [tabindex]:not([tabindex='-1'])\",\n ),\n ).filter((node) => node.getAttribute(\"aria-hidden\") !== \"true\");\n}\n\nfunction trapTab(event: KeyboardEvent<HTMLElement>, container: HTMLElement): void {\n if (event.key !== \"Tab\") return;\n const items = focusableElements(container);\n if (items.length === 0) {\n event.preventDefault();\n return;\n }\n\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = document.activeElement;\n if (event.shiftKey && (active === first || !container.contains(active))) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (active === last || !container.contains(active))) {\n event.preventDefault();\n first.focus();\n }\n}\n\nconst baseButton: CSSProperties = {\n minHeight: 38,\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 9,\n background: TOKENS.surface,\n color: TOKENS.ink,\n padding: \"8px 12px\",\n font: \"inherit\",\n fontWeight: 650,\n cursor: \"pointer\",\n};\n\nconst primaryButton: CSSProperties = {\n ...baseButton,\n borderColor: TOKENS.accent,\n background: TOKENS.accent,\n color: TOKENS.surface,\n};\n\nconst inputStyle: CSSProperties = {\n width: \"100%\",\n boxSizing: \"border-box\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 9,\n background: TOKENS.surface,\n color: TOKENS.ink,\n padding: \"10px 11px\",\n font: \"inherit\",\n};\n\nfunction MarkerStatus({ status }: { status: \"sending\" | \"done\" | \"pending\" }) {\n const presentation =\n status === \"sending\"\n ? { icon: \"↻\", text: \"전송 중\", color: TOKENS.accent }\n : status === \"done\"\n ? { icon: \"✓\", text: \"완료\", color: TOKENS.success }\n : { icon: \"◷\", text: \"대기\", color: TOKENS.warning };\n\n return (\n <span style={{ display: \"inline-flex\", gap: 4, alignItems: \"center\", color: presentation.color }}>\n <span\n aria-hidden=\"true\"\n style={\n status === \"sending\"\n ? { display: \"inline-block\", animation: \"feedback-kit-spin 900ms linear infinite\" }\n : undefined\n }\n >\n {presentation.icon}\n </span>\n <span>{presentation.text}</span>\n </span>\n );\n}\n\n/**\n * `createWebWidget`의 헤드리스 상태를 실제 DOM으로 그리는 공개 React 진입점.\n * 큐·제보 조립·스크린샷 캡처는 모두 props로 주입된 기존 계약을 그대로 사용한다.\n */\nexport function FeedbackKit(props: FeedbackKitProps) {\n const rootRef = useRef<HTMLDivElement | null>(null);\n const floatingButtonRef = useRef<HTMLButtonElement | null>(null);\n const modalRef = useRef<HTMLDivElement | null>(null);\n const confirmRef = useRef<HTMLDivElement | null>(null);\n const fileInputRef = useRef<HTMLInputElement | null>(null);\n const previousScreenRef = useRef<WidgetState[\"screen\"]>(\"button\");\n const previousModalOpenRef = useRef(false);\n const previousConfirmVisibleRef = useRef(false);\n const [kit, setKit] = useState<WebWidget | null>(null);\n const [widgetState, setWidgetState] = useState<WidgetState | null>(null);\n const [pickingState, setPickingState] = useState<PickingState>(INITIAL_PICKING_STATE);\n const [announcement, setAnnouncement] = useState<string | null>(null);\n\n const {\n queue,\n createReport,\n capture,\n reencode,\n screenshotLimitBytes,\n store,\n getPathname,\n doc,\n getViewport,\n } = props;\n\n useEffect(() => {\n const created = createWebWidget({\n queue,\n createReport,\n capture,\n reencode,\n screenshotLimitBytes,\n store,\n getPathname,\n doc,\n getViewport,\n });\n setKit(created);\n const unsubscribeWidget = created.widget.subscribe(setWidgetState);\n const unsubscribePicking = created.picking.subscribe(setPickingState);\n\n return () => {\n unsubscribeWidget();\n unsubscribePicking();\n created.dispose();\n };\n }, [\n queue,\n createReport,\n capture,\n reencode,\n screenshotLimitBytes,\n store,\n getPathname,\n doc,\n getViewport,\n ]);\n\n useEffect(() => {\n if (!widgetState) return;\n const { modal, screen } = widgetState;\n if (modal.submitMessage) setAnnouncement(modal.submitMessage);\n\n if (modal.open && !previousModalOpenRef.current && modal.focused) {\n document.querySelector<HTMLElement>(`[data-fk-focus-id=\"${modal.focused}\"]`)?.focus();\n }\n if (modal.closeConfirmVisible && !previousConfirmVisibleRef.current) confirmRef.current?.focus();\n\n const wasModal = previousScreenRef.current === \"modal\";\n if (wasModal && screen === \"button\" && modal.restoreFocusTo === FLOATING_BUTTON_ID) {\n void nextPaint().then(() => floatingButtonRef.current?.focus());\n }\n previousScreenRef.current = screen;\n previousModalOpenRef.current = modal.open;\n previousConfirmVisibleRef.current = modal.closeConfirmVisible;\n }, [widgetState]);\n\n /**\n * 우리 오버레이가 떠 있는 동안 Escape 를 **우리가 가져간다.**\n *\n * 왜 React 의 onKeyDown 으로는 안 되나: 그건 모달 div 에 붙은 핸들러라 `preventDefault`\n * 를 해도 **네이티브 이벤트는 그대로 document 까지 올라간다.** 호스트 앱이 거기서 듣고\n * 있으면(대부분의 모달 구현이 그렇다) 호스트 모달이 닫힌다.\n *\n * 실측(2026-08-09, cardnews 콘솔): 호스트 모달 위에 우리 모달을 띄우고 Escape 를 누르면\n * **뒤에 있던 호스트 모달이 닫히고 우리 모달이 남았다.** 위에 뜬 쪽이 Escape 를 가져가는\n * 게 상식이고, 지금은 사용자가 우리 모달을 닫으려다 뒤에서 쓰던 폼을 잃는다. z-index 는\n * 우리가 최댓값이라 이겼는데 키보드는 호스트가 먼저 듣고 있어서 졌다.\n *\n * 그래서 document 의 **캡처 단계**에서 잡는다. 캡처는 어떤 버블 리스너보다 먼저 돌아서\n * `stopPropagation` 이 호스트에 닿기 전에 끊는다. 대신 우리 React 핸들러에도 안 가므로\n * 닫기 처리를 여기서 직접 한다.\n *\n * **우리 오버레이가 없을 때는 아무것도 하지 않는다** — 호스트의 Escape 를 상시로 뺏으면\n * 그건 그것대로 남의 앱을 망가뜨리는 것이다.\n */\n useEffect(() => {\n if (!kit || !widgetState) return;\n const { screen, modal } = widgetState;\n if (screen !== \"modal\" && screen !== \"pin\") return;\n const widget = kit.widget;\n\n function onKeyDownCapture(event: globalThis.KeyboardEvent): void {\n if (event.key !== \"Escape\") return;\n // 조합 중(한글 입력 등)의 Escape 는 IME 취소다. 모달을 닫으면 글자를 잃는다.\n if (event.isComposing) return;\n event.stopPropagation();\n event.preventDefault();\n if (screen === \"pin\") widget.cancelPin();\n else if (modal.closeConfirmVisible) widget.cancelCloseReport();\n else widget.closeReport();\n }\n\n document.addEventListener(\"keydown\", onKeyDownCapture, true);\n return () => document.removeEventListener(\"keydown\", onKeyDownCapture, true);\n }, [kit, widgetState]);\n\n if (!kit || !widgetState) {\n return <div {...OWN_UI_PROPS} data-feedback-kit-loading=\"true\" />;\n }\n\n const activeKit = kit;\n const { modal, screen } = widgetState;\n const draftLocked = modal.submitStatus === \"sending\" || modal.submitStatus === \"pending\";\n\n async function withOwnUiHidden(action: () => Promise<void>): Promise<void> {\n const root = rootRef.current;\n const previousVisibility = root?.style.visibility ?? \"\";\n if (root) root.style.visibility = \"hidden\";\n try {\n await nextPaint();\n await action();\n } finally {\n if (root) root.style.visibility = previousVisibility;\n }\n }\n\n async function openReport(): Promise<void> {\n setAnnouncement(null);\n await withOwnUiHidden(() => activeKit.widget.openReport());\n }\n\n async function recapture(): Promise<void> {\n await withOwnUiHidden(() => activeKit.widget.modal.recapture());\n }\n\n async function attachFile(event: ChangeEvent<HTMLInputElement>): Promise<void> {\n const input = event.currentTarget;\n const file = input.files?.[0];\n if (file) {\n const screenshot = await readScreenshotFile(file);\n if (screenshot) await activeKit.widget.modal.attachFile(screenshot);\n }\n input.value = \"\";\n }\n\n function closeReport(): void {\n activeKit.widget.closeReport();\n }\n\n function handleModalKeyDown(event: KeyboardEvent<HTMLDivElement>): void {\n if (event.key === \"Escape\") {\n event.preventDefault();\n if (modal.closeConfirmVisible) activeKit.widget.cancelCloseReport();\n else closeReport();\n return;\n }\n trapTab(event, modal.closeConfirmVisible ? confirmRef.current ?? event.currentTarget : event.currentTarget);\n }\n\n function handleBackdrop(event: MouseEvent<HTMLDivElement>): void {\n if (event.target === event.currentTarget) closeReport();\n }\n\n function submitReport(event: FormEvent<HTMLFormElement>): void {\n event.preventDefault();\n void activeKit.widget.submitReport();\n }\n\n const floating = screen === \"button\" || screen === \"picking\";\n const hoverBox = pickingState.hovered?.boundingBox;\n const popup = pickingState.popup;\n\n return (\n <div\n ref={rootRef}\n {...OWN_UI_PROPS}\n style={{ fontFamily: FONT_STACK, color: TOKENS.ink, fontSize: 14, lineHeight: 1.45 }}\n >\n <style>{`@keyframes feedback-kit-spin{to{transform:rotate(360deg)}}`}</style>\n\n {floating ? (\n <button\n ref={floatingButtonRef}\n id={FLOATING_BUTTON_ID}\n type=\"button\"\n disabled={screen === \"picking\"}\n aria-label={screen === \"picking\" ? \"요소 지목 중\" : \"피드백 보내기\"}\n onClick={() => void openReport()}\n style={{\n ...primaryButton,\n position: \"fixed\",\n right: 24,\n bottom: 24,\n zIndex: 2147483600,\n minWidth: 112,\n minHeight: 46,\n borderRadius: 24,\n boxShadow: TOKENS.shadow,\n opacity: screen === \"picking\" ? 0.76 : 1,\n cursor: screen === \"picking\" ? \"default\" : \"pointer\",\n }}\n >\n {screen === \"picking\" ? \"지목 중\" : \"피드백\"}\n </button>\n ) : null}\n\n {announcement && !modal.open ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n style={{\n position: \"fixed\",\n right: 24,\n bottom: 82,\n zIndex: 2147483601,\n padding: \"10px 14px\",\n borderRadius: 10,\n background: TOKENS.ink,\n color: TOKENS.surface,\n boxShadow: TOKENS.shadow,\n }}\n >\n {announcement}\n </div>\n ) : null}\n\n {screen === \"modal\" && modal.open ? (\n <div\n onMouseDown={handleBackdrop}\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483602,\n display: \"grid\",\n placeItems: \"center\",\n padding: 20,\n background: \"rgba(23, 32, 42, 0.48)\",\n }}\n >\n <div\n ref={modalRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"feedback-kit-dialog-title\"\n onKeyDown={handleModalKeyDown}\n style={{\n width: \"min(520px, 100%)\",\n maxHeight: \"min(760px, calc(100vh - 40px))\",\n overflowY: \"auto\",\n boxSizing: \"border-box\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: TOKENS.radius,\n background: TOKENS.surface,\n padding: 22,\n boxShadow: TOKENS.shadow,\n }}\n >\n <form onSubmit={submitReport}>\n <h2 id=\"feedback-kit-dialog-title\" style={{ margin: \"0 0 4px\", fontSize: 20 }}>\n 피드백 보내기\n </h2>\n <p style={{ margin: \"0 0 18px\", color: TOKENS.muted }}>\n 현재 화면의 문제나 의견을 남겨주세요.\n </p>\n\n <section aria-labelledby=\"feedback-kit-screenshot-label\" style={{ marginBottom: 16 }}>\n <strong id=\"feedback-kit-screenshot-label\">스크린샷</strong>\n <div\n style={{\n display: \"grid\",\n placeItems: \"center\",\n minHeight: 150,\n marginTop: 7,\n overflow: \"hidden\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 10,\n background: TOKENS.subtle,\n }}\n >\n {modal.screenshotStatus === \"capturing\" ? (\n <span role=\"status\">화면 캡처 중…</span>\n ) : modal.screenshot ? (\n <img\n src={screenshotSource(modal.screenshot)}\n alt=\"자동 캡처된 현재 화면 미리보기\"\n style={{ display: \"block\", maxWidth: \"100%\", maxHeight: 260, objectFit: \"contain\" }}\n />\n ) : (\n <span role=\"status\" style={{ padding: 18, color: TOKENS.muted, textAlign: \"center\" }}>\n {modal.screenshotMessage ?? \"스크린샷 없음\"}\n </span>\n )}\n </div>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 8, marginTop: 8 }}>\n {modal.screenshot ? (\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_REMOVE_SCREENSHOT}\n disabled={draftLocked}\n onClick={() => kit.widget.modal.removeScreenshot()}\n style={baseButton}\n >\n 제거\n </button>\n ) : null}\n <button\n type=\"button\"\n disabled={draftLocked || modal.screenshotStatus === \"capturing\"}\n onClick={() => void recapture()}\n style={baseButton}\n >\n 다시 찍기\n </button>\n <button\n type=\"button\"\n disabled={draftLocked}\n onClick={() => fileInputRef.current?.click()}\n style={baseButton}\n >\n {modal.screenshot ? \"파일로 교체\" : \"파일로 첨부\"}\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/png,image/jpeg\"\n tabIndex={-1}\n aria-hidden=\"true\"\n onChange={(event) => void attachFile(event)}\n style={{ display: \"none\" }}\n />\n </div>\n </section>\n\n <label htmlFor=\"feedback-kit-comment\" style={{ display: \"block\", fontWeight: 700 }}>\n 코멘트 <span aria-hidden=\"true\">*</span>\n </label>\n <textarea\n id=\"feedback-kit-comment\"\n data-fk-focus-id={MODAL_FIELD_COMMENT}\n value={modal.comment}\n disabled={draftLocked}\n aria-required=\"true\"\n aria-invalid={modal.commentError ? \"true\" : undefined}\n aria-describedby={modal.commentError ? \"feedback-kit-comment-error\" : undefined}\n onChange={(event) => kit.widget.modal.setComment(event.currentTarget.value)}\n rows={5}\n placeholder=\"무엇이 불편했는지 알려주세요\"\n style={{ ...inputStyle, marginTop: 7, resize: \"vertical\" }}\n />\n <div style={{ minHeight: 21, marginTop: 3 }}>\n {modal.commentError ? (\n <span id=\"feedback-kit-comment-error\" role=\"alert\" style={{ color: TOKENS.danger }}>\n {modal.commentError}\n </span>\n ) : (\n <span style={{ color: TOKENS.muted }}>{modal.comment.length.toLocaleString()} / 4,000</span>\n )}\n </div>\n\n <label htmlFor=\"feedback-kit-priority\" style={{ display: \"block\", marginTop: 10, fontWeight: 700 }}>\n 우선순위\n </label>\n <select\n id=\"feedback-kit-priority\"\n data-fk-focus-id={MODAL_FIELD_PRIORITY}\n value={modal.priority}\n disabled={draftLocked}\n onChange={(event) =>\n kit.widget.modal.setPriority(event.currentTarget.value as FeedbackPriority)\n }\n style={{ ...inputStyle, marginTop: 7 }}\n >\n <option value=\"unset\">선택 안 함</option>\n <option value=\"normal\">보통</option>\n <option value=\"high\">높음</option>\n <option value=\"urgent\">긴급</option>\n </select>\n\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked=\"false\"\n disabled={draftLocked}\n data-fk-focus-id={MODAL_ACTION_PICK}\n onClick={() => kit.widget.startPicking()}\n style={{ ...baseButton, width: \"100%\", marginTop: 16 }}\n >\n 요소 지목\n </button>\n\n {modal.submitStatus !== \"idle\" || modal.pending > 0 ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n style={{\n marginTop: 14,\n padding: 11,\n borderRadius: 9,\n background: TOKENS.subtle,\n color: modal.submitStatus === \"pending\" ? TOKENS.warning : TOKENS.ink,\n }}\n >\n {modal.submitStatus === \"sending\" ? \"전송 중…\" : modal.submitMessage}\n {modal.pending > 0 ? ` · 대기 큐 ${modal.pending}건` : \"\"}\n {modal.showRetry ? (\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_RETRY}\n onClick={() => void kit.widget.retryReport()}\n style={{ ...baseButton, marginLeft: 10 }}\n >\n 다시 보내기\n </button>\n ) : null}\n </div>\n ) : null}\n\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, marginTop: 18 }}>\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_CANCEL}\n onClick={closeReport}\n style={baseButton}\n >\n 취소\n </button>\n <button\n type=\"submit\"\n data-fk-focus-id={MODAL_ACTION_SEND}\n disabled={!modal.canSubmit}\n style={{\n ...primaryButton,\n opacity: modal.canSubmit ? 1 : 0.5,\n cursor: modal.canSubmit ? \"pointer\" : \"not-allowed\",\n }}\n >\n {modal.submitStatus === \"sending\" ? \"전송 중…\" : \"보내기\"}\n </button>\n </div>\n </form>\n\n {modal.closeConfirmVisible ? (\n <div\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483604,\n display: \"grid\",\n placeItems: \"center\",\n padding: 20,\n background: \"rgba(23, 32, 42, 0.58)\",\n }}\n >\n <div\n ref={confirmRef}\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"feedback-kit-close-title\"\n tabIndex={-1}\n style={{\n width: \"min(360px, 100%)\",\n boxSizing: \"border-box\",\n borderRadius: 12,\n background: TOKENS.surface,\n padding: 20,\n boxShadow: TOKENS.shadow,\n }}\n >\n <h3 id=\"feedback-kit-close-title\" style={{ margin: \"0 0 6px\", fontSize: 17 }}>\n 작성 중인 내용을 닫을까요?\n </h3>\n <p style={{ margin: \"0 0 16px\", color: TOKENS.muted }}>\n 아직 보내지 않은 코멘트가 있습니다.\n </p>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8 }}>\n <button type=\"button\" onClick={() => kit.widget.cancelCloseReport()} style={baseButton}>\n 계속 쓰기\n </button>\n <button type=\"button\" onClick={() => kit.widget.confirmCloseReport()} style={primaryButton}>\n 닫기\n </button>\n </div>\n </div>\n </div>\n ) : null}\n </div>\n </div>\n ) : null}\n\n {screen === \"picking\" && pickingState.active ? (\n <div\n aria-label=\"요소 지목 오버레이\"\n style={{ position: \"fixed\", inset: 0, zIndex: 2147483599, pointerEvents: \"none\" }}\n >\n {hoverBox ? (\n <div\n aria-hidden=\"true\"\n style={{\n position: \"fixed\",\n left: hoverBox.x,\n top: hoverBox.y,\n width: hoverBox.width,\n height: hoverBox.height,\n boxSizing: \"border-box\",\n border: `2px solid ${TOKENS.accent}`,\n background: \"rgba(49, 93, 115, 0.12)\",\n }}\n />\n ) : null}\n\n <button\n type=\"button\"\n onClick={() => kit.widget.stopPicking()}\n style={{\n ...primaryButton,\n position: \"fixed\",\n top: 18,\n right: 18,\n pointerEvents: \"auto\",\n boxShadow: TOKENS.shadow,\n }}\n >\n 지목 종료\n </button>\n\n {pickingState.markers.map((marker, index) => (\n <div\n key={marker.id}\n role=\"status\"\n aria-label={`주석 ${index + 1}: ${marker.comment}`}\n style={{\n position: \"fixed\",\n left: `${marker.x * 100}%`,\n top: `${marker.y * 100}%`,\n transform: \"translate(-13px, -13px)\",\n display: \"grid\",\n placeItems: \"center\",\n minWidth: 26,\n height: 26,\n padding: \"0 7px\",\n border: `2px solid ${TOKENS.surface}`,\n borderRadius: 16,\n background: TOKENS.ink,\n color: TOKENS.surface,\n boxShadow: \"0 3px 10px rgba(23, 32, 42, 0.3)\",\n pointerEvents: \"none\",\n fontSize: 11,\n fontWeight: 700,\n }}\n >\n <MarkerStatus status={marker.status} />\n </div>\n ))}\n\n {popup ? (\n <form\n aria-label=\"요소 주석\"\n onSubmit={(event) => {\n event.preventDefault();\n void kit.picking.saveAnnotation();\n }}\n style={{\n position: \"fixed\",\n left: `min(${popup.point.x * 100}vw, calc(100vw - 336px))`,\n top: `min(${popup.point.y * 100}vh, calc(100vh - 230px))`,\n width: \"min(320px, calc(100vw - 32px))\",\n boxSizing: \"border-box\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 12,\n background: TOKENS.surface,\n padding: 14,\n boxShadow: TOKENS.shadow,\n pointerEvents: \"auto\",\n }}\n >\n <label htmlFor=\"feedback-kit-annotation\" style={{ display: \"block\", fontWeight: 700 }}>\n 이 요소에 주석 남기기\n </label>\n {popup.element?.text ? (\n <p style={{ margin: \"4px 0 9px\", color: TOKENS.muted }}>\n {popup.element.text}\n </p>\n ) : null}\n <textarea\n id=\"feedback-kit-annotation\"\n autoFocus\n value={popup.comment}\n onChange={(event) => kit.picking.setAnnotationComment(event.currentTarget.value)}\n rows={4}\n placeholder=\"의견을 입력해주세요\"\n style={{ ...inputStyle, resize: \"vertical\" }}\n />\n {popup.comment.length > 4000 ? (\n <div role=\"alert\" style={{ marginTop: 4, color: TOKENS.danger }}>\n 4,000자 이하로 입력해주세요\n </div>\n ) : null}\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, marginTop: 10 }}>\n <button type=\"button\" onClick={() => kit.picking.cancelAnnotation()} style={baseButton}>\n 취소\n </button>\n <button\n type=\"submit\"\n disabled={!popup.canSave}\n style={{\n ...primaryButton,\n opacity: popup.canSave ? 1 : 0.5,\n cursor: popup.canSave ? \"pointer\" : \"not-allowed\",\n }}\n >\n 저장\n </button>\n </div>\n </form>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n","// 웹 기본 저장소 — `localStorage` 를 FeedbackStorage 계약에 맞춘다.\n//\n// 왜 패키지가 제공하나: 이게 없으면 붙이는 앱마다 같은 20줄을 다시 쓴다.\n// 코어는 저장소 구현을 모르고(플랫폼 무관), 웹 패키지가 기본값을 준다.\n//\n// localStorage 는 동기지만 계약은 비동기라 Promise 로 감싼다. 사파리 프라이빗\n// 모드처럼 접근 자체가 던지는 환경이 있어서, 실패하면 **메모리로 조용히 물러난다**\n// — 제보를 잃는 것보다 세션 동안만 유지되는 편이 낫다.\n\nimport type { FeedbackStorage } from \"@solhun/feedback-kit-core\";\n\ninterface SyncStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nfunction memoryStorage(): SyncStorage {\n const map = new Map<string, string>();\n return {\n getItem: (key) => map.get(key) ?? null,\n setItem: (key, value) => void map.set(key, value),\n removeItem: (key) => void map.delete(key),\n };\n}\n\n/** 접근 가능한 localStorage 를 고른다. 못 쓰면 메모리. */\nfunction pickBacking(): SyncStorage {\n try {\n const ls = (globalThis as { localStorage?: SyncStorage }).localStorage;\n if (!ls) return memoryStorage();\n // 존재만으로는 부족하다 — 프라이빗 모드는 쓰기에서 던진다.\n const probe = \"__feedback_kit_probe__\";\n ls.setItem(probe, \"1\");\n ls.removeItem(probe);\n return ls;\n } catch {\n return memoryStorage();\n }\n}\n\n/**\n * 웹 기본 FeedbackStorage.\n *\n * 쓰기가 쿼터를 넘기면 그대로 던진다 — 큐가 그 신호를 받아 스크린샷을 떼어내고\n * 본문만 남기는 축소 경로를 타기 때문에, 여기서 삼키면 안 된다.\n */\nexport function createWebStorage(backing?: SyncStorage): FeedbackStorage {\n const store = backing ?? pickBacking();\n return {\n async get(key) {\n try {\n return store.getItem(key);\n } catch {\n return null;\n }\n },\n async set(key, value) {\n store.setItem(key, value);\n },\n async remove(key) {\n try {\n store.removeItem(key);\n } catch {\n /* 지우기 실패는 다음 쓰기가 덮으므로 무시한다. */\n }\n },\n };\n}\n","// 웹 기본 컨텍스트 제공자.\n//\n// 왜 패키지가 주나: `getUrl` 을 호스트가 깜빡하면 제보의 url 이 빈 문자열로 나가고,\n// 수집 서버는 그걸 400 으로 거절한다. 400 은 재시도 불가라 **제보가 조용히 버려진다.**\n// 실제로 e-fm 첫 배선에서 이 일이 났다 — 화면에는 \"보내지 못했습니다\"만 뜨고,\n// 원인(빈 url)은 네트워크를 까 봐야 알 수 있었다.\n//\n// 기본값을 패키지가 쥐고 있으면 붙이는 쪽이 잊을 수 없다. 필요하면 덮어쓰면 된다.\n\nimport type { ContextProviders, WebContextInput } from \"@solhun/feedback-kit-core\";\n\n/**\n * 브라우저 전역에서 뽑을 수 있는 기본 제공자들.\n *\n * SSR·비브라우저에서 불려도 던지지 않는다(`null` 을 돌려준다) — 위젯이 마운트되기 전\n * 서버 렌더 단계에서 실행될 수 있기 때문이다.\n */\nexport function webContextProviders(): ContextProviders {\n return {\n getUrl: () => {\n const loc = (globalThis as { location?: { href?: string } }).location;\n return typeof loc?.href === \"string\" && loc.href.length > 0 ? loc.href : null;\n },\n getWebContext: (): WebContextInput | null => {\n const g = globalThis as {\n location?: unknown;\n navigator?: { userAgent?: string };\n innerWidth?: number;\n innerHeight?: number;\n devicePixelRatio?: number;\n };\n if (!g.location) return null;\n return {\n userAgent: g.navigator?.userAgent ?? null,\n viewport: {\n width: typeof g.innerWidth === \"number\" ? g.innerWidth : null,\n height: typeof g.innerHeight === \"number\" ? g.innerHeight : null,\n devicePixelRatio: typeof g.devicePixelRatio === \"number\" ? g.devicePixelRatio : null,\n },\n };\n },\n };\n}\n"],"mappings":"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","names":["isOwnUi","import_feedback_kit_core","isOwnUi","html2canvas","import_feedback_kit_core","import_feedback_kit_core","modal","screen"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/react-tree.ts","../src/element-info.ts","../src/marker-store.ts","../src/picking.ts","../src/screenshot.ts","../src/widget.ts","../src/feedback-kit.tsx","../src/storage.ts","../src/providers.ts"],"sourcesContent":["// @solhun/feedback-kit-web\n//\n// 웹용 피드백 위젯 엔트리.\n//\n// 화면 지도·리포트 모달·핀 좌표 계산은 전부 코어에 있다. 여기 있는 건 웹에서만 뜻이 있는 것들 —\n// DOM 요소를 제보에 실을 형태로 옮기고(`element-info`), 지목 모드와 마커를 브라우저 저장소에\n// 남기고(`marker-store`), 페이지 클릭을 가로채 주석을 다는 것(`picking`) — 뿐이다.\n\nexport type {\n FeedbackReport,\n SubmitResult,\n FeedbackContext,\n FeedbackUser,\n FeedbackStorage,\n FeedbackAdapter,\n ElementInfo,\n FeedbackPin,\n} from \"@solhun/feedback-kit-core\";\n\n// 설정은 코어가 정의한다. 웹/앱이 각자 다른 설정 타입을 갖지 않는 게 중요해서\n// (같은 설정을 두 곳에 적으면 갈라진다) 여기서는 다시 내보내기만 한다.\nexport type {\n FeedbackConfig,\n ResolvedConfig,\n Visibility,\n VisibilityEnv,\n VisibilityFn,\n WidgetPosition,\n} from \"@solhun/feedback-kit-core\";\nexport { resolveConfig, shouldShowWidget } from \"@solhun/feedback-kit-core\";\n\n// 빌드 플러그인이 심은 소스 경로를 읽는 쪽. 플러그인이 없으면 조용히 null 이다.\nexport { parseSourceAttr, SOURCE_ATTR, sourceFromElement } from \"@solhun/feedback-kit-core\";\n\n// 코어의 헤드리스 위젯 계층. 웹 컴포넌트가 이 상태기계를 그대로 그린다.\nexport type {\n ModalSubmitStatus,\n ReportModalState,\n ScreenshotStatus,\n WidgetScreen,\n WidgetState,\n} from \"@solhun/feedback-kit-core\";\nexport {\n COMMENT_MAX_CHARS,\n COMMENT_REQUIRED_MESSAGE,\n COMMENT_TOO_LONG_MESSAGE,\n denormalizePin,\n FLOATING_BUTTON_ID,\n MODAL_ACTION_PICK,\n normalizePin,\n ReportModalController,\n SCREENSHOT_FAILED_MESSAGE,\n SUBMIT_DONE_MESSAGE,\n SUBMIT_PENDING_MESSAGE,\n WidgetController,\n} from \"@solhun/feedback-kit-core\";\n\n// ── 웹 전용 ────────────────────────────────────────────────────────────────\n\nexport {\n cssSelectorPath,\n describeElement,\n ELEMENT_TEXT_MAX_CHARS,\n SELECTOR_MAX_DEPTH,\n visibleText,\n} from \"./element-info.js\";\n\nexport type { MarkerStatus, StorageLike, StoredMarker } from \"./marker-store.js\";\nexport {\n MARKER_KEY_PREFIX,\n MarkerStore,\n MAX_MARKERS_PER_PATH,\n PICKING_MODE_KEY,\n} from \"./marker-store.js\";\n\nexport type {\n AnnotationPopupState,\n ElementPickingOpts,\n PickingListener,\n PickingQueueLike,\n PickingState,\n} from \"./picking.js\";\nexport { ElementPickingController, OWN_UI_ATTR } from \"./picking.js\";\n\nexport { captureWebScreenshot, isOwnUi, reencodeWebScreenshot } from \"./screenshot.js\";\n\n// 지목한 요소의 React 컴포넌트 경로. 제보에서 코드를 찾는 시간을 가장 크게 줄인다.\nexport { reactComponentPath, reactComponentSummary } from \"./react-tree.js\";\n\nexport type { WebWidget, WebWidgetOpts } from \"./widget.js\";\nexport { createWebWidget } from \"./widget.js\";\n\nexport type { FeedbackKitProps } from \"./feedback-kit.js\";\nexport { FeedbackKit } from \"./feedback-kit.js\";\n\n// 웹 기본 저장소(localStorage). 앱마다 다시 쓰지 않게 패키지가 제공한다.\nexport { createWebStorage } from \"./storage.js\";\n\n// 웹 기본 컨텍스트 제공자(getUrl 등). 붙이는 쪽이 잊으면 제보가 버려진다.\nexport { webContextProviders } from \"./providers.js\";\n","// 지목한 요소가 **어느 React 컴포넌트에 속하는지** 알아낸다.\n//\n// 왜 필요한가: `div.flex.items-center` 같은 선택자만 오면 고치는 사람이 그걸\n// 코드에서 다시 찾아야 한다. `LoginForm > SubmitButton` 이 오면 바로 파일이 열린다.\n// 제보 하나당 이 한 줄이 탐색 시간을 가장 크게 줄인다.\n//\n// 방법: React 는 DOM 노드에 `__reactFiber$<랜덤>` 프로퍼티를 심는다(공개 API 가 아니다).\n// 거기서 fiber 를 잡아 부모로 거슬러 올라가며 컴포넌트 이름을 모은다.\n//\n// 한계를 미리 적어둔다:\n// - **프로덕션 빌드에서는 이름이 minify 되어 `t`·`Xe` 처럼 나올 수 있다.** 그래도 트리\n// 모양은 단서가 되므로 버리지 않는다. 이름을 살리려면 빌드에서 displayName 을 남긴다.\n// - React 19 도 같은 프로퍼티를 쓰지만 내부 구조라 언제든 바뀔 수 있다. 못 읽으면\n// **조용히 빈 배열**을 돌려준다 — 제보 자체를 막지 않는다.\n\n/**\n * 프레임워크가 만든 이름. 사람이 지은 게 아니라서 제보에 실어도 단서가 안 된다.\n *\n * 왜 필요한가 (2026-08-09 실측, cardnews 랜딩 = Next.js App Router):\n * 지목 제보의 컴포넌트 경로가 **전부 프레임워크 내부로만** 채워져 왔다 —\n * `HotReload > AppDevOverlayErrorBoundary > ... > InnerLayoutRouter > LayoutRouterContext`.\n * 페이지가 서버 컴포넌트면 클라이언트 fiber 에 그 이름이 안 남고, 위로는 전부 경계\n * 컴포넌트다. 원래 목적(\"`LoginForm > SubmitButton` 이 오면 바로 파일이 열린다\")이\n * 무너지는데다 300자 넘는 잡음이 제보마다 들어갔다. Vite + 클라이언트 컴포넌트인\n * KITS 웹에서는 정상이라 안 드러났다.\n *\n * **접미사로 거르지 않는다.** `*Provider`·`*Context`·`*Boundary` 를 통째로 지우면\n * 사람이 지은 `AuthProvider`·`ThemeContext` 까지 사라진다. 그래서 실제로 관측된\n * 이름만 정확히 적는다 — 새 프레임워크 잡음이 보이면 여기 추가한다.\n */\nconst FRAMEWORK_INTERNALS = new Set([\n // Next.js App Router\n \"AppRouter\",\n \"InnerLayoutRouter\",\n \"OuterLayoutRouter\",\n \"LayoutRouterContext\",\n \"RenderFromTemplateContext\",\n \"TemplateContext\",\n \"SegmentStateProvider\",\n \"SegmentViewNode\",\n \"ScrollAndMaybeFocusHandler\",\n \"InnerScrollAndFocusHandler\",\n \"InnerScrollAndFocusHandlerOld\",\n \"RedirectBoundary\",\n \"RedirectErrorBoundary\",\n \"NotFoundBoundary\",\n \"NotFoundErrorBoundary\",\n \"HTTPAccessFallbackBoundary\",\n \"HTTPAccessFallbackErrorBoundary\",\n \"DevRootHTTPAccessFallbackBoundary\",\n \"LoadingBoundary\",\n \"ErrorBoundaryHandler\",\n \"MetadataBoundary\",\n \"ViewportBoundary\",\n \"OutletBoundary\",\n // 개발 전용 오버레이 — 프로덕션엔 없지만 dev 제보에는 항상 낀다\n \"HotReload\",\n \"AppDevOverlay\",\n \"AppDevOverlayErrorBoundary\",\n \"ReactDevOverlay\",\n \"DevOverlay\",\n \"Router\",\n \"ServerRoot\",\n \"RSCComponent\",\n]);\n\n/** `__next_root_layout_boundary__` 처럼 규칙이 이름에 드러나는 것들. */\nfunction isFrameworkName(name: string): boolean {\n return FRAMEWORK_INTERNALS.has(name) || name.startsWith(\"__next\");\n}\n\n/** 호스트 요소(div, span…)는 건너뛴다. 우리가 원하는 건 사람이 지은 컴포넌트 이름이다. */\nfunction componentName(type: unknown): string | null {\n if (typeof type === \"function\") {\n const fn = type as { displayName?: string; name?: string };\n return fn.displayName || fn.name || null;\n }\n if (type !== null && typeof type === \"object\") {\n // memo/forwardRef 등 래퍼. 한 겹 안쪽을 본다.\n const wrapped = type as { displayName?: string; render?: unknown; type?: unknown };\n if (wrapped.displayName) return wrapped.displayName;\n if (wrapped.render) return componentName(wrapped.render);\n if (wrapped.type) return componentName(wrapped.type);\n }\n return null;\n}\n\nfunction fiberOf(node: Element): Record<string, unknown> | null {\n // Object.keys 가 아니라 getOwnPropertyNames — React 는 열거 가능하게 심지만,\n // 그렇지 않은 환경/버전에서도 찾을 수 있어야 한다.\n for (const key of Object.getOwnPropertyNames(node)) {\n if (key.startsWith(\"__reactFiber$\") || key.startsWith(\"__reactInternalInstance$\")) {\n const value = (node as unknown as Record<string, unknown>)[key];\n if (value !== null && typeof value === \"object\") return value as Record<string, unknown>;\n }\n }\n return null;\n}\n\n/**\n * 바깥 → 안쪽 순서의 **앱** 컴포넌트 이름 목록.\n * 예: `[\"App\", \"LoginPage\", \"LoginForm\", \"SubmitButton\"]`\n *\n * 프레임워크가 만든 이름은 뺀다. 다 빼고 나면 빈 배열이고, 그게 정직한 답이다 —\n * 서버 컴포넌트만 있는 화면에는 클라이언트 fiber 에 앱 이름이 애초에 없다.\n *\n * @param limit 안쪽에서부터 이만큼만 남긴다. 요소에 **가까운 쪽이 단서**라, 넘치면\n * 바깥(앱 루트 쪽)을 버린다. 예전엔 20 이었는데 그 길이가 그대로 제보에 실려서\n * 한 줄이 300자를 넘었다.\n */\nexport function reactComponentPath(node: Element, limit = 5): string[] {\n try {\n let fiber = fiberOf(node);\n const names: string[] = [];\n // 안쪽 → 바깥 순으로 훑는다. 프레임워크 이름을 거른 뒤에 세야 limit 이\n // 잡음으로 먼저 차지 않는다.\n let depth = 0;\n while (fiber && names.length < limit && depth < 200) {\n depth += 1;\n const name = componentName(fiber.elementType ?? fiber.type);\n // 같은 이름이 연속으로 나오는 건(래퍼) 한 번만 남긴다.\n if (name && !isFrameworkName(name) && names[0] !== name) names.unshift(name);\n fiber = (fiber.return ?? null) as Record<string, unknown> | null;\n }\n return names;\n } catch {\n return [];\n }\n}\n\n/** 제보 본문에 실을 한 줄. 컴포넌트를 못 찾으면 null(빈 줄을 만들지 않는다). */\nexport function reactComponentSummary(node: Element): string | null {\n const path = reactComponentPath(node);\n return path.length > 0 ? path.join(\" > \") : null;\n}\n","import { reactComponentSummary } from \"./react-tree.js\";\n// 지목한 DOM 요소를 제보에 실을 수 있는 형태로 옮긴다.\n//\n// 목표는 \"나중에 이 요소를 다시 찾을 수 있게\" 하는 것이다. 그래서 태그·id·클래스만이 아니라\n// 형제 순서까지 담은 CSS 선택자 경로를 함께 만든다. 못 만들어도 제보는 성립해야 하므로\n// 이 파일의 함수들은 절대 던지지 않는다.\n\nimport type { ElementInfo } from \"@solhun/feedback-kit-core\";\n\n/** 요소 텍스트를 담을 때의 상한. 본문 전체가 실려 페이로드가 붓는 걸 막는다. */\nexport const ELEMENT_TEXT_MAX_CHARS = 200;\n\n/** 선택자 경로를 몇 단계까지 거슬러 올라갈지. 너무 깊으면 오히려 잘 깨진다. */\nexport const SELECTOR_MAX_DEPTH = 8;\n\n/** 담아 둘 속성. 전부 담으면 인라인 style 같은 게 페이로드를 채운다. */\nconst KEPT_ATTRS = [\n \"id\",\n \"class\",\n \"name\",\n \"type\",\n \"role\",\n \"href\",\n \"aria-label\",\n \"data-testid\",\n \"data-fk-source\",\n];\n\nfunction cssEscape(value: string): string {\n const fn = (globalThis as { CSS?: { escape?: (v: string) => string } }).CSS?.escape;\n if (typeof fn === \"function\") return fn(value);\n // CSS.escape 가 없는 환경 대비 최소 폴백. 식별자에 쓰이면 곤란한 문자만 막는다.\n return value.replace(/[^a-zA-Z0-9_-]/g, (ch) => `\\\\${ch}`);\n}\n\n/** 요소가 형제들 사이에서 몇 번째 같은 태그인지. 1부터 센다. */\nfunction nthOfType(el: Element): number {\n const parent = el.parentElement;\n if (!parent) return 1;\n let index = 0;\n for (const child of Array.from(parent.children)) {\n if (child.tagName === el.tagName) {\n index += 1;\n if (child === el) return index;\n }\n }\n return 1;\n}\n\n/** 같은 태그 형제가 둘 이상인가. 하나뿐이면 `:nth-of-type` 을 붙일 이유가 없다. */\nfunction hasTypeSiblings(el: Element): boolean {\n const parent = el.parentElement;\n if (!parent) return false;\n let count = 0;\n for (const child of Array.from(parent.children)) {\n if (child.tagName === el.tagName) {\n count += 1;\n if (count > 1) return true;\n }\n }\n return false;\n}\n\n/**\n * 요소까지의 CSS 선택자 경로.\n *\n * id 를 만나면 거기서 멈춘다 — 문서에서 유일하다고 가정할 수 있는 지점이라 그 위는 군더더기다.\n * 클래스는 쓰지 않는다: 유틸리티 클래스(`px-4` 같은 것)가 섞이면 선택자가 길기만 하고\n * 재현성은 오히려 떨어진다.\n */\nexport function cssSelectorPath(el: Element | null | undefined): string | null {\n if (!el || typeof el.tagName !== \"string\") return null;\n try {\n const parts: string[] = [];\n let node: Element | null = el;\n let depth = 0;\n\n while (node && depth < SELECTOR_MAX_DEPTH) {\n const tag = node.tagName.toLowerCase();\n if (tag === \"html\" || tag === \"body\") {\n parts.unshift(tag);\n break;\n }\n\n const id = node.getAttribute(\"id\");\n if (id) {\n parts.unshift(`#${cssEscape(id)}`);\n break; // id 위로는 올라갈 필요가 없다.\n }\n\n parts.unshift(hasTypeSiblings(node) ? `${tag}:nth-of-type(${nthOfType(node)})` : tag);\n node = node.parentElement;\n depth += 1;\n }\n\n return parts.length > 0 ? parts.join(\" > \") : null;\n } catch {\n return null;\n }\n}\n\n/** 눈에 보이는 텍스트를 한 줄로 눌러 담는다. */\nexport function visibleText(el: Element): string | null {\n const raw = (el as HTMLElement).innerText ?? el.textContent ?? \"\";\n const collapsed = raw.replace(/\\s+/g, \" \").trim();\n if (collapsed === \"\") return null;\n return collapsed.length > ELEMENT_TEXT_MAX_CHARS\n ? collapsed.slice(0, ELEMENT_TEXT_MAX_CHARS)\n : collapsed;\n}\n\n/** 지목된 요소를 `ElementInfo` 로 옮긴다. 실패해도 던지지 않는다. */\nexport function describeElement(el: Element | null | undefined): ElementInfo | null {\n if (!el || typeof el.tagName !== \"string\") return null;\n\n const attributes: Record<string, string> = {};\n for (const name of KEPT_ATTRS) {\n const value = el.getAttribute(name);\n if (value !== null) attributes[name] = value;\n }\n\n let boundingBox: ElementInfo[\"boundingBox\"] = null;\n try {\n const rect = el.getBoundingClientRect();\n boundingBox = { x: rect.left, y: rect.top, width: rect.width, height: rect.height };\n } catch {\n boundingBox = null;\n }\n\n const react = reactComponentSummary(el);\n\n return {\n tag: el.tagName.toLowerCase(),\n id: el.getAttribute(\"id\"),\n className: el.getAttribute(\"class\"),\n text: visibleText(el),\n boundingBox,\n attributes: {\n ...attributes,\n // 지목한 요소가 어느 컴포넌트에 속하는지. `div.flex.items-center` 만 오면\n // 고치는 사람이 코드에서 다시 찾아야 하지만, `LoginForm > SubmitButton` 이\n // 오면 바로 파일이 열린다. React 가 아니면 이 키 자체가 없다.\n ...(react ? { \"react-components\": react } : {}),\n },\n selector: cssSelectorPath(el),\n };\n}\n","// 요소 지목 모드와 마커를 브라우저 저장소에 남긴다.\n//\n// 왜 저장하나: 지목 모드는 페이지를 옮겨 다니며 쓰는 기능이라 새로고침·이동에 살아남아야 하고,\n// 찍은 마커는 같은 경로로 돌아왔을 때 원래 자리에 다시 보여야 한다.\n//\n// 키 이름은 `feedback-kit:` 로 시작하는 우리 고유 네임스페이스를 쓴다. 남의 위젯과 키가 겹치면\n// 서로의 상태를 지우게 된다.\n\n/** 지목 모드 on/off 플래그. */\nexport const PICKING_MODE_KEY = \"feedback-kit:picking-mode\";\n\n/** 경로별 마커 목록. 실제 키는 여기에 pathname 을 이어 붙인다. */\nexport const MARKER_KEY_PREFIX = \"feedback-kit:markers:\";\n\n/** 한 경로에 남겨 둘 마커 상한. 넘으면 오래된 것부터 버린다. */\nexport const MAX_MARKERS_PER_PATH = 50;\n\n/** 마커에 보이는 전송 상태. `sending` → `done`, 실패하면 `pending`. */\nexport type MarkerStatus = \"sending\" | \"done\" | \"pending\";\n\nexport interface StoredMarker {\n /** 제보의 `clientSubmissionId` 와 같다 — 전송 상태를 되짚어 갱신하려고 맞춰 둔다. */\n id: string;\n /** 뷰포트 기준 상대 좌표(0~1). 해상도가 달라도 같은 지점을 가리킨다. */\n x: number;\n y: number;\n selector: string | null;\n comment: string;\n status: MarkerStatus;\n /** 생성 시각(ISO). 상한을 넘겼을 때 무엇을 버릴지 정하는 기준. */\n at: string;\n}\n\n/** `localStorage` 가 만족하는 최소 계약. 테스트에서 갈아 끼운다. */\nexport interface StorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nfunction defaultStorage(): StorageLike | null {\n try {\n const ls = (globalThis as { localStorage?: StorageLike }).localStorage;\n return ls ?? null;\n } catch {\n // 사파리 프라이빗 모드처럼 접근 자체가 던지는 환경이 있다.\n return null;\n }\n}\n\nfunction markerKey(pathname: string): string {\n return `${MARKER_KEY_PREFIX}${pathname}`;\n}\n\n/**\n * 저장소 접근을 한 곳으로 모은다.\n *\n * 저장소가 없거나(SSR) 던지는 환경(프라이빗 모드·용량 초과)에서도 위젯이 죽으면 안 되므로\n * 모든 접근을 try/catch 로 감싸고 실패는 조용히 흘린다. 저장 실패는 \"마커가 안 남는다\" 정도의\n * 불편이지 제보 자체를 막을 이유가 아니다.\n */\nexport class MarkerStore {\n private readonly storage: StorageLike | null;\n\n constructor(storage?: StorageLike | null) {\n this.storage = storage === undefined ? defaultStorage() : storage;\n }\n\n private read(key: string): string | null {\n try {\n return this.storage?.getItem(key) ?? null;\n } catch {\n return null;\n }\n }\n\n private write(key: string, value: string): void {\n try {\n this.storage?.setItem(key, value);\n } catch {\n /* 저장 실패는 흘린다 */\n }\n }\n\n private drop(key: string): void {\n try {\n this.storage?.removeItem(key);\n } catch {\n /* 삭제 실패도 흘린다 */\n }\n }\n\n // ── 지목 모드 ─────────────────────────────────────────────────────────────\n\n isPickingActive(): boolean {\n return this.read(PICKING_MODE_KEY) === \"1\";\n }\n\n setPickingActive(active: boolean): void {\n if (active) this.write(PICKING_MODE_KEY, \"1\");\n else this.drop(PICKING_MODE_KEY);\n }\n\n // ── 마커 ──────────────────────────────────────────────────────────────────\n\n list(pathname: string): StoredMarker[] {\n const raw = this.read(markerKey(pathname));\n if (!raw) return [];\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n // 남이 쓴 값이나 옛 포맷이 섞여 있어도 위젯이 깨지지 않게 형태를 확인하고 거른다.\n return parsed.filter((item): item is StoredMarker => {\n if (typeof item !== \"object\" || item === null) return false;\n const m = item as Partial<StoredMarker>;\n return (\n typeof m.id === \"string\" && typeof m.x === \"number\" && typeof m.y === \"number\"\n );\n });\n } catch {\n return [];\n }\n }\n\n /** 같은 id 가 있으면 덮어쓰고, 없으면 뒤에 붙인다. */\n upsert(pathname: string, marker: StoredMarker): StoredMarker[] {\n const current = this.list(pathname);\n const index = current.findIndex((m) => m.id === marker.id);\n if (index >= 0) current[index] = marker;\n else current.push(marker);\n\n const trimmed =\n current.length > MAX_MARKERS_PER_PATH\n ? current.slice(current.length - MAX_MARKERS_PER_PATH)\n : current;\n this.write(markerKey(pathname), JSON.stringify(trimmed));\n return trimmed;\n }\n\n /** 전송 상태만 바꾼다. 없는 id 면 아무것도 하지 않는다. */\n updateStatus(pathname: string, id: string, status: MarkerStatus): StoredMarker[] {\n const current = this.list(pathname);\n const index = current.findIndex((m) => m.id === id);\n if (index < 0) return current;\n current[index] = { ...current[index], status };\n this.write(markerKey(pathname), JSON.stringify(current));\n return current;\n }\n\n clear(pathname: string): void {\n this.drop(markerKey(pathname));\n }\n}\n","// 요소 지목 모드 (웹 전용).\n//\n// 켜져 있는 동안 페이지의 아무 요소나 클릭하면 그 자리에 주석을 남긴다. 리포트 모달과 달리\n// \"열고 → 쓰고 → 닫는\" 절차가 없다 — 한 번 켜 두고 여러 곳을 연달아 지목하는 게 이 모드의 전부다.\n// 그래서 주석을 저장해도 모드는 꺼지지 않고, 페이지를 옮겨도 살아남는다.\n//\n// 페이지의 원래 클릭(링크 이동·버튼 동작)은 막아야 한다. 지목하려고 누른 링크가 실제로 이동해\n// 버리면 주석을 달 화면이 사라진다.\n\nimport {\n COMMENT_MAX_CHARS,\n normalizePin,\n type ElementInfo,\n type FeedbackPin,\n type FeedbackReport,\n type ReportParts,\n type QueueStatus,\n type SubmitOutcome,\n} from \"@solhun/feedback-kit-core\";\nimport { describeElement } from \"./element-info.js\";\nimport { MarkerStore, type StoredMarker } from \"./marker-store.js\";\n\n/** 위젯 자신의 UI 를 표시하는 속성. 이 안쪽 클릭은 가로채지 않는다. */\nexport const OWN_UI_ATTR = \"data-feedback-kit\";\n\n/** 지목 컨트롤러가 큐에게 요구하는 최소 계약. */\nexport interface PickingQueueLike {\n submit(report: FeedbackReport): Promise<SubmitOutcome>;\n subscribe?(listener: (status: QueueStatus) => void): () => void;\n getOutcome?(clientSubmissionId: string): Pick<SubmitOutcome, \"delivered\" | \"id\"> | null;\n}\n\nexport interface AnnotationPopupState {\n element: ElementInfo | null;\n /** 클릭한 지점의 뷰포트 상대 좌표(0~1). */\n point: FeedbackPin;\n comment: string;\n canSave: boolean;\n}\n\nexport interface PickingState {\n active: boolean;\n /** 지금 마우스가 올라가 있는 요소. 하이라이트를 그리는 근거. */\n hovered: ElementInfo | null;\n popup: AnnotationPopupState | null;\n /** 현재 경로에 남아 있는 마커들. */\n markers: readonly StoredMarker[];\n}\n\nexport type PickingListener = (state: PickingState) => void;\n\nexport interface ElementPickingOpts {\n queue: PickingQueueLike;\n createReport: (parts: ReportParts) => Promise<FeedbackReport>;\n store?: MarkerStore;\n /** 현재 경로. 마커는 경로별로 나눠 저장한다. */\n getPathname?: () => string;\n /** 이벤트를 들을 문서. 기본은 전역 `document`. */\n doc?: Document | null;\n /** 좌표를 정규화할 기준 크기. 기본은 현재 뷰포트. */\n getViewport?: () => { width: number; height: number };\n /**\n * 요소를 고르면 호출된다. 주면 **인라인 팝업 대신 이걸 부른다** —\n * 호스트가 리포트 모달을 열어 스크린샷·우선순위까지 받게 하기 위해서다.\n *\n * 모드는 켜진 채로 둔다. 한 번 켜고 여러 요소를 연달아 지목하는 게 이 모드의 목적이라,\n * 요소 하나 고를 때마다 모드가 꺼지면 매번 다시 켜야 한다.\n */\n onPick?: (element: ElementInfo | null, point: FeedbackPin) => void;\n}\n\nfunction defaultDocument(): Document | null {\n return (globalThis as { document?: Document }).document ?? null;\n}\n\nfunction defaultPathname(): string {\n const loc = (globalThis as { location?: { pathname?: string } }).location;\n return loc?.pathname ?? \"/\";\n}\n\nfunction defaultViewport(): { width: number; height: number } {\n const w = globalThis as { innerWidth?: number; innerHeight?: number };\n return { width: w.innerWidth ?? 0, height: w.innerHeight ?? 0 };\n}\n\n/** 위젯 자신의 UI 안에서 일어난 일인가. */\nfunction isOwnUi(target: EventTarget | null): boolean {\n const el = target as Element | null;\n if (!el || typeof el.closest !== \"function\") return false;\n return el.closest(`[${OWN_UI_ATTR}]`) !== null;\n}\n\nexport class ElementPickingController {\n private readonly queue: PickingQueueLike;\n private readonly createReport: (parts: ReportParts) => Promise<FeedbackReport>;\n private readonly onPick:\n | ((element: ElementInfo | null, point: FeedbackPin) => void)\n | null;\n private readonly store: MarkerStore;\n private readonly getPathname: () => string;\n private readonly doc: Document | null;\n private readonly getViewport: () => { width: number; height: number };\n private readonly unsubscribeQueue: () => void;\n\n private readonly listeners = new Set<PickingListener>();\n\n private active = false;\n private attached = false;\n private hovered: ElementInfo | null = null;\n private popup: AnnotationPopupState | null = null;\n private markers: StoredMarker[] = [];\n private pathWatch: ReturnType<typeof setInterval> | null = null;\n private lastPathname: string;\n private saving = false;\n\n private readonly onClick = (event: Event) => this.handleClick(event);\n private readonly onMouseOver = (event: Event) => this.handleMouseOver(event);\n\n constructor(opts: ElementPickingOpts) {\n this.queue = opts.queue;\n this.createReport = opts.createReport;\n this.store = opts.store ?? new MarkerStore();\n this.onPick = opts.onPick ?? null;\n this.getPathname = opts.getPathname ?? defaultPathname;\n this.doc = opts.doc === undefined ? defaultDocument() : opts.doc;\n this.getViewport = opts.getViewport ?? defaultViewport;\n this.lastPathname = this.getPathname();\n this.markers = this.store.list(this.lastPathname);\n this.unsubscribeQueue =\n this.queue.subscribe?.(() => {\n if (this.reconcileMarkerOutcomes()) this.emit();\n }) ?? (() => undefined);\n }\n\n // ── 조회 ──────────────────────────────────────────────────────────────────\n\n getState(): PickingState {\n return {\n active: this.active,\n hovered: this.hovered,\n popup: this.popup,\n markers: this.markers,\n };\n }\n\n get isActive(): boolean {\n return this.active;\n }\n\n subscribe(listener: PickingListener): () => void {\n this.listeners.add(listener);\n listener(this.getState());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n // ── 모드 on/off ───────────────────────────────────────────────────────────\n\n start(): void {\n if (this.active) return;\n this.active = true;\n this.store.setPickingActive(true);\n this.attach();\n this.startPathWatch();\n this.markers = this.store.list(this.getPathname());\n this.emit();\n }\n\n /** [지목 종료]. 저장된 플래그까지 지워서 새로고침해도 다시 켜지지 않게 한다. */\n stop(): void {\n this.active = false;\n this.store.setPickingActive(false);\n this.detach();\n this.stopPathWatch();\n this.hovered = null;\n this.popup = null;\n this.emit();\n }\n\n /**\n * 저장돼 있던 모드를 되살린다. 새로고침·페이지 이동 직후에 한 번 부른다.\n * @returns 되살아났으면 true.\n */\n restore(): boolean {\n if (!this.store.isPickingActive()) {\n this.markers = this.store.list(this.getPathname());\n this.emit();\n return false;\n }\n this.start();\n return true;\n }\n\n /** 경로가 바뀌었을 때 그 경로의 마커로 갈아 끼운다. */\n syncPath(): readonly StoredMarker[] {\n this.lastPathname = this.getPathname();\n this.markers = this.store.list(this.lastPathname);\n this.reconcileMarkerOutcomes();\n this.popup = null;\n this.hovered = null;\n this.emit();\n return this.markers;\n }\n\n dispose(): void {\n this.detach();\n this.stopPathWatch();\n this.unsubscribeQueue();\n this.listeners.clear();\n }\n\n // ── DOM 이벤트 ────────────────────────────────────────────────────────────\n\n private attach(): void {\n if (this.attached || !this.doc) return;\n // 캡처 단계에서 잡아야 페이지의 핸들러보다 먼저 막을 수 있다.\n this.doc.addEventListener(\"click\", this.onClick, true);\n this.doc.addEventListener(\"mouseover\", this.onMouseOver, true);\n this.attached = true;\n }\n\n private detach(): void {\n if (!this.attached || !this.doc) return;\n this.doc.removeEventListener(\"click\", this.onClick, true);\n this.doc.removeEventListener(\"mouseover\", this.onMouseOver, true);\n this.attached = false;\n }\n\n private handleMouseOver(event: Event): void {\n if (!this.active || isOwnUi(event.target)) return;\n this.hovered = describeElement(event.target as Element | null);\n this.emit();\n }\n\n private handleClick(event: Event): void {\n if (!this.active) return;\n if (isOwnUi(event.target)) return;\n\n // 페이지가 이 클릭을 보지 못하게 한다 — 링크 이동·버튼 동작을 전부 막는다.\n event.preventDefault();\n event.stopPropagation();\n if (typeof event.stopImmediatePropagation === \"function\") {\n event.stopImmediatePropagation();\n }\n\n const mouse = event as MouseEvent;\n this.openAnnotation(event.target as Element | null, {\n x: mouse.clientX ?? 0,\n y: mouse.clientY ?? 0,\n });\n }\n\n // ── 주석 팝업 ─────────────────────────────────────────────────────────────\n\n /** 클릭 지점 기준으로 주석 팝업을 연다. 좌표는 해상도 무관한 상대값으로 접어 둔다. */\n openAnnotation(element: Element | null, clientPoint: { x: number; y: number }): void {\n const point = normalizePin(clientPoint, this.getViewport());\n if (this.onPick) {\n // 호스트가 모달을 연다. 팝업은 만들지 않는다(둘이 동시에 뜨면 안 된다).\n this.onPick(describeElement(element), point);\n return;\n }\n this.popup = {\n element: describeElement(element),\n point,\n comment: \"\",\n canSave: false,\n };\n this.emit();\n }\n\n setAnnotationComment(value: string): void {\n if (!this.popup) return;\n this.popup = {\n ...this.popup,\n comment: value,\n canSave: value.trim().length > 0 && value.length <= COMMENT_MAX_CHARS,\n };\n this.emit();\n }\n\n /** 취소 — 마커도 제보도 남기지 않는다. 모드는 켜진 채로 둔다. */\n cancelAnnotation(): void {\n this.popup = null;\n this.emit();\n }\n\n /**\n * 제출된 제보를 마커로 남긴다. 모달 경로에서 쓴다 — 마커 생성이 팝업 저장 안에만\n * 있으면, 리포트 모달로 보낸 지목 제보는 화면에 흔적이 남지 않는다(\"보냈는지 알 수\n * 없다\"가 이 모드가 없애려던 불편이다).\n */\n noteSubmitted(report: FeedbackReport, delivered: boolean): void {\n if (!report.element && !report.pin) return;\n const pathname = this.getPathname();\n const marker: StoredMarker = {\n id: report.clientSubmissionId,\n x: report.pin?.x ?? 0,\n y: report.pin?.y ?? 0,\n selector: report.element?.selector ?? null,\n comment: report.comment,\n status: delivered ? \"done\" : \"pending\",\n at: report.createdAt,\n };\n this.markers = this.store.upsert(pathname, marker);\n this.emit();\n }\n\n /**\n * 주석 저장. 마커를 먼저 \"전송 중\"으로 찍고 나서 보낸다 — 사용자는 결과를 기다리지 않고\n * 다음 요소로 넘어갈 수 있어야 한다.\n *\n * @returns 검증에 걸려 아무것도 하지 않았으면 `null`.\n */\n async saveAnnotation(): Promise<SubmitOutcome | null> {\n const popup = this.popup;\n if (!popup || !popup.canSave || this.saving) return null;\n this.saving = true;\n\n const pathname = this.getPathname();\n const parts: ReportParts = {\n kind: \"annotation\",\n comment: popup.comment,\n priority: \"unset\",\n screenshot: null,\n pin: popup.point,\n element: popup.element,\n };\n\n let report: FeedbackReport;\n try {\n report = await this.createReport(parts);\n } catch {\n // 조립이 실패하면 남길 마커의 id 도 없다. 팝업만 닫고 조용히 물러난다.\n this.popup = null;\n this.emit();\n this.saving = false;\n return null;\n }\n\n const marker: StoredMarker = {\n id: report.clientSubmissionId,\n x: popup.point.x,\n y: popup.point.y,\n selector: popup.element?.selector ?? null,\n comment: popup.comment,\n status: \"sending\",\n at: report.createdAt,\n };\n this.markers = this.store.upsert(pathname, marker);\n this.popup = null; // 모드는 그대로 — 다음 요소를 바로 지목할 수 있다.\n this.emit();\n\n let outcome: SubmitOutcome;\n try {\n outcome = await this.queue.submit(report);\n } catch {\n outcome = { delivered: false, id: null, queued: true };\n }\n\n const next = this.store.updateStatus(\n pathname,\n marker.id,\n outcome.delivered ? \"done\" : \"pending\",\n );\n // 저장 중에 경로가 바뀌었으면 화면에 보이는 목록은 건드리지 않는다.\n if (pathname === this.getPathname()) this.markers = next;\n this.emit();\n\n this.saving = false;\n return outcome;\n }\n\n private startPathWatch(): void {\n if (this.pathWatch !== null || typeof setInterval !== \"function\") return;\n this.lastPathname = this.getPathname();\n this.pathWatch = setInterval(() => {\n const pathname = this.getPathname();\n if (pathname !== this.lastPathname) this.syncPath();\n }, 200);\n }\n\n private stopPathWatch(): void {\n if (this.pathWatch === null) return;\n clearInterval(this.pathWatch);\n this.pathWatch = null;\n }\n\n /** 전역 pending 수가 아니라 마커와 같은 clientSubmissionId의 확정 성공만 완료로 바꾼다. */\n private reconcileMarkerOutcomes(): boolean {\n if (!this.queue.getOutcome) return false;\n let changed = false;\n for (const marker of this.markers) {\n const outcome = this.queue.getOutcome(marker.id);\n if (marker.status !== \"done\" && outcome?.delivered) {\n this.markers = this.store.updateStatus(this.lastPathname, marker.id, \"done\");\n changed = true;\n }\n }\n return changed;\n }\n\n private emit(): void {\n const state = this.getState();\n for (const listener of this.listeners) listener(state);\n }\n}\n","import type { ScreenshotCapture, ScreenshotReencode } from \"@solhun/feedback-kit-core\";\nimport html2canvas from \"html2canvas-pro\";\n\nimport { OWN_UI_ATTR } from \"./picking.js\";\n\n/**\n * 왜 라이브러리를 쓰나 (직접 만든 SVG foreignObject 방식에서 갈아탄 이유):\n *\n * `<foreignObject>` 로 DOM 을 감싼 SVG 를 캔버스에 그리면 **캔버스가 tainted 되어**\n * `toDataURL()` 이 `SecurityError` 를 던진다. 즉 캡처가 항상 null 이 되고, 사용자는\n * 매번 \"캡처 실패 — 파일로 첨부할 수 있습니다\"만 본다. 실측으로 확인했다:\n * 같은 브라우저에서 일반 SVG(rect)는 오염되지 않고 `foreignObject` 만 오염된다.\n *\n * html2canvas-pro(MIT)는 foreignObject 를 쓰지 않는다. DOM 을 순회하며 캔버스에\n * 직접 그리므로 오염 자체가 생기지 않는다. 명세의 \"DOM 을 이미지로 변환하는 MIT\n * 라이브러리를 쓴다\"도 원래 이 뜻이었다.\n */\n\nfunction payload(dataUrl: string): string | null {\n const comma = dataUrl.indexOf(\",\");\n return comma >= 0 ? dataUrl.slice(comma + 1) : null;\n}\n\n/** 캡처에서 빼야 할 노드인가(위젯 자신의 UI). 결과에 자기 모달이 찍히면 안 된다. */\nexport function isOwnUi(node: Element): boolean {\n return node.hasAttribute?.(OWN_UI_ATTR) === true\n || node.closest?.(`[${OWN_UI_ATTR}]`) !== null;\n}\n\n/**\n * 웹 기본 캡처 — 현재 viewport 를 JPEG 로.\n *\n * 실패하면 **null 을 돌려준다**(던지지 않는다). 코어가 \"캡처 실패\" 상태로 바꾸고\n * 사용자는 파일로 첨부할 수 있다 — 그림 때문에 제보 자체를 잃지 않는다.\n */\n/**\n * 캔버스를 같은 크기의 <img> 로 바꾼다.\n *\n * html2canvas 는 <canvas> 의 픽셀은 가져오지만 **배치를 잘못 잡는 경우가 있다.**\n * 실측: KITS 홈의 Rive 캔버스(700×933, 왼쪽 x=0)가 캡처본에서는 가운데로 옮겨져\n * 본문 텍스트와 버튼을 덮었다. 원본 화면과 캡처를 나란히 놓고서야 보였다.\n *\n * 캔버스를 이미지로 바꾸면 일반 요소로 배치되므로 위치가 어긋나지 않는다.\n * 픽셀을 못 읽는 캔버스(WebGL 에서 preserveDrawingBuffer 미설정, 오염 등)는\n * 원본을 그대로 둔다 — 자리를 비우는 것보다 어긋나더라도 뭔가 보이는 편이 낫다.\n */\nfunction freezeCanvases(source: Document, clone: Document): void {\n const originals = source.querySelectorAll(\"canvas\");\n const clones = clone.querySelectorAll(\"canvas\");\n for (let index = 0; index < originals.length && index < clones.length; index++) {\n const from = originals[index];\n const to = clones[index];\n let dataUrl: string;\n try {\n dataUrl = from.toDataURL(\"image/png\");\n } catch {\n continue;\n }\n if (!dataUrl.startsWith(\"data:image\") || dataUrl.length < 64) continue;\n\n const box = from.getBoundingClientRect();\n if (box.width < 1 || box.height < 1) continue;\n\n const image = clone.createElement(\"img\");\n image.src = dataUrl;\n // 원래 캔버스가 받던 레이아웃을 그대로 물려받게 한다.\n image.setAttribute(\"style\", to.getAttribute(\"style\") ?? \"\");\n image.style.width = `${box.width}px`;\n image.style.height = `${box.height}px`;\n image.className = to.className;\n to.replaceWith(image);\n }\n}\n\nexport const captureWebScreenshot: ScreenshotCapture = async () => {\n const doc = globalThis.document;\n const view = doc?.defaultView;\n if (!doc?.documentElement || !view) return null;\n\n try {\n const canvas = await html2canvas(doc.documentElement, {\n // 현재 보이는 화면만. 문서 전체를 그리면 제보와 무관한 영역까지 커진다.\n width: view.innerWidth,\n height: view.innerHeight,\n x: view.scrollX,\n y: view.scrollY,\n windowWidth: view.innerWidth,\n windowHeight: view.innerHeight,\n // 배율은 2배까지만. 그 위는 용량만 커지고 8 MiB 한도에 먼저 걸린다.\n scale: Math.min(2, Math.max(1, view.devicePixelRatio || 1)),\n // 위젯 자신의 UI 는 결과에서 뺀다.\n ignoreElements: isOwnUi,\n // 캔버스(Rive·차트 등)는 이미지로 바꿔 넘긴다 — freezeCanvases 주석 참고.\n onclone: (cloned: Document) => freezeCanvases(doc, cloned),\n // 외부 이미지가 CORS 를 안 열어두면 그것 때문에 전체가 실패할 수 있다.\n // 못 가져오는 리소스는 건너뛰고 나머지를 그린다.\n useCORS: true,\n allowTaint: false,\n logging: false,\n backgroundColor: null,\n });\n return { base64: payload(canvas.toDataURL(\"image/jpeg\", 0.9)) ?? \"\", contentType: \"image/jpeg\" };\n } catch {\n return null;\n }\n};\n\n/** 8 MiB 정책이 요구할 때 기존 이미지를 JPEG 품질 단계로 다시 인코딩한다. */\nexport const reencodeWebScreenshot: ScreenshotReencode = async (shot, quality) => {\n const doc = globalThis.document;\n if (!doc) return null;\n try {\n const image = await new Promise<HTMLImageElement | null>((resolve) => {\n const img = new Image();\n img.onload = () => resolve(img);\n img.onerror = () => resolve(null);\n img.src = `data:${shot.contentType};base64,${shot.base64}`;\n });\n if (!image) return null;\n const canvas = doc.createElement(\"canvas\");\n canvas.width = image.naturalWidth || image.width;\n canvas.height = image.naturalHeight || image.height;\n const context = canvas.getContext(\"2d\");\n if (!context) return null;\n context.drawImage(image, 0, 0);\n const base64 = payload(canvas.toDataURL(\"image/jpeg\", quality));\n return base64 ? { base64, contentType: \"image/jpeg\" } : null;\n } catch {\n return null;\n }\n};\n","// 웹 위젯 조립.\n//\n// 코어의 화면 지도(`WidgetController`)와 웹 전용 지목 모드(`ElementPickingController`)를\n// 한 덩어리로 묶는다. 둘을 따로 두면 \"모달에서 [요소 지목]을 눌렀다\"와 \"지목 오버레이가 떴다\"가\n// 서로 모르는 상태가 되므로, 연결 지점을 여기 한 곳으로 모은다.\n//\n// 지목 모드 on/off 의 유일한 입구는 `widget.startPicking()` / `widget.stopPicking()` 이다.\n// 지목 컨트롤러를 직접 켜면 화면 지도가 그 사실을 모른다.\n\nimport { WidgetController, type WidgetControllerOpts } from \"@solhun/feedback-kit-core\";\nimport { MarkerStore } from \"./marker-store.js\";\nimport { ElementPickingController } from \"./picking.js\";\nimport { captureWebScreenshot, reencodeWebScreenshot } from \"./screenshot.js\";\n\nexport interface WebWidgetOpts\n extends Omit<WidgetControllerOpts, \"platform\" | \"initialPicking\" | \"onPickingChange\"> {\n /** 지목 모드 플래그와 마커를 담아 둘 저장소. 기본은 `localStorage`. */\n store?: MarkerStore;\n /** 현재 경로. 마커를 경로별로 나눠 담는 기준. */\n getPathname?: () => string;\n /** 지목 이벤트를 들을 문서. 기본은 전역 `document`. */\n doc?: Document | null;\n getViewport?: () => { width: number; height: number };\n}\n\nexport interface WebWidget {\n widget: WidgetController;\n picking: ElementPickingController;\n store: MarkerStore;\n dispose(): void;\n}\n\nexport function createWebWidget(opts: WebWidgetOpts): WebWidget {\n const store = opts.store ?? new MarkerStore();\n // widget 이 아래에서 만들어지므로, 콜백이 불릴 때 참조할 수 있게 늦게 채운다.\n let widgetRef: WidgetController | null = null;\n\n const picking = new ElementPickingController({\n queue: opts.queue,\n createReport: opts.createReport,\n store,\n getPathname: opts.getPathname,\n doc: opts.doc,\n getViewport: opts.getViewport,\n // 요소를 고르면 인라인 팝업이 아니라 **리포트 모달**을 연다.\n // 코멘트만 받던 팝업과 달리 스크린샷·우선순위까지 같은 화면에서 받는다.\n // 모드는 켜진 채로 둬서 한 번 켜고 여러 요소를 연달아 지목할 수 있다.\n onPick: (element, point) => {\n // modal.open() 을 직접 부르지 않는다 — 그러면 모달 상태만 열리고 화면은\n // picking 에 머물러 아무것도 안 보인다. 화면 전환은 컨트롤러가 한다.\n void widgetRef?.openReport({ element });\n widgetRef?.modal.setPin(point);\n },\n });\n\n const widget = new WidgetController({\n ...opts,\n capture: opts.capture === undefined ? captureWebScreenshot : opts.capture,\n reencode: opts.reencode === undefined ? reencodeWebScreenshot : opts.reencode,\n platform: \"web\",\n // 새로고침·페이지 이동 직후에도 켜져 있던 모드를 그대로 이어받는다.\n initialPicking: store.isPickingActive(),\n onPickingChange: (active) => {\n if (active) picking.start();\n else picking.stop();\n },\n });\n\n widgetRef = widget;\n\n // 모달로 보낸 지목 제보도 마커로 남긴다. 마커 생성이 옛 팝업 저장 경로에만 있으면\n // 모달로 보낸 제보는 화면에 흔적이 없다 — \"보냈는지 알 수 없다\"가 이 모드가\n // 없애려던 불편이라, 그게 되살아난다.\n const originalSubmit = widget.submitReport.bind(widget);\n widget.submitReport = async () => {\n const outcome = await originalSubmit();\n const report = widget.modal.getLastReport();\n if (report?.element) picking.noteSubmitted(report, outcome?.delivered === true);\n return outcome;\n };\n\n // 저장돼 있던 모드면 여기서 DOM 리스너까지 다시 붙인다. 꺼져 있으면 마커만 읽고 만다.\n picking.restore();\n\n return {\n widget,\n picking,\n store,\n dispose(): void {\n picking.dispose();\n widget.dispose();\n },\n };\n}\n","import {\n FLOATING_BUTTON_ID,\n MODAL_ACTION_CANCEL,\n MODAL_ACTION_PICK,\n MODAL_ACTION_REMOVE_SCREENSHOT,\n MODAL_ACTION_RETRY,\n MODAL_ACTION_SEND,\n MODAL_FIELD_COMMENT,\n MODAL_FIELD_PRIORITY,\n type FeedbackPriority,\n type FeedbackScreenshot,\n type WidgetState,\n} from \"@solhun/feedback-kit-core\";\nimport {\n type ChangeEvent,\n type CSSProperties,\n type FormEvent,\n type KeyboardEvent,\n type MouseEvent,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport type { PickingState } from \"./picking.js\";\nimport { OWN_UI_ATTR } from \"./picking.js\";\nimport { createWebWidget, type WebWidget, type WebWidgetOpts } from \"./widget.js\";\n\n/**\n * 패키지를 붙인 제품의 디자인 시스템을 침범하지 않는 중립 토큰이다.\n * 모든 값은 인라인 스타일로만 쓰여 호스트의 전역 CSS와 충돌하지 않는다.\n */\nconst TOKENS = {\n ink: \"#17202a\",\n muted: \"#667085\",\n surface: \"#ffffff\",\n subtle: \"#f4f6f8\",\n line: \"#d7dce2\",\n accent: \"#315d73\",\n accentHover: \"#274b5d\",\n danger: \"#b42318\",\n warning: \"#8a5a00\",\n success: \"#18794e\",\n shadow: \"0 18px 50px rgba(23, 32, 42, 0.2)\",\n radius: \"14px\",\n} as const;\n\nconst OWN_UI_PROPS = { [OWN_UI_ATTR]: \"\" } as Record<string, string>;\nconst FONT_STACK =\n \"ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\";\n\nexport type FeedbackKitProps = WebWidgetOpts;\n\nconst INITIAL_PICKING_STATE: PickingState = {\n active: false,\n hovered: null,\n popup: null,\n markers: [],\n};\n\nfunction screenshotSource(screenshot: FeedbackScreenshot): string {\n return `data:${screenshot.contentType};base64,${screenshot.base64}`;\n}\n\nfunction readScreenshotFile(file: File): Promise<FeedbackScreenshot | null> {\n if (file.type !== \"image/png\" && file.type !== \"image/jpeg\") {\n return Promise.resolve(null);\n }\n const contentType: FeedbackScreenshot[\"contentType\"] = file.type;\n\n return new Promise((resolve) => {\n const reader = new FileReader();\n reader.onerror = () => resolve(null);\n reader.onload = () => {\n const result = typeof reader.result === \"string\" ? reader.result : \"\";\n const comma = result.indexOf(\",\");\n if (comma < 0) {\n resolve(null);\n return;\n }\n resolve({\n base64: result.slice(comma + 1),\n contentType,\n });\n };\n reader.readAsDataURL(file);\n });\n}\n\nfunction nextPaint(): Promise<void> {\n return new Promise((resolve) => {\n if (typeof requestAnimationFrame === \"function\") {\n requestAnimationFrame(() => resolve());\n } else {\n setTimeout(resolve, 0);\n }\n });\n}\n\nfunction focusableElements(container: HTMLElement): HTMLElement[] {\n return Array.from(\n container.querySelectorAll<HTMLElement>(\n \"button:not([disabled]), textarea:not([disabled]), select:not([disabled]), input:not([disabled]):not([type='hidden']), [tabindex]:not([tabindex='-1'])\",\n ),\n ).filter((node) => node.getAttribute(\"aria-hidden\") !== \"true\");\n}\n\nfunction trapTab(event: KeyboardEvent<HTMLElement>, container: HTMLElement): void {\n if (event.key !== \"Tab\") return;\n const items = focusableElements(container);\n if (items.length === 0) {\n event.preventDefault();\n return;\n }\n\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = document.activeElement;\n if (event.shiftKey && (active === first || !container.contains(active))) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (active === last || !container.contains(active))) {\n event.preventDefault();\n first.focus();\n }\n}\n\nconst baseButton: CSSProperties = {\n minHeight: 38,\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 9,\n background: TOKENS.surface,\n color: TOKENS.ink,\n padding: \"8px 12px\",\n font: \"inherit\",\n fontWeight: 650,\n cursor: \"pointer\",\n};\n\nconst primaryButton: CSSProperties = {\n ...baseButton,\n borderColor: TOKENS.accent,\n background: TOKENS.accent,\n color: TOKENS.surface,\n};\n\nconst inputStyle: CSSProperties = {\n width: \"100%\",\n boxSizing: \"border-box\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 9,\n background: TOKENS.surface,\n color: TOKENS.ink,\n padding: \"10px 11px\",\n font: \"inherit\",\n};\n\nfunction MarkerStatus({ status }: { status: \"sending\" | \"done\" | \"pending\" }) {\n const presentation =\n status === \"sending\"\n ? { icon: \"↻\", text: \"전송 중\", color: TOKENS.accent }\n : status === \"done\"\n ? { icon: \"✓\", text: \"완료\", color: TOKENS.success }\n : { icon: \"◷\", text: \"대기\", color: TOKENS.warning };\n\n return (\n <span style={{ display: \"inline-flex\", gap: 4, alignItems: \"center\", color: presentation.color }}>\n <span\n aria-hidden=\"true\"\n style={\n status === \"sending\"\n ? { display: \"inline-block\", animation: \"feedback-kit-spin 900ms linear infinite\" }\n : undefined\n }\n >\n {presentation.icon}\n </span>\n <span>{presentation.text}</span>\n </span>\n );\n}\n\n/**\n * `createWebWidget`의 헤드리스 상태를 실제 DOM으로 그리는 공개 React 진입점.\n * 큐·제보 조립·스크린샷 캡처는 모두 props로 주입된 기존 계약을 그대로 사용한다.\n */\nexport function FeedbackKit(props: FeedbackKitProps) {\n const rootRef = useRef<HTMLDivElement | null>(null);\n const floatingButtonRef = useRef<HTMLButtonElement | null>(null);\n const modalRef = useRef<HTMLDivElement | null>(null);\n const confirmRef = useRef<HTMLDivElement | null>(null);\n const fileInputRef = useRef<HTMLInputElement | null>(null);\n const previousScreenRef = useRef<WidgetState[\"screen\"]>(\"button\");\n const previousModalOpenRef = useRef(false);\n const previousConfirmVisibleRef = useRef(false);\n const [kit, setKit] = useState<WebWidget | null>(null);\n const [widgetState, setWidgetState] = useState<WidgetState | null>(null);\n const [pickingState, setPickingState] = useState<PickingState>(INITIAL_PICKING_STATE);\n const [announcement, setAnnouncement] = useState<string | null>(null);\n\n const {\n queue,\n createReport,\n capture,\n reencode,\n screenshotLimitBytes,\n store,\n getPathname,\n doc,\n getViewport,\n } = props;\n\n useEffect(() => {\n const created = createWebWidget({\n queue,\n createReport,\n capture,\n reencode,\n screenshotLimitBytes,\n store,\n getPathname,\n doc,\n getViewport,\n });\n setKit(created);\n const unsubscribeWidget = created.widget.subscribe(setWidgetState);\n const unsubscribePicking = created.picking.subscribe(setPickingState);\n\n return () => {\n unsubscribeWidget();\n unsubscribePicking();\n created.dispose();\n };\n }, [\n queue,\n createReport,\n capture,\n reencode,\n screenshotLimitBytes,\n store,\n getPathname,\n doc,\n getViewport,\n ]);\n\n useEffect(() => {\n if (!widgetState) return;\n const { modal, screen } = widgetState;\n if (modal.submitMessage) setAnnouncement(modal.submitMessage);\n\n if (modal.open && !previousModalOpenRef.current && modal.focused) {\n document.querySelector<HTMLElement>(`[data-fk-focus-id=\"${modal.focused}\"]`)?.focus();\n }\n if (modal.closeConfirmVisible && !previousConfirmVisibleRef.current) confirmRef.current?.focus();\n\n const wasModal = previousScreenRef.current === \"modal\";\n if (wasModal && screen === \"button\" && modal.restoreFocusTo === FLOATING_BUTTON_ID) {\n void nextPaint().then(() => floatingButtonRef.current?.focus());\n }\n previousScreenRef.current = screen;\n previousModalOpenRef.current = modal.open;\n previousConfirmVisibleRef.current = modal.closeConfirmVisible;\n }, [widgetState]);\n\n /**\n * 우리 오버레이가 떠 있는 동안 Escape 를 **우리가 가져간다.**\n *\n * 왜 React 의 onKeyDown 으로는 안 되나: 그건 모달 div 에 붙은 핸들러라 `preventDefault`\n * 를 해도 **네이티브 이벤트는 그대로 document 까지 올라간다.** 호스트 앱이 거기서 듣고\n * 있으면(대부분의 모달 구현이 그렇다) 호스트 모달이 닫힌다.\n *\n * 실측(2026-08-09, cardnews 콘솔): 호스트 모달 위에 우리 모달을 띄우고 Escape 를 누르면\n * **뒤에 있던 호스트 모달이 닫히고 우리 모달이 남았다.** 위에 뜬 쪽이 Escape 를 가져가는\n * 게 상식이고, 지금은 사용자가 우리 모달을 닫으려다 뒤에서 쓰던 폼을 잃는다. z-index 는\n * 우리가 최댓값이라 이겼는데 키보드는 호스트가 먼저 듣고 있어서 졌다.\n *\n * 그래서 document 의 **캡처 단계**에서 잡는다. 캡처는 어떤 버블 리스너보다 먼저 돌아서\n * `stopPropagation` 이 호스트에 닿기 전에 끊는다. 대신 우리 React 핸들러에도 안 가므로\n * 닫기 처리를 여기서 직접 한다.\n *\n * **우리 오버레이가 없을 때는 아무것도 하지 않는다** — 호스트의 Escape 를 상시로 뺏으면\n * 그건 그것대로 남의 앱을 망가뜨리는 것이다.\n */\n useEffect(() => {\n if (!kit || !widgetState) return;\n const { screen, modal } = widgetState;\n if (screen !== \"modal\" && screen !== \"pin\") return;\n const widget = kit.widget;\n\n function onKeyDownCapture(event: globalThis.KeyboardEvent): void {\n if (event.key !== \"Escape\") return;\n // 조합 중(한글 입력 등)의 Escape 는 IME 취소다. 모달을 닫으면 글자를 잃는다.\n if (event.isComposing) return;\n event.stopPropagation();\n event.preventDefault();\n if (screen === \"pin\") widget.cancelPin();\n else if (modal.closeConfirmVisible) widget.cancelCloseReport();\n else widget.closeReport();\n }\n\n document.addEventListener(\"keydown\", onKeyDownCapture, true);\n return () => document.removeEventListener(\"keydown\", onKeyDownCapture, true);\n }, [kit, widgetState]);\n\n if (!kit || !widgetState) {\n return <div {...OWN_UI_PROPS} data-feedback-kit-loading=\"true\" />;\n }\n\n const activeKit = kit;\n const { modal, screen } = widgetState;\n const draftLocked = modal.submitStatus === \"sending\" || modal.submitStatus === \"pending\";\n\n async function withOwnUiHidden(action: () => Promise<void>): Promise<void> {\n const root = rootRef.current;\n const previousVisibility = root?.style.visibility ?? \"\";\n if (root) root.style.visibility = \"hidden\";\n try {\n await nextPaint();\n await action();\n } finally {\n if (root) root.style.visibility = previousVisibility;\n }\n }\n\n async function openReport(): Promise<void> {\n setAnnouncement(null);\n await withOwnUiHidden(() => activeKit.widget.openReport());\n }\n\n async function recapture(): Promise<void> {\n await withOwnUiHidden(() => activeKit.widget.modal.recapture());\n }\n\n async function attachFile(event: ChangeEvent<HTMLInputElement>): Promise<void> {\n const input = event.currentTarget;\n const file = input.files?.[0];\n if (file) {\n const screenshot = await readScreenshotFile(file);\n if (screenshot) await activeKit.widget.modal.attachFile(screenshot);\n }\n input.value = \"\";\n }\n\n function closeReport(): void {\n activeKit.widget.closeReport();\n }\n\n function handleModalKeyDown(event: KeyboardEvent<HTMLDivElement>): void {\n if (event.key === \"Escape\") {\n event.preventDefault();\n if (modal.closeConfirmVisible) activeKit.widget.cancelCloseReport();\n else closeReport();\n return;\n }\n trapTab(event, modal.closeConfirmVisible ? confirmRef.current ?? event.currentTarget : event.currentTarget);\n }\n\n function handleBackdrop(event: MouseEvent<HTMLDivElement>): void {\n if (event.target === event.currentTarget) closeReport();\n }\n\n function submitReport(event: FormEvent<HTMLFormElement>): void {\n event.preventDefault();\n void activeKit.widget.submitReport();\n }\n\n const floating = screen === \"button\" || screen === \"picking\";\n const hoverBox = pickingState.hovered?.boundingBox;\n const popup = pickingState.popup;\n\n return (\n <div\n ref={rootRef}\n {...OWN_UI_PROPS}\n style={{ fontFamily: FONT_STACK, color: TOKENS.ink, fontSize: 14, lineHeight: 1.45 }}\n >\n <style>{`@keyframes feedback-kit-spin{to{transform:rotate(360deg)}}`}</style>\n\n {floating ? (\n <button\n ref={floatingButtonRef}\n id={FLOATING_BUTTON_ID}\n type=\"button\"\n disabled={screen === \"picking\"}\n aria-label={screen === \"picking\" ? \"요소 지목 중\" : \"피드백 보내기\"}\n onClick={() => void openReport()}\n style={{\n ...primaryButton,\n position: \"fixed\",\n right: 24,\n bottom: 24,\n zIndex: 2147483600,\n minWidth: 112,\n minHeight: 46,\n borderRadius: 24,\n boxShadow: TOKENS.shadow,\n opacity: screen === \"picking\" ? 0.76 : 1,\n cursor: screen === \"picking\" ? \"default\" : \"pointer\",\n }}\n >\n {screen === \"picking\" ? \"지목 중\" : \"피드백\"}\n </button>\n ) : null}\n\n {announcement && !modal.open ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n style={{\n position: \"fixed\",\n right: 24,\n bottom: 82,\n zIndex: 2147483601,\n padding: \"10px 14px\",\n borderRadius: 10,\n background: TOKENS.ink,\n color: TOKENS.surface,\n boxShadow: TOKENS.shadow,\n }}\n >\n {announcement}\n </div>\n ) : null}\n\n {screen === \"modal\" && modal.open ? (\n <div\n onMouseDown={handleBackdrop}\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483602,\n display: \"grid\",\n placeItems: \"center\",\n padding: 20,\n background: \"rgba(23, 32, 42, 0.48)\",\n }}\n >\n <div\n ref={modalRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"feedback-kit-dialog-title\"\n onKeyDown={handleModalKeyDown}\n style={{\n width: \"min(520px, 100%)\",\n maxHeight: \"min(760px, calc(100vh - 40px))\",\n overflowY: \"auto\",\n boxSizing: \"border-box\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: TOKENS.radius,\n background: TOKENS.surface,\n padding: 22,\n boxShadow: TOKENS.shadow,\n }}\n >\n <form onSubmit={submitReport}>\n <h2 id=\"feedback-kit-dialog-title\" style={{ margin: \"0 0 4px\", fontSize: 20 }}>\n 피드백 보내기\n </h2>\n <p style={{ margin: \"0 0 18px\", color: TOKENS.muted }}>\n 현재 화면의 문제나 의견을 남겨주세요.\n </p>\n\n <section aria-labelledby=\"feedback-kit-screenshot-label\" style={{ marginBottom: 16 }}>\n <strong id=\"feedback-kit-screenshot-label\">스크린샷</strong>\n <div\n style={{\n display: \"grid\",\n placeItems: \"center\",\n minHeight: 150,\n marginTop: 7,\n overflow: \"hidden\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 10,\n background: TOKENS.subtle,\n }}\n >\n {modal.screenshotStatus === \"capturing\" ? (\n <span role=\"status\">화면 캡처 중…</span>\n ) : modal.screenshot ? (\n <img\n src={screenshotSource(modal.screenshot)}\n alt=\"자동 캡처된 현재 화면 미리보기\"\n style={{ display: \"block\", maxWidth: \"100%\", maxHeight: 260, objectFit: \"contain\" }}\n />\n ) : (\n <span role=\"status\" style={{ padding: 18, color: TOKENS.muted, textAlign: \"center\" }}>\n {modal.screenshotMessage ?? \"스크린샷 없음\"}\n </span>\n )}\n </div>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 8, marginTop: 8 }}>\n {modal.screenshot ? (\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_REMOVE_SCREENSHOT}\n disabled={draftLocked}\n onClick={() => kit.widget.modal.removeScreenshot()}\n style={baseButton}\n >\n 제거\n </button>\n ) : null}\n <button\n type=\"button\"\n disabled={draftLocked || modal.screenshotStatus === \"capturing\"}\n onClick={() => void recapture()}\n style={baseButton}\n >\n 다시 찍기\n </button>\n <button\n type=\"button\"\n disabled={draftLocked}\n onClick={() => fileInputRef.current?.click()}\n style={baseButton}\n >\n {modal.screenshot ? \"파일로 교체\" : \"파일로 첨부\"}\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/png,image/jpeg\"\n tabIndex={-1}\n aria-hidden=\"true\"\n onChange={(event) => void attachFile(event)}\n style={{ display: \"none\" }}\n />\n </div>\n </section>\n\n <label htmlFor=\"feedback-kit-comment\" style={{ display: \"block\", fontWeight: 700 }}>\n 코멘트 <span aria-hidden=\"true\">*</span>\n </label>\n <textarea\n id=\"feedback-kit-comment\"\n data-fk-focus-id={MODAL_FIELD_COMMENT}\n value={modal.comment}\n disabled={draftLocked}\n aria-required=\"true\"\n aria-invalid={modal.commentError ? \"true\" : undefined}\n aria-describedby={modal.commentError ? \"feedback-kit-comment-error\" : undefined}\n onChange={(event) => kit.widget.modal.setComment(event.currentTarget.value)}\n rows={5}\n placeholder=\"무엇이 불편했는지 알려주세요\"\n style={{ ...inputStyle, marginTop: 7, resize: \"vertical\" }}\n />\n <div style={{ minHeight: 21, marginTop: 3 }}>\n {modal.commentError ? (\n <span id=\"feedback-kit-comment-error\" role=\"alert\" style={{ color: TOKENS.danger }}>\n {modal.commentError}\n </span>\n ) : (\n <span style={{ color: TOKENS.muted }}>{modal.comment.length.toLocaleString()} / 4,000</span>\n )}\n </div>\n\n <label htmlFor=\"feedback-kit-priority\" style={{ display: \"block\", marginTop: 10, fontWeight: 700 }}>\n 우선순위\n </label>\n <select\n id=\"feedback-kit-priority\"\n data-fk-focus-id={MODAL_FIELD_PRIORITY}\n value={modal.priority}\n disabled={draftLocked}\n onChange={(event) =>\n kit.widget.modal.setPriority(event.currentTarget.value as FeedbackPriority)\n }\n style={{ ...inputStyle, marginTop: 7 }}\n >\n <option value=\"unset\">선택 안 함</option>\n <option value=\"normal\">보통</option>\n <option value=\"high\">높음</option>\n <option value=\"urgent\">긴급</option>\n </select>\n\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked=\"false\"\n disabled={draftLocked}\n data-fk-focus-id={MODAL_ACTION_PICK}\n onClick={() => kit.widget.startPicking()}\n style={{ ...baseButton, width: \"100%\", marginTop: 16 }}\n >\n 요소 지목\n </button>\n\n {modal.submitStatus !== \"idle\" || modal.pending > 0 ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n style={{\n marginTop: 14,\n padding: 11,\n borderRadius: 9,\n background: TOKENS.subtle,\n color: modal.submitStatus === \"pending\" ? TOKENS.warning : TOKENS.ink,\n }}\n >\n {modal.submitStatus === \"sending\" ? \"전송 중…\" : modal.submitMessage}\n {modal.pending > 0 ? ` · 대기 큐 ${modal.pending}건` : \"\"}\n {modal.showRetry ? (\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_RETRY}\n onClick={() => void kit.widget.retryReport()}\n style={{ ...baseButton, marginLeft: 10 }}\n >\n 다시 보내기\n </button>\n ) : null}\n </div>\n ) : null}\n\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, marginTop: 18 }}>\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_CANCEL}\n onClick={closeReport}\n style={baseButton}\n >\n 취소\n </button>\n <button\n type=\"submit\"\n data-fk-focus-id={MODAL_ACTION_SEND}\n disabled={!modal.canSubmit}\n style={{\n ...primaryButton,\n opacity: modal.canSubmit ? 1 : 0.5,\n cursor: modal.canSubmit ? \"pointer\" : \"not-allowed\",\n }}\n >\n {modal.submitStatus === \"sending\" ? \"전송 중…\" : \"보내기\"}\n </button>\n </div>\n </form>\n\n {modal.closeConfirmVisible ? (\n <div\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483604,\n display: \"grid\",\n placeItems: \"center\",\n padding: 20,\n background: \"rgba(23, 32, 42, 0.58)\",\n }}\n >\n <div\n ref={confirmRef}\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"feedback-kit-close-title\"\n tabIndex={-1}\n style={{\n width: \"min(360px, 100%)\",\n boxSizing: \"border-box\",\n borderRadius: 12,\n background: TOKENS.surface,\n padding: 20,\n boxShadow: TOKENS.shadow,\n }}\n >\n <h3 id=\"feedback-kit-close-title\" style={{ margin: \"0 0 6px\", fontSize: 17 }}>\n 작성 중인 내용을 닫을까요?\n </h3>\n <p style={{ margin: \"0 0 16px\", color: TOKENS.muted }}>\n 아직 보내지 않은 코멘트가 있습니다.\n </p>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8 }}>\n <button type=\"button\" onClick={() => kit.widget.cancelCloseReport()} style={baseButton}>\n 계속 쓰기\n </button>\n <button type=\"button\" onClick={() => kit.widget.confirmCloseReport()} style={primaryButton}>\n 닫기\n </button>\n </div>\n </div>\n </div>\n ) : null}\n </div>\n </div>\n ) : null}\n\n {screen === \"picking\" && pickingState.active ? (\n <div\n aria-label=\"요소 지목 오버레이\"\n style={{ position: \"fixed\", inset: 0, zIndex: 2147483599, pointerEvents: \"none\" }}\n >\n {hoverBox ? (\n <div\n aria-hidden=\"true\"\n style={{\n position: \"fixed\",\n left: hoverBox.x,\n top: hoverBox.y,\n width: hoverBox.width,\n height: hoverBox.height,\n boxSizing: \"border-box\",\n border: `2px solid ${TOKENS.accent}`,\n background: \"rgba(49, 93, 115, 0.12)\",\n }}\n />\n ) : null}\n\n <button\n type=\"button\"\n onClick={() => kit.widget.stopPicking()}\n style={{\n ...primaryButton,\n position: \"fixed\",\n top: 18,\n right: 18,\n pointerEvents: \"auto\",\n boxShadow: TOKENS.shadow,\n }}\n >\n 지목 종료\n </button>\n\n {pickingState.markers.map((marker, index) => (\n <div\n key={marker.id}\n role=\"status\"\n aria-label={`주석 ${index + 1}: ${marker.comment}`}\n style={{\n position: \"fixed\",\n left: `${marker.x * 100}%`,\n top: `${marker.y * 100}%`,\n transform: \"translate(-13px, -13px)\",\n display: \"grid\",\n placeItems: \"center\",\n minWidth: 26,\n height: 26,\n padding: \"0 7px\",\n border: `2px solid ${TOKENS.surface}`,\n borderRadius: 16,\n background: TOKENS.ink,\n color: TOKENS.surface,\n boxShadow: \"0 3px 10px rgba(23, 32, 42, 0.3)\",\n pointerEvents: \"none\",\n fontSize: 11,\n fontWeight: 700,\n }}\n >\n <MarkerStatus status={marker.status} />\n </div>\n ))}\n\n {popup ? (\n <form\n aria-label=\"요소 주석\"\n onSubmit={(event) => {\n event.preventDefault();\n void kit.picking.saveAnnotation();\n }}\n style={{\n position: \"fixed\",\n left: `min(${popup.point.x * 100}vw, calc(100vw - 336px))`,\n top: `min(${popup.point.y * 100}vh, calc(100vh - 230px))`,\n width: \"min(320px, calc(100vw - 32px))\",\n boxSizing: \"border-box\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 12,\n background: TOKENS.surface,\n padding: 14,\n boxShadow: TOKENS.shadow,\n pointerEvents: \"auto\",\n }}\n >\n <label htmlFor=\"feedback-kit-annotation\" style={{ display: \"block\", fontWeight: 700 }}>\n 이 요소에 주석 남기기\n </label>\n {popup.element?.text ? (\n <p style={{ margin: \"4px 0 9px\", color: TOKENS.muted }}>\n {popup.element.text}\n </p>\n ) : null}\n <textarea\n id=\"feedback-kit-annotation\"\n autoFocus\n value={popup.comment}\n onChange={(event) => kit.picking.setAnnotationComment(event.currentTarget.value)}\n rows={4}\n placeholder=\"의견을 입력해주세요\"\n style={{ ...inputStyle, resize: \"vertical\" }}\n />\n {popup.comment.length > 4000 ? (\n <div role=\"alert\" style={{ marginTop: 4, color: TOKENS.danger }}>\n 4,000자 이하로 입력해주세요\n </div>\n ) : null}\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, marginTop: 10 }}>\n <button type=\"button\" onClick={() => kit.picking.cancelAnnotation()} style={baseButton}>\n 취소\n </button>\n <button\n type=\"submit\"\n disabled={!popup.canSave}\n style={{\n ...primaryButton,\n opacity: popup.canSave ? 1 : 0.5,\n cursor: popup.canSave ? \"pointer\" : \"not-allowed\",\n }}\n >\n 저장\n </button>\n </div>\n </form>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n","// 웹 기본 저장소 — `localStorage` 를 FeedbackStorage 계약에 맞춘다.\n//\n// 왜 패키지가 제공하나: 이게 없으면 붙이는 앱마다 같은 20줄을 다시 쓴다.\n// 코어는 저장소 구현을 모르고(플랫폼 무관), 웹 패키지가 기본값을 준다.\n//\n// localStorage 는 동기지만 계약은 비동기라 Promise 로 감싼다. 사파리 프라이빗\n// 모드처럼 접근 자체가 던지는 환경이 있어서, 실패하면 **메모리로 조용히 물러난다**\n// — 제보를 잃는 것보다 세션 동안만 유지되는 편이 낫다.\n\nimport type { FeedbackStorage } from \"@solhun/feedback-kit-core\";\n\ninterface SyncStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nfunction memoryStorage(): SyncStorage {\n const map = new Map<string, string>();\n return {\n getItem: (key) => map.get(key) ?? null,\n setItem: (key, value) => void map.set(key, value),\n removeItem: (key) => void map.delete(key),\n };\n}\n\n/** 접근 가능한 localStorage 를 고른다. 못 쓰면 메모리. */\nfunction pickBacking(): SyncStorage {\n try {\n const ls = (globalThis as { localStorage?: SyncStorage }).localStorage;\n if (!ls) return memoryStorage();\n // 존재만으로는 부족하다 — 프라이빗 모드는 쓰기에서 던진다.\n const probe = \"__feedback_kit_probe__\";\n ls.setItem(probe, \"1\");\n ls.removeItem(probe);\n return ls;\n } catch {\n return memoryStorage();\n }\n}\n\n/**\n * 웹 기본 FeedbackStorage.\n *\n * 쓰기가 쿼터를 넘기면 그대로 던진다 — 큐가 그 신호를 받아 스크린샷을 떼어내고\n * 본문만 남기는 축소 경로를 타기 때문에, 여기서 삼키면 안 된다.\n */\nexport function createWebStorage(backing?: SyncStorage): FeedbackStorage {\n const store = backing ?? pickBacking();\n return {\n async get(key) {\n try {\n return store.getItem(key);\n } catch {\n return null;\n }\n },\n async set(key, value) {\n store.setItem(key, value);\n },\n async remove(key) {\n try {\n store.removeItem(key);\n } catch {\n /* 지우기 실패는 다음 쓰기가 덮으므로 무시한다. */\n }\n },\n };\n}\n","// 웹 기본 컨텍스트 제공자.\n//\n// 왜 패키지가 주나: `getUrl` 을 호스트가 깜빡하면 제보의 url 이 빈 문자열로 나가고,\n// 수집 서버는 그걸 400 으로 거절한다. 400 은 재시도 불가라 **제보가 조용히 버려진다.**\n// 실제로 e-fm 첫 배선에서 이 일이 났다 — 화면에는 \"보내지 못했습니다\"만 뜨고,\n// 원인(빈 url)은 네트워크를 까 봐야 알 수 있었다.\n//\n// 기본값을 패키지가 쥐고 있으면 붙이는 쪽이 잊을 수 없다. 필요하면 덮어쓰면 된다.\n\nimport type { ContextProviders, WebContextInput } from \"@solhun/feedback-kit-core\";\n\n/**\n * 브라우저 전역에서 뽑을 수 있는 기본 제공자들.\n *\n * SSR·비브라우저에서 불려도 던지지 않는다(`null` 을 돌려준다) — 위젯이 마운트되기 전\n * 서버 렌더 단계에서 실행될 수 있기 때문이다.\n */\nexport function webContextProviders(): ContextProviders {\n return {\n getUrl: () => {\n const loc = (globalThis as { location?: { href?: string } }).location;\n return typeof loc?.href === \"string\" && loc.href.length > 0 ? loc.href : null;\n },\n getWebContext: (): WebContextInput | null => {\n const g = globalThis as {\n location?: unknown;\n navigator?: { userAgent?: string };\n innerWidth?: number;\n innerHeight?: number;\n devicePixelRatio?: number;\n };\n if (!g.location) return null;\n return {\n userAgent: g.navigator?.userAgent ?? null,\n viewport: {\n width: typeof g.innerWidth === \"number\" ? g.innerWidth : null,\n height: typeof g.innerHeight === \"number\" ? g.innerHeight : null,\n devicePixelRatio: typeof g.devicePixelRatio === \"number\" ? g.devicePixelRatio : null,\n },\n };\n },\n };\n}\n"],"mappings":"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","names":["isOwnUi","import_feedback_kit_core","isOwnUi","html2canvas","import_feedback_kit_core","import_feedback_kit_core","modal","screen"]}
|