@solhun/feedback-kit-web 0.7.0 → 0.8.0
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 +232 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -3
- package/dist/index.d.ts +78 -3
- package/dist/index.js +227 -17
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ElementInfo, FeedbackPin, FeedbackScreenshot, FeedbackHint, FeedbackReport, SubmitOutcome, QueueStatus, ReportParts, ScreenshotCapture, ScreenshotReencode, WidgetControllerOpts, HintProvider, WidgetController, FeedbackStorage, ContextProviders } from '@solhun/feedback-kit-core';
|
|
1
|
+
import { ElementInfo, FeedbackPin, FeedbackScreenshot, FeedbackHint, FeedbackReport, SubmitOutcome, QueueStatus, ReportParts, ScreenshotCapture, ScreenshotReencode, RouteWatcher, WidgetControllerOpts, HintProvider, WidgetController, FeedbackStorage, ContextProviders, DiagnosticsInstallOpts, RouteTrailStore } from '@solhun/feedback-kit-core';
|
|
2
2
|
export { COMMENT_MAX_CHARS, COMMENT_REQUIRED_MESSAGE, COMMENT_TOO_LONG_MESSAGE, ElementInfo, ElementLike, FLOATING_BUTTON_ID, FeedbackAdapter, FeedbackConfig, FeedbackContext, FeedbackHint, FeedbackPin, FeedbackReport, FeedbackStorage, FeedbackUser, HintCatalog, HintElementRule, HintPlatform, HintProvider, HintRankInput, HintScope, MODAL_ACTION_HINT_TOGGLE, MODAL_ACTION_PICK, ModalSubmitStatus, ReportModalController, ReportModalState, ResolvedConfig, SCREENSHOT_FAILED_MESSAGE, SOURCE_ATTR, SUBMIT_DONE_MESSAGE, SUBMIT_PENDING_MESSAGE, ScreenshotStatus, SubmitResult, Visibility, VisibilityEnv, VisibilityFn, WidgetController, WidgetPosition, WidgetScreen, WidgetState, denormalizePin, normalizePin, parseSourceAttr, rankHints, resolveConfig, shouldShowWidget, sourceFromElement } from '@solhun/feedback-kit-core';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
|
|
@@ -163,6 +163,12 @@ interface ElementPickingOpts {
|
|
|
163
163
|
* `rankHints` 한 결과를 돌려준다. 없으면(힌트 기능이 꺼져 있으면) 팝업에 칩이 안 뜬다.
|
|
164
164
|
*/
|
|
165
165
|
rankHintsFor?: ((element: ElementInfo | null) => readonly FeedbackHint[]) | null;
|
|
166
|
+
/**
|
|
167
|
+
* 화면 이동 감시자. 기본은 웹 공용 감시자(`webRouteWatcher`) — **진단의 화면 이동 궤적과
|
|
168
|
+
* 같은 것**이다. 여기만 따로 구현하면 한쪽이 이동을 놓쳤을 때 원인을 가릴 수 없다.
|
|
169
|
+
* `null` 을 주면 감시하지 않는다(테스트에서 `syncPath()` 를 직접 부를 때).
|
|
170
|
+
*/
|
|
171
|
+
watchRoutes?: RouteWatcher | null;
|
|
166
172
|
}
|
|
167
173
|
declare class ElementPickingController {
|
|
168
174
|
private readonly queue;
|
|
@@ -186,10 +192,14 @@ declare class ElementPickingController {
|
|
|
186
192
|
*/
|
|
187
193
|
private paused;
|
|
188
194
|
private attached;
|
|
195
|
+
/** 같은 요소 위 mousemove 마다 React 경로·선택자를 다시 만들지 않기 위한 캐시. */
|
|
196
|
+
private hoveredTarget;
|
|
189
197
|
private hovered;
|
|
190
198
|
private popup;
|
|
191
199
|
private markers;
|
|
192
|
-
private
|
|
200
|
+
private readonly watchRoutes;
|
|
201
|
+
/** 경로 감시 해제. 감시 중이 아니면 null. */
|
|
202
|
+
private unwatchRoutes;
|
|
193
203
|
private lastPathname;
|
|
194
204
|
private saving;
|
|
195
205
|
/** 늦게 끝난 캡처가 다음 주석의 그림을 덮지 못하게 하는 세대 번호. */
|
|
@@ -203,6 +213,7 @@ declare class ElementPickingController {
|
|
|
203
213
|
private userToggledHints;
|
|
204
214
|
private readonly onClick;
|
|
205
215
|
private readonly onMouseOver;
|
|
216
|
+
private readonly onMouseMove;
|
|
206
217
|
constructor(opts: ElementPickingOpts);
|
|
207
218
|
getState(): PickingState;
|
|
208
219
|
get isActive(): boolean;
|
|
@@ -228,6 +239,17 @@ declare class ElementPickingController {
|
|
|
228
239
|
* @returns 되살아났으면 true.
|
|
229
240
|
*/
|
|
230
241
|
restore(): boolean;
|
|
242
|
+
/**
|
|
243
|
+
* 이 화면에 쌓인 마커 표시를 지운다.
|
|
244
|
+
*
|
|
245
|
+
* 지워지는 건 **화면 표시뿐**이다. 제보는 이미 큐를 거쳐 수집처로 갔으므로 없어지지 않는다.
|
|
246
|
+
* 마커는 `localStorage` 에 남아 새로고침에도 살아남는데(그게 원래 목적이다) 정작 치울
|
|
247
|
+
* 수단이 없어서, 한 번 보낸 [완료] 배지가 그 화면을 볼 때마다 계속 따라다녔다.
|
|
248
|
+
*
|
|
249
|
+
* 화면에 보이는 목록의 기준은 `lastPathname` 이다(`reconcileMarkerOutcomes` 와 같다).
|
|
250
|
+
* 이동 직후 아직 `syncPath` 가 안 돈 순간에도 "지금 눈에 보이는 것"이 지워져야 한다.
|
|
251
|
+
*/
|
|
252
|
+
clearMarkers(): void;
|
|
231
253
|
/** 경로가 바뀌었을 때 그 경로의 마커로 갈아 끼운다. */
|
|
232
254
|
syncPath(): readonly StoredMarker[];
|
|
233
255
|
dispose(): void;
|
|
@@ -375,5 +397,58 @@ declare function createWebStorage(backing?: SyncStorage): FeedbackStorage;
|
|
|
375
397
|
* 서버 렌더 단계에서 실행될 수 있기 때문이다.
|
|
376
398
|
*/
|
|
377
399
|
declare function webContextProviders(): ContextProviders;
|
|
400
|
+
/**
|
|
401
|
+
* 웹 진단 수집의 기본 배선.
|
|
402
|
+
*
|
|
403
|
+
* `sharedDiagnostics.install(webDiagnosticsOptions({ ... }))` 로 쓴다. 넘긴 값이 기본값을
|
|
404
|
+
* 덮으므로 감시자·저장소를 직접 갈아 끼울 수도 있다.
|
|
405
|
+
*
|
|
406
|
+
* 왜 패키지가 주나: 감시자를 호스트가 깜빡하면 **궤적만 조용히 빈다.** 네트워크·로그는
|
|
407
|
+
* 그대로 모이니 화면상 아무 문제가 없어 보이고, "이 제보엔 이동이 없었나 보다"로 읽힌다 —
|
|
408
|
+
* 빠진 것과 없는 것이 구분되지 않는 실패다.
|
|
409
|
+
*
|
|
410
|
+
* 진단을 끄면(`captureDiagnostics: false`) install 자체가 안 불리므로 궤적도 같이 꺼진다.
|
|
411
|
+
* 궤적만 따로 켜고 끄는 스위치는 만들지 않는다.
|
|
412
|
+
*/
|
|
413
|
+
declare function webDiagnosticsOptions(opts?: DiagnosticsInstallOpts): DiagnosticsInstallOpts;
|
|
414
|
+
|
|
415
|
+
/** 감시자가 들여다보는 전역. 테스트에서는 가짜를 넣는다. */
|
|
416
|
+
interface RouteWatchTarget {
|
|
417
|
+
location?: {
|
|
418
|
+
pathname?: string;
|
|
419
|
+
} | null;
|
|
420
|
+
history?: {
|
|
421
|
+
pushState?: (...args: unknown[]) => unknown;
|
|
422
|
+
replaceState?: (...args: unknown[]) => unknown;
|
|
423
|
+
} | null;
|
|
424
|
+
addEventListener?: (type: string, listener: () => void) => void;
|
|
425
|
+
removeEventListener?: (type: string, listener: () => void) => void;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* 경로 감시자 하나를 만든다.
|
|
429
|
+
*
|
|
430
|
+
* 돌려주는 것은 **구독 함수**다. 구독하면 현재 경로를 한 번 흘리고(마운트 시점의 화면이
|
|
431
|
+
* 궤적의 첫 줄이 된다), 이후 이동마다 새 경로를 흘린다. 해제 함수를 부르면 구독이 끊긴다.
|
|
432
|
+
*
|
|
433
|
+
* `history` 패치는 **첫 구독자가 생길 때 걸고 마지막이 나가면 푼다.** 구독자가 없는데 패치가
|
|
434
|
+
* 남아 있으면, 위젯을 떼어낸 뒤에도 호스트의 모든 화면 이동이 우리 코드를 거친다.
|
|
435
|
+
*/
|
|
436
|
+
declare function createRouteWatcher(target?: RouteWatchTarget | null): RouteWatcher;
|
|
437
|
+
/**
|
|
438
|
+
* 웹 기본 감시자. **모듈 스코프에 하나**라서 궤적과 지목이 같은 인스턴스를 물려 쓴다.
|
|
439
|
+
* 각자 `createRouteWatcher()` 를 부르면 `history` 가 두 겹으로 패치돼, 해제 순서가 엇갈릴 때
|
|
440
|
+
* 원본이 복구되지 않는다.
|
|
441
|
+
*/
|
|
442
|
+
declare const webRouteWatcher: RouteWatcher;
|
|
443
|
+
/**
|
|
444
|
+
* 궤적을 남길 탭 단위 저장소(`sessionStorage`).
|
|
445
|
+
*
|
|
446
|
+
* `localStorage` 가 아닌 이유: 궤적은 **이 탭에서 지금 지나온 길**이다. 탭을 나눠 쓰면
|
|
447
|
+
* 다른 탭의 이동이 섞여 재현 절차가 거짓이 된다.
|
|
448
|
+
*
|
|
449
|
+
* 프라이빗 모드·차단 설정에서는 접근 자체가 던진다. 그때는 `null` 을 돌려주고 궤적은
|
|
450
|
+
* 메모리로 떨어진다 — 저장을 못 한다고 제보를 막지 않는다.
|
|
451
|
+
*/
|
|
452
|
+
declare function webRouteStorage(): RouteTrailStore | null;
|
|
378
453
|
|
|
379
|
-
export { type AnnotationPopupState, ELEMENT_TEXT_MAX_CHARS, ElementPickingController, type ElementPickingOpts, FeedbackKit, type FeedbackKitProps, MARKER_KEY_PREFIX, MAX_MARKERS_PER_PATH, type MarkerStatus, MarkerStore, OWN_UI_ATTR, PICKING_MODE_KEY, type PickingListener, type PickingQueueLike, type PickingState, SELECTOR_MAX_DEPTH, type StorageLike, type StoredMarker, type WebWidget, type WebWidgetOpts, captureWebScreenshot, createWebStorage, createWebWidget, cssSelectorPath, describeElement, isOwnUi, reactComponentPath, reactComponentSummary, reencodeWebScreenshot, visibleText, webContextProviders };
|
|
454
|
+
export { type AnnotationPopupState, ELEMENT_TEXT_MAX_CHARS, ElementPickingController, type ElementPickingOpts, FeedbackKit, type FeedbackKitProps, MARKER_KEY_PREFIX, MAX_MARKERS_PER_PATH, type MarkerStatus, MarkerStore, OWN_UI_ATTR, PICKING_MODE_KEY, type PickingListener, type PickingQueueLike, type PickingState, type RouteWatchTarget, SELECTOR_MAX_DEPTH, type StorageLike, type StoredMarker, type WebWidget, type WebWidgetOpts, captureWebScreenshot, createRouteWatcher, createWebStorage, createWebWidget, cssSelectorPath, describeElement, isOwnUi, reactComponentPath, reactComponentSummary, reencodeWebScreenshot, visibleText, webContextProviders, webDiagnosticsOptions, webRouteStorage, webRouteWatcher };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ElementInfo, FeedbackPin, FeedbackScreenshot, FeedbackHint, FeedbackReport, SubmitOutcome, QueueStatus, ReportParts, ScreenshotCapture, ScreenshotReencode, WidgetControllerOpts, HintProvider, WidgetController, FeedbackStorage, ContextProviders } from '@solhun/feedback-kit-core';
|
|
1
|
+
import { ElementInfo, FeedbackPin, FeedbackScreenshot, FeedbackHint, FeedbackReport, SubmitOutcome, QueueStatus, ReportParts, ScreenshotCapture, ScreenshotReencode, RouteWatcher, WidgetControllerOpts, HintProvider, WidgetController, FeedbackStorage, ContextProviders, DiagnosticsInstallOpts, RouteTrailStore } from '@solhun/feedback-kit-core';
|
|
2
2
|
export { COMMENT_MAX_CHARS, COMMENT_REQUIRED_MESSAGE, COMMENT_TOO_LONG_MESSAGE, ElementInfo, ElementLike, FLOATING_BUTTON_ID, FeedbackAdapter, FeedbackConfig, FeedbackContext, FeedbackHint, FeedbackPin, FeedbackReport, FeedbackStorage, FeedbackUser, HintCatalog, HintElementRule, HintPlatform, HintProvider, HintRankInput, HintScope, MODAL_ACTION_HINT_TOGGLE, MODAL_ACTION_PICK, ModalSubmitStatus, ReportModalController, ReportModalState, ResolvedConfig, SCREENSHOT_FAILED_MESSAGE, SOURCE_ATTR, SUBMIT_DONE_MESSAGE, SUBMIT_PENDING_MESSAGE, ScreenshotStatus, SubmitResult, Visibility, VisibilityEnv, VisibilityFn, WidgetController, WidgetPosition, WidgetScreen, WidgetState, denormalizePin, normalizePin, parseSourceAttr, rankHints, resolveConfig, shouldShowWidget, sourceFromElement } from '@solhun/feedback-kit-core';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
|
|
@@ -163,6 +163,12 @@ interface ElementPickingOpts {
|
|
|
163
163
|
* `rankHints` 한 결과를 돌려준다. 없으면(힌트 기능이 꺼져 있으면) 팝업에 칩이 안 뜬다.
|
|
164
164
|
*/
|
|
165
165
|
rankHintsFor?: ((element: ElementInfo | null) => readonly FeedbackHint[]) | null;
|
|
166
|
+
/**
|
|
167
|
+
* 화면 이동 감시자. 기본은 웹 공용 감시자(`webRouteWatcher`) — **진단의 화면 이동 궤적과
|
|
168
|
+
* 같은 것**이다. 여기만 따로 구현하면 한쪽이 이동을 놓쳤을 때 원인을 가릴 수 없다.
|
|
169
|
+
* `null` 을 주면 감시하지 않는다(테스트에서 `syncPath()` 를 직접 부를 때).
|
|
170
|
+
*/
|
|
171
|
+
watchRoutes?: RouteWatcher | null;
|
|
166
172
|
}
|
|
167
173
|
declare class ElementPickingController {
|
|
168
174
|
private readonly queue;
|
|
@@ -186,10 +192,14 @@ declare class ElementPickingController {
|
|
|
186
192
|
*/
|
|
187
193
|
private paused;
|
|
188
194
|
private attached;
|
|
195
|
+
/** 같은 요소 위 mousemove 마다 React 경로·선택자를 다시 만들지 않기 위한 캐시. */
|
|
196
|
+
private hoveredTarget;
|
|
189
197
|
private hovered;
|
|
190
198
|
private popup;
|
|
191
199
|
private markers;
|
|
192
|
-
private
|
|
200
|
+
private readonly watchRoutes;
|
|
201
|
+
/** 경로 감시 해제. 감시 중이 아니면 null. */
|
|
202
|
+
private unwatchRoutes;
|
|
193
203
|
private lastPathname;
|
|
194
204
|
private saving;
|
|
195
205
|
/** 늦게 끝난 캡처가 다음 주석의 그림을 덮지 못하게 하는 세대 번호. */
|
|
@@ -203,6 +213,7 @@ declare class ElementPickingController {
|
|
|
203
213
|
private userToggledHints;
|
|
204
214
|
private readonly onClick;
|
|
205
215
|
private readonly onMouseOver;
|
|
216
|
+
private readonly onMouseMove;
|
|
206
217
|
constructor(opts: ElementPickingOpts);
|
|
207
218
|
getState(): PickingState;
|
|
208
219
|
get isActive(): boolean;
|
|
@@ -228,6 +239,17 @@ declare class ElementPickingController {
|
|
|
228
239
|
* @returns 되살아났으면 true.
|
|
229
240
|
*/
|
|
230
241
|
restore(): boolean;
|
|
242
|
+
/**
|
|
243
|
+
* 이 화면에 쌓인 마커 표시를 지운다.
|
|
244
|
+
*
|
|
245
|
+
* 지워지는 건 **화면 표시뿐**이다. 제보는 이미 큐를 거쳐 수집처로 갔으므로 없어지지 않는다.
|
|
246
|
+
* 마커는 `localStorage` 에 남아 새로고침에도 살아남는데(그게 원래 목적이다) 정작 치울
|
|
247
|
+
* 수단이 없어서, 한 번 보낸 [완료] 배지가 그 화면을 볼 때마다 계속 따라다녔다.
|
|
248
|
+
*
|
|
249
|
+
* 화면에 보이는 목록의 기준은 `lastPathname` 이다(`reconcileMarkerOutcomes` 와 같다).
|
|
250
|
+
* 이동 직후 아직 `syncPath` 가 안 돈 순간에도 "지금 눈에 보이는 것"이 지워져야 한다.
|
|
251
|
+
*/
|
|
252
|
+
clearMarkers(): void;
|
|
231
253
|
/** 경로가 바뀌었을 때 그 경로의 마커로 갈아 끼운다. */
|
|
232
254
|
syncPath(): readonly StoredMarker[];
|
|
233
255
|
dispose(): void;
|
|
@@ -375,5 +397,58 @@ declare function createWebStorage(backing?: SyncStorage): FeedbackStorage;
|
|
|
375
397
|
* 서버 렌더 단계에서 실행될 수 있기 때문이다.
|
|
376
398
|
*/
|
|
377
399
|
declare function webContextProviders(): ContextProviders;
|
|
400
|
+
/**
|
|
401
|
+
* 웹 진단 수집의 기본 배선.
|
|
402
|
+
*
|
|
403
|
+
* `sharedDiagnostics.install(webDiagnosticsOptions({ ... }))` 로 쓴다. 넘긴 값이 기본값을
|
|
404
|
+
* 덮으므로 감시자·저장소를 직접 갈아 끼울 수도 있다.
|
|
405
|
+
*
|
|
406
|
+
* 왜 패키지가 주나: 감시자를 호스트가 깜빡하면 **궤적만 조용히 빈다.** 네트워크·로그는
|
|
407
|
+
* 그대로 모이니 화면상 아무 문제가 없어 보이고, "이 제보엔 이동이 없었나 보다"로 읽힌다 —
|
|
408
|
+
* 빠진 것과 없는 것이 구분되지 않는 실패다.
|
|
409
|
+
*
|
|
410
|
+
* 진단을 끄면(`captureDiagnostics: false`) install 자체가 안 불리므로 궤적도 같이 꺼진다.
|
|
411
|
+
* 궤적만 따로 켜고 끄는 스위치는 만들지 않는다.
|
|
412
|
+
*/
|
|
413
|
+
declare function webDiagnosticsOptions(opts?: DiagnosticsInstallOpts): DiagnosticsInstallOpts;
|
|
414
|
+
|
|
415
|
+
/** 감시자가 들여다보는 전역. 테스트에서는 가짜를 넣는다. */
|
|
416
|
+
interface RouteWatchTarget {
|
|
417
|
+
location?: {
|
|
418
|
+
pathname?: string;
|
|
419
|
+
} | null;
|
|
420
|
+
history?: {
|
|
421
|
+
pushState?: (...args: unknown[]) => unknown;
|
|
422
|
+
replaceState?: (...args: unknown[]) => unknown;
|
|
423
|
+
} | null;
|
|
424
|
+
addEventListener?: (type: string, listener: () => void) => void;
|
|
425
|
+
removeEventListener?: (type: string, listener: () => void) => void;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* 경로 감시자 하나를 만든다.
|
|
429
|
+
*
|
|
430
|
+
* 돌려주는 것은 **구독 함수**다. 구독하면 현재 경로를 한 번 흘리고(마운트 시점의 화면이
|
|
431
|
+
* 궤적의 첫 줄이 된다), 이후 이동마다 새 경로를 흘린다. 해제 함수를 부르면 구독이 끊긴다.
|
|
432
|
+
*
|
|
433
|
+
* `history` 패치는 **첫 구독자가 생길 때 걸고 마지막이 나가면 푼다.** 구독자가 없는데 패치가
|
|
434
|
+
* 남아 있으면, 위젯을 떼어낸 뒤에도 호스트의 모든 화면 이동이 우리 코드를 거친다.
|
|
435
|
+
*/
|
|
436
|
+
declare function createRouteWatcher(target?: RouteWatchTarget | null): RouteWatcher;
|
|
437
|
+
/**
|
|
438
|
+
* 웹 기본 감시자. **모듈 스코프에 하나**라서 궤적과 지목이 같은 인스턴스를 물려 쓴다.
|
|
439
|
+
* 각자 `createRouteWatcher()` 를 부르면 `history` 가 두 겹으로 패치돼, 해제 순서가 엇갈릴 때
|
|
440
|
+
* 원본이 복구되지 않는다.
|
|
441
|
+
*/
|
|
442
|
+
declare const webRouteWatcher: RouteWatcher;
|
|
443
|
+
/**
|
|
444
|
+
* 궤적을 남길 탭 단위 저장소(`sessionStorage`).
|
|
445
|
+
*
|
|
446
|
+
* `localStorage` 가 아닌 이유: 궤적은 **이 탭에서 지금 지나온 길**이다. 탭을 나눠 쓰면
|
|
447
|
+
* 다른 탭의 이동이 섞여 재현 절차가 거짓이 된다.
|
|
448
|
+
*
|
|
449
|
+
* 프라이빗 모드·차단 설정에서는 접근 자체가 던진다. 그때는 `null` 을 돌려주고 궤적은
|
|
450
|
+
* 메모리로 떨어진다 — 저장을 못 한다고 제보를 막지 않는다.
|
|
451
|
+
*/
|
|
452
|
+
declare function webRouteStorage(): RouteTrailStore | null;
|
|
378
453
|
|
|
379
|
-
export { type AnnotationPopupState, ELEMENT_TEXT_MAX_CHARS, ElementPickingController, type ElementPickingOpts, FeedbackKit, type FeedbackKitProps, MARKER_KEY_PREFIX, MAX_MARKERS_PER_PATH, type MarkerStatus, MarkerStore, OWN_UI_ATTR, PICKING_MODE_KEY, type PickingListener, type PickingQueueLike, type PickingState, SELECTOR_MAX_DEPTH, type StorageLike, type StoredMarker, type WebWidget, type WebWidgetOpts, captureWebScreenshot, createWebStorage, createWebWidget, cssSelectorPath, describeElement, isOwnUi, reactComponentPath, reactComponentSummary, reencodeWebScreenshot, visibleText, webContextProviders };
|
|
454
|
+
export { type AnnotationPopupState, ELEMENT_TEXT_MAX_CHARS, ElementPickingController, type ElementPickingOpts, FeedbackKit, type FeedbackKitProps, MARKER_KEY_PREFIX, MAX_MARKERS_PER_PATH, type MarkerStatus, MarkerStore, OWN_UI_ATTR, PICKING_MODE_KEY, type PickingListener, type PickingQueueLike, type PickingState, type RouteWatchTarget, SELECTOR_MAX_DEPTH, type StorageLike, type StoredMarker, type WebWidget, type WebWidgetOpts, captureWebScreenshot, createRouteWatcher, createWebStorage, createWebWidget, cssSelectorPath, describeElement, isOwnUi, reactComponentPath, reactComponentSummary, reencodeWebScreenshot, visibleText, webContextProviders, webDiagnosticsOptions, webRouteStorage, webRouteWatcher };
|
package/dist/index.js
CHANGED
|
@@ -103,6 +103,7 @@ function reactComponentSummary(node) {
|
|
|
103
103
|
// src/element-info.ts
|
|
104
104
|
var ELEMENT_TEXT_MAX_CHARS = 200;
|
|
105
105
|
var SELECTOR_MAX_DEPTH = 8;
|
|
106
|
+
var PICK_TARGET_SNAP_PX = 24;
|
|
106
107
|
var KEPT_ATTRS = [
|
|
107
108
|
"id",
|
|
108
109
|
"class",
|
|
@@ -175,6 +176,55 @@ function visibleText(el) {
|
|
|
175
176
|
if (collapsed === "") return null;
|
|
176
177
|
return collapsed.length > ELEMENT_TEXT_MAX_CHARS ? collapsed.slice(0, ELEMENT_TEXT_MAX_CHARS) : collapsed;
|
|
177
178
|
}
|
|
179
|
+
function elementRect(element) {
|
|
180
|
+
try {
|
|
181
|
+
const rect = element.getBoundingClientRect();
|
|
182
|
+
const left = rect.left;
|
|
183
|
+
const top = rect.top;
|
|
184
|
+
const width = rect.width;
|
|
185
|
+
const height = rect.height;
|
|
186
|
+
if (![left, top, width, height].every(Number.isFinite) || width <= 0 || height <= 0) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
element,
|
|
191
|
+
left,
|
|
192
|
+
top,
|
|
193
|
+
right: left + width,
|
|
194
|
+
bottom: top + height,
|
|
195
|
+
width,
|
|
196
|
+
height
|
|
197
|
+
};
|
|
198
|
+
} catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function distanceFromPoint(rect, point) {
|
|
203
|
+
const dx = Math.max(rect.left - point.x, 0, point.x - rect.right);
|
|
204
|
+
const dy = Math.max(rect.top - point.y, 0, point.y - rect.bottom);
|
|
205
|
+
return Math.hypot(dx, dy);
|
|
206
|
+
}
|
|
207
|
+
function resolvePickTarget(target, point) {
|
|
208
|
+
if (!target || typeof target.tagName !== "string") return null;
|
|
209
|
+
if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) return target;
|
|
210
|
+
let current = target;
|
|
211
|
+
while (true) {
|
|
212
|
+
const currentRect = elementRect(current);
|
|
213
|
+
const children = Array.from(current.children).map(elementRect).filter((rect) => rect !== null).map((rect) => ({ rect, distance: distanceFromPoint(rect, point) }));
|
|
214
|
+
if (children.length === 0) return current;
|
|
215
|
+
const containing = children.filter(({ distance }) => distance === 0).sort((a, b) => a.rect.width * a.rect.height - b.rect.width * b.rect.height);
|
|
216
|
+
if (containing[0]) {
|
|
217
|
+
current = containing[0].rect.element;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const nearest = children.sort((a, b) => a.distance - b.distance)[0];
|
|
221
|
+
const isMorePrecise = currentRect ? nearest.rect.width * nearest.rect.height < currentRect.width * currentRect.height : true;
|
|
222
|
+
if (nearest.distance <= PICK_TARGET_SNAP_PX && isMorePrecise) {
|
|
223
|
+
return nearest.rect.element;
|
|
224
|
+
}
|
|
225
|
+
return current;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
178
228
|
function describeElement(el) {
|
|
179
229
|
if (!el || typeof el.tagName !== "string") return null;
|
|
180
230
|
const attributes = {};
|
|
@@ -299,6 +349,84 @@ import {
|
|
|
299
349
|
captureWithinLimit,
|
|
300
350
|
normalizePin
|
|
301
351
|
} from "@solhun/feedback-kit-core";
|
|
352
|
+
|
|
353
|
+
// src/route-watcher.ts
|
|
354
|
+
function defaultTarget() {
|
|
355
|
+
const g = globalThis;
|
|
356
|
+
return g.location ? g : null;
|
|
357
|
+
}
|
|
358
|
+
function createRouteWatcher(target = defaultTarget()) {
|
|
359
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
360
|
+
let detach = null;
|
|
361
|
+
const currentPath = () => {
|
|
362
|
+
const p = target?.location?.pathname;
|
|
363
|
+
return typeof p === "string" && p.length > 0 ? p : "/";
|
|
364
|
+
};
|
|
365
|
+
const emit = () => {
|
|
366
|
+
const path = currentPath();
|
|
367
|
+
for (const listener of [...listeners]) {
|
|
368
|
+
try {
|
|
369
|
+
listener(path);
|
|
370
|
+
} catch {
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
const attach = () => {
|
|
375
|
+
if (detach !== null || !target) return;
|
|
376
|
+
const onPop = () => emit();
|
|
377
|
+
target.addEventListener?.("popstate", onPop);
|
|
378
|
+
const history = target.history;
|
|
379
|
+
const originalPush = typeof history?.pushState === "function" ? history.pushState : null;
|
|
380
|
+
const originalReplace = typeof history?.replaceState === "function" ? history.replaceState : null;
|
|
381
|
+
if (history && originalPush) {
|
|
382
|
+
history.pushState = function patched(...args) {
|
|
383
|
+
const out = originalPush.apply(this, args);
|
|
384
|
+
emit();
|
|
385
|
+
return out;
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
if (history && originalReplace) {
|
|
389
|
+
history.replaceState = function patched(...args) {
|
|
390
|
+
const out = originalReplace.apply(this, args);
|
|
391
|
+
emit();
|
|
392
|
+
return out;
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
detach = () => {
|
|
396
|
+
target.removeEventListener?.("popstate", onPop);
|
|
397
|
+
if (history && originalPush) history.pushState = originalPush;
|
|
398
|
+
if (history && originalReplace) history.replaceState = originalReplace;
|
|
399
|
+
};
|
|
400
|
+
};
|
|
401
|
+
return (onPath) => {
|
|
402
|
+
listeners.add(onPath);
|
|
403
|
+
if (listeners.size === 1) attach();
|
|
404
|
+
try {
|
|
405
|
+
onPath(currentPath());
|
|
406
|
+
} catch {
|
|
407
|
+
}
|
|
408
|
+
return () => {
|
|
409
|
+
listeners.delete(onPath);
|
|
410
|
+
if (listeners.size === 0 && detach) {
|
|
411
|
+
detach();
|
|
412
|
+
detach = null;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
var webRouteWatcher = createRouteWatcher();
|
|
418
|
+
function webRouteStorage() {
|
|
419
|
+
try {
|
|
420
|
+
const s = globalThis.sessionStorage;
|
|
421
|
+
if (!s || typeof s.getItem !== "function" || typeof s.setItem !== "function") return null;
|
|
422
|
+
s.getItem("feedback-kit:probe");
|
|
423
|
+
return s;
|
|
424
|
+
} catch {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/picking.ts
|
|
302
430
|
var OWN_UI_ATTR = "data-feedback-kit";
|
|
303
431
|
function defaultDocument() {
|
|
304
432
|
return globalThis.document ?? null;
|
|
@@ -327,10 +455,13 @@ var ElementPickingController = class {
|
|
|
327
455
|
*/
|
|
328
456
|
this.paused = false;
|
|
329
457
|
this.attached = false;
|
|
458
|
+
/** 같은 요소 위 mousemove 마다 React 경로·선택자를 다시 만들지 않기 위한 캐시. */
|
|
459
|
+
this.hoveredTarget = null;
|
|
330
460
|
this.hovered = null;
|
|
331
461
|
this.popup = null;
|
|
332
462
|
this.markers = [];
|
|
333
|
-
|
|
463
|
+
/** 경로 감시 해제. 감시 중이 아니면 null. */
|
|
464
|
+
this.unwatchRoutes = null;
|
|
334
465
|
this.saving = false;
|
|
335
466
|
/** 늦게 끝난 캡처가 다음 주석의 그림을 덮지 못하게 하는 세대 번호. */
|
|
336
467
|
this.shotGeneration = 0;
|
|
@@ -343,6 +474,7 @@ var ElementPickingController = class {
|
|
|
343
474
|
this.userToggledHints = false;
|
|
344
475
|
this.onClick = (event) => this.handleClick(event);
|
|
345
476
|
this.onMouseOver = (event) => this.handleMouseOver(event);
|
|
477
|
+
this.onMouseMove = (event) => this.handleMouseOver(event);
|
|
346
478
|
this.queue = opts.queue;
|
|
347
479
|
this.createReport = opts.createReport;
|
|
348
480
|
this.store = opts.store ?? new MarkerStore();
|
|
@@ -354,6 +486,7 @@ var ElementPickingController = class {
|
|
|
354
486
|
this.reencode = opts.reencode ?? null;
|
|
355
487
|
this.screenshotLimitBytes = opts.screenshotLimitBytes;
|
|
356
488
|
this.rankHintsFor = opts.rankHintsFor ?? null;
|
|
489
|
+
this.watchRoutes = opts.watchRoutes === void 0 ? webRouteWatcher : opts.watchRoutes;
|
|
357
490
|
this.lastPathname = this.getPathname();
|
|
358
491
|
this.markers = this.store.list(this.lastPathname);
|
|
359
492
|
this.unsubscribeQueue = this.queue.subscribe?.(() => {
|
|
@@ -402,6 +535,7 @@ var ElementPickingController = class {
|
|
|
402
535
|
this.store.setPickingActive(false);
|
|
403
536
|
this.detach();
|
|
404
537
|
this.stopPathWatch();
|
|
538
|
+
this.hoveredTarget = null;
|
|
405
539
|
this.hovered = null;
|
|
406
540
|
this.popup = null;
|
|
407
541
|
this.emit();
|
|
@@ -417,6 +551,7 @@ var ElementPickingController = class {
|
|
|
417
551
|
if (!this.active || this.paused) return;
|
|
418
552
|
this.paused = true;
|
|
419
553
|
this.detach();
|
|
554
|
+
this.hoveredTarget = null;
|
|
420
555
|
this.hovered = null;
|
|
421
556
|
this.emit();
|
|
422
557
|
}
|
|
@@ -444,6 +579,22 @@ var ElementPickingController = class {
|
|
|
444
579
|
this.start();
|
|
445
580
|
return true;
|
|
446
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* 이 화면에 쌓인 마커 표시를 지운다.
|
|
584
|
+
*
|
|
585
|
+
* 지워지는 건 **화면 표시뿐**이다. 제보는 이미 큐를 거쳐 수집처로 갔으므로 없어지지 않는다.
|
|
586
|
+
* 마커는 `localStorage` 에 남아 새로고침에도 살아남는데(그게 원래 목적이다) 정작 치울
|
|
587
|
+
* 수단이 없어서, 한 번 보낸 [완료] 배지가 그 화면을 볼 때마다 계속 따라다녔다.
|
|
588
|
+
*
|
|
589
|
+
* 화면에 보이는 목록의 기준은 `lastPathname` 이다(`reconcileMarkerOutcomes` 와 같다).
|
|
590
|
+
* 이동 직후 아직 `syncPath` 가 안 돈 순간에도 "지금 눈에 보이는 것"이 지워져야 한다.
|
|
591
|
+
*/
|
|
592
|
+
clearMarkers() {
|
|
593
|
+
if (this.markers.length === 0) return;
|
|
594
|
+
this.store.clear(this.lastPathname);
|
|
595
|
+
this.markers = [];
|
|
596
|
+
this.emit();
|
|
597
|
+
}
|
|
447
598
|
/** 경로가 바뀌었을 때 그 경로의 마커로 갈아 끼운다. */
|
|
448
599
|
syncPath() {
|
|
449
600
|
if (this.paused) this.resume();
|
|
@@ -451,6 +602,7 @@ var ElementPickingController = class {
|
|
|
451
602
|
this.markers = this.store.list(this.lastPathname);
|
|
452
603
|
this.reconcileMarkerOutcomes();
|
|
453
604
|
this.popup = null;
|
|
605
|
+
this.hoveredTarget = null;
|
|
454
606
|
this.hovered = null;
|
|
455
607
|
this.emit();
|
|
456
608
|
return this.markers;
|
|
@@ -466,17 +618,26 @@ var ElementPickingController = class {
|
|
|
466
618
|
if (this.attached || !this.doc) return;
|
|
467
619
|
this.doc.addEventListener("click", this.onClick, true);
|
|
468
620
|
this.doc.addEventListener("mouseover", this.onMouseOver, true);
|
|
621
|
+
this.doc.addEventListener("mousemove", this.onMouseMove, true);
|
|
469
622
|
this.attached = true;
|
|
470
623
|
}
|
|
471
624
|
detach() {
|
|
472
625
|
if (!this.attached || !this.doc) return;
|
|
473
626
|
this.doc.removeEventListener("click", this.onClick, true);
|
|
474
627
|
this.doc.removeEventListener("mouseover", this.onMouseOver, true);
|
|
628
|
+
this.doc.removeEventListener("mousemove", this.onMouseMove, true);
|
|
475
629
|
this.attached = false;
|
|
476
630
|
}
|
|
477
631
|
handleMouseOver(event) {
|
|
478
632
|
if (!this.active || this.paused || isOwnUi(event.target)) return;
|
|
479
|
-
|
|
633
|
+
const mouse = event;
|
|
634
|
+
const target = resolvePickTarget(event.target, {
|
|
635
|
+
x: mouse.clientX ?? 0,
|
|
636
|
+
y: mouse.clientY ?? 0
|
|
637
|
+
});
|
|
638
|
+
if (target === this.hoveredTarget) return;
|
|
639
|
+
this.hoveredTarget = target;
|
|
640
|
+
this.hovered = describeElement(target);
|
|
480
641
|
this.emit();
|
|
481
642
|
}
|
|
482
643
|
handleClick(event) {
|
|
@@ -497,11 +658,12 @@ var ElementPickingController = class {
|
|
|
497
658
|
/** 클릭 지점 기준으로 주석 팝업을 연다. 좌표는 해상도 무관한 상대값으로 접어 둔다. */
|
|
498
659
|
openAnnotation(element, clientPoint) {
|
|
499
660
|
const point = normalizePin(clientPoint, this.getViewport());
|
|
661
|
+
const target = resolvePickTarget(element, clientPoint);
|
|
500
662
|
if (this.onPick) {
|
|
501
|
-
this.onPick(describeElement(
|
|
663
|
+
this.onPick(describeElement(target), point);
|
|
502
664
|
return;
|
|
503
665
|
}
|
|
504
|
-
const info = describeElement(
|
|
666
|
+
const info = describeElement(target);
|
|
505
667
|
this.userToggledHints = false;
|
|
506
668
|
const hints = this.rankHintsFor ? this.rankHintsFor(info) : [];
|
|
507
669
|
this.popup = {
|
|
@@ -716,17 +878,17 @@ ${hint.draft}`;
|
|
|
716
878
|
return outcome;
|
|
717
879
|
}
|
|
718
880
|
startPathWatch() {
|
|
719
|
-
if (this.
|
|
881
|
+
if (this.unwatchRoutes !== null || !this.watchRoutes) return;
|
|
720
882
|
this.lastPathname = this.getPathname();
|
|
721
|
-
this.
|
|
883
|
+
this.unwatchRoutes = this.watchRoutes(() => {
|
|
722
884
|
const pathname = this.getPathname();
|
|
723
885
|
if (pathname !== this.lastPathname) this.syncPath();
|
|
724
|
-
}
|
|
886
|
+
});
|
|
725
887
|
}
|
|
726
888
|
stopPathWatch() {
|
|
727
|
-
if (this.
|
|
728
|
-
|
|
729
|
-
this.
|
|
889
|
+
if (this.unwatchRoutes === null) return;
|
|
890
|
+
this.unwatchRoutes();
|
|
891
|
+
this.unwatchRoutes = null;
|
|
730
892
|
}
|
|
731
893
|
/** 전역 pending 수가 아니라 마커와 같은 clientSubmissionId의 확정 성공만 완료로 바꾼다. */
|
|
732
894
|
reconcileMarkerOutcomes() {
|
|
@@ -1012,7 +1174,7 @@ import {
|
|
|
1012
1174
|
} from "react";
|
|
1013
1175
|
|
|
1014
1176
|
// src/version.ts
|
|
1015
|
-
var VERSION = true ? "0.
|
|
1177
|
+
var VERSION = true ? "0.8.0" : "dev";
|
|
1016
1178
|
|
|
1017
1179
|
// src/feedback-kit.tsx
|
|
1018
1180
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -1278,8 +1440,10 @@ function PickToggle({
|
|
|
1278
1440
|
}
|
|
1279
1441
|
function PickBar({
|
|
1280
1442
|
paused,
|
|
1443
|
+
markerCount,
|
|
1281
1444
|
onToggle,
|
|
1282
|
-
onTogglePause
|
|
1445
|
+
onTogglePause,
|
|
1446
|
+
onClearMarkers
|
|
1283
1447
|
}) {
|
|
1284
1448
|
return /* @__PURE__ */ jsxs(
|
|
1285
1449
|
"div",
|
|
@@ -1287,10 +1451,20 @@ function PickBar({
|
|
|
1287
1451
|
style: {
|
|
1288
1452
|
position: "fixed",
|
|
1289
1453
|
top: 16,
|
|
1290
|
-
left:
|
|
1291
|
-
transform
|
|
1454
|
+
// `left:50% + translateX(-50%)` 로 가운데를 잡으면 **쓸 수 있는 폭이 화면의 절반**이
|
|
1455
|
+
// 된다(transform 은 레이아웃 계산에 안 들어간다). 버튼이 셋이 되면서 그 절반에
|
|
1456
|
+
// 안 맞아 좁은 화면에서 버튼이 한 줄에 하나씩 세로로 쌓였다(390px 에서 3줄).
|
|
1457
|
+
// 양끝을 물리고 auto 마진으로 가운데를 잡으면 화면 전체 폭을 쓴다 — 같은 화면이 2줄.
|
|
1458
|
+
left: 0,
|
|
1459
|
+
right: 0,
|
|
1460
|
+
marginInline: "auto",
|
|
1461
|
+
width: "fit-content",
|
|
1462
|
+
maxWidth: "calc(100% - 32px)",
|
|
1292
1463
|
display: "flex",
|
|
1293
1464
|
alignItems: "stretch",
|
|
1465
|
+
justifyContent: "center",
|
|
1466
|
+
// 셋이 한 줄에 안 들어가는 폭에서는 줄을 바꾼다 — 안 그러면 화면 밖으로 나간다.
|
|
1467
|
+
flexWrap: "wrap",
|
|
1294
1468
|
gap: 8,
|
|
1295
1469
|
// 오버레이 자체는 클릭을 통과시킨다(pointerEvents:none) — 이 막대만 되살린다.
|
|
1296
1470
|
pointerEvents: "auto",
|
|
@@ -1323,7 +1497,30 @@ function PickBar({
|
|
|
1323
1497
|
paused ? "\uC9C0\uBAA9 \uC7AC\uAC1C" : "\uC7A0\uC2DC \uBA48\uCDA4"
|
|
1324
1498
|
]
|
|
1325
1499
|
}
|
|
1326
|
-
)
|
|
1500
|
+
),
|
|
1501
|
+
markerCount > 0 ? /* @__PURE__ */ jsxs(
|
|
1502
|
+
"button",
|
|
1503
|
+
{
|
|
1504
|
+
type: "button",
|
|
1505
|
+
"data-fk-pick-clear": markerCount,
|
|
1506
|
+
title: "\uD654\uBA74\uC758 \uD45C\uC2DC\uB9CC \uC9C0\uC6C1\uB2C8\uB2E4. \uBCF4\uB0B8 \uC81C\uBCF4\uB294 \uADF8\uB300\uB85C \uB0A8\uC2B5\uB2C8\uB2E4.",
|
|
1507
|
+
"aria-label": `\uC774 \uD654\uBA74\uC758 \uC8FC\uC11D \uD45C\uC2DC ${markerCount}\uAC1C \uC9C0\uC6B0\uAE30 (\uBCF4\uB0B8 \uC81C\uBCF4\uB294 \uADF8\uB300\uB85C \uB0A8\uC2B5\uB2C8\uB2E4)`,
|
|
1508
|
+
onClick: onClearMarkers,
|
|
1509
|
+
style: {
|
|
1510
|
+
...baseButton,
|
|
1511
|
+
display: "inline-flex",
|
|
1512
|
+
alignItems: "center",
|
|
1513
|
+
gap: 6,
|
|
1514
|
+
whiteSpace: "nowrap",
|
|
1515
|
+
color: TOKENS.muted
|
|
1516
|
+
},
|
|
1517
|
+
children: [
|
|
1518
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2715" }),
|
|
1519
|
+
"\uD45C\uC2DC \uC9C0\uC6B0\uAE30 ",
|
|
1520
|
+
markerCount
|
|
1521
|
+
]
|
|
1522
|
+
}
|
|
1523
|
+
) : null
|
|
1327
1524
|
]
|
|
1328
1525
|
}
|
|
1329
1526
|
);
|
|
@@ -1988,8 +2185,10 @@ function FeedbackKit(props) {
|
|
|
1988
2185
|
PickBar,
|
|
1989
2186
|
{
|
|
1990
2187
|
paused: pickingState.paused,
|
|
2188
|
+
markerCount: pickingState.markers.length,
|
|
1991
2189
|
onToggle: togglePicking,
|
|
1992
|
-
onTogglePause: () => activeKit.picking.togglePause()
|
|
2190
|
+
onTogglePause: () => activeKit.picking.togglePause(),
|
|
2191
|
+
onClearMarkers: () => activeKit.picking.clearMarkers()
|
|
1993
2192
|
}
|
|
1994
2193
|
),
|
|
1995
2194
|
pickingState.markers.map((marker, index) => /* @__PURE__ */ jsx(
|
|
@@ -2215,6 +2414,13 @@ function webContextProviders() {
|
|
|
2215
2414
|
}
|
|
2216
2415
|
};
|
|
2217
2416
|
}
|
|
2417
|
+
function webDiagnosticsOptions(opts = {}) {
|
|
2418
|
+
return {
|
|
2419
|
+
watchRoutes: webRouteWatcher,
|
|
2420
|
+
routeStorage: webRouteStorage(),
|
|
2421
|
+
...opts
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2218
2424
|
export {
|
|
2219
2425
|
COMMENT_MAX_CHARS3 as COMMENT_MAX_CHARS,
|
|
2220
2426
|
COMMENT_REQUIRED_MESSAGE,
|
|
@@ -2239,6 +2445,7 @@ export {
|
|
|
2239
2445
|
SUBMIT_PENDING_MESSAGE,
|
|
2240
2446
|
WidgetController2 as WidgetController,
|
|
2241
2447
|
captureWebScreenshot,
|
|
2448
|
+
createRouteWatcher,
|
|
2242
2449
|
createWebStorage,
|
|
2243
2450
|
createWebWidget,
|
|
2244
2451
|
cssSelectorPath,
|
|
@@ -2255,6 +2462,9 @@ export {
|
|
|
2255
2462
|
shouldShowWidget,
|
|
2256
2463
|
sourceFromElement,
|
|
2257
2464
|
visibleText,
|
|
2258
|
-
webContextProviders
|
|
2465
|
+
webContextProviders,
|
|
2466
|
+
webDiagnosticsOptions,
|
|
2467
|
+
webRouteStorage,
|
|
2468
|
+
webRouteWatcher
|
|
2259
2469
|
};
|
|
2260
2470
|
//# sourceMappingURL=index.js.map
|