@solhun/feedback-kit-web 0.6.1 → 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 +398 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -5
- package/dist/index.d.ts +111 -5
- package/dist/index.js +393 -72
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -53,6 +53,7 @@ __export(index_exports, {
|
|
|
53
53
|
SUBMIT_PENDING_MESSAGE: () => import_feedback_kit_core6.SUBMIT_PENDING_MESSAGE,
|
|
54
54
|
WidgetController: () => import_feedback_kit_core6.WidgetController,
|
|
55
55
|
captureWebScreenshot: () => captureWebScreenshot,
|
|
56
|
+
createRouteWatcher: () => createRouteWatcher,
|
|
56
57
|
createWebStorage: () => createWebStorage,
|
|
57
58
|
createWebWidget: () => createWebWidget,
|
|
58
59
|
cssSelectorPath: () => cssSelectorPath,
|
|
@@ -69,7 +70,10 @@ __export(index_exports, {
|
|
|
69
70
|
shouldShowWidget: () => import_feedback_kit_core4.shouldShowWidget,
|
|
70
71
|
sourceFromElement: () => import_feedback_kit_core5.sourceFromElement,
|
|
71
72
|
visibleText: () => visibleText,
|
|
72
|
-
webContextProviders: () => webContextProviders
|
|
73
|
+
webContextProviders: () => webContextProviders,
|
|
74
|
+
webDiagnosticsOptions: () => webDiagnosticsOptions,
|
|
75
|
+
webRouteStorage: () => webRouteStorage,
|
|
76
|
+
webRouteWatcher: () => webRouteWatcher
|
|
73
77
|
});
|
|
74
78
|
module.exports = __toCommonJS(index_exports);
|
|
75
79
|
var import_feedback_kit_core4 = require("@solhun/feedback-kit-core");
|
|
@@ -162,6 +166,7 @@ function reactComponentSummary(node) {
|
|
|
162
166
|
// src/element-info.ts
|
|
163
167
|
var ELEMENT_TEXT_MAX_CHARS = 200;
|
|
164
168
|
var SELECTOR_MAX_DEPTH = 8;
|
|
169
|
+
var PICK_TARGET_SNAP_PX = 24;
|
|
165
170
|
var KEPT_ATTRS = [
|
|
166
171
|
"id",
|
|
167
172
|
"class",
|
|
@@ -234,6 +239,55 @@ function visibleText(el) {
|
|
|
234
239
|
if (collapsed === "") return null;
|
|
235
240
|
return collapsed.length > ELEMENT_TEXT_MAX_CHARS ? collapsed.slice(0, ELEMENT_TEXT_MAX_CHARS) : collapsed;
|
|
236
241
|
}
|
|
242
|
+
function elementRect(element) {
|
|
243
|
+
try {
|
|
244
|
+
const rect = element.getBoundingClientRect();
|
|
245
|
+
const left = rect.left;
|
|
246
|
+
const top = rect.top;
|
|
247
|
+
const width = rect.width;
|
|
248
|
+
const height = rect.height;
|
|
249
|
+
if (![left, top, width, height].every(Number.isFinite) || width <= 0 || height <= 0) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
element,
|
|
254
|
+
left,
|
|
255
|
+
top,
|
|
256
|
+
right: left + width,
|
|
257
|
+
bottom: top + height,
|
|
258
|
+
width,
|
|
259
|
+
height
|
|
260
|
+
};
|
|
261
|
+
} catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function distanceFromPoint(rect, point) {
|
|
266
|
+
const dx = Math.max(rect.left - point.x, 0, point.x - rect.right);
|
|
267
|
+
const dy = Math.max(rect.top - point.y, 0, point.y - rect.bottom);
|
|
268
|
+
return Math.hypot(dx, dy);
|
|
269
|
+
}
|
|
270
|
+
function resolvePickTarget(target, point) {
|
|
271
|
+
if (!target || typeof target.tagName !== "string") return null;
|
|
272
|
+
if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) return target;
|
|
273
|
+
let current = target;
|
|
274
|
+
while (true) {
|
|
275
|
+
const currentRect = elementRect(current);
|
|
276
|
+
const children = Array.from(current.children).map(elementRect).filter((rect) => rect !== null).map((rect) => ({ rect, distance: distanceFromPoint(rect, point) }));
|
|
277
|
+
if (children.length === 0) return current;
|
|
278
|
+
const containing = children.filter(({ distance }) => distance === 0).sort((a, b) => a.rect.width * a.rect.height - b.rect.width * b.rect.height);
|
|
279
|
+
if (containing[0]) {
|
|
280
|
+
current = containing[0].rect.element;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const nearest = children.sort((a, b) => a.distance - b.distance)[0];
|
|
284
|
+
const isMorePrecise = currentRect ? nearest.rect.width * nearest.rect.height < currentRect.width * currentRect.height : true;
|
|
285
|
+
if (nearest.distance <= PICK_TARGET_SNAP_PX && isMorePrecise) {
|
|
286
|
+
return nearest.rect.element;
|
|
287
|
+
}
|
|
288
|
+
return current;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
237
291
|
function describeElement(el) {
|
|
238
292
|
if (!el || typeof el.tagName !== "string") return null;
|
|
239
293
|
const attributes = {};
|
|
@@ -354,6 +408,84 @@ var MarkerStore = class {
|
|
|
354
408
|
|
|
355
409
|
// src/picking.ts
|
|
356
410
|
var import_feedback_kit_core = require("@solhun/feedback-kit-core");
|
|
411
|
+
|
|
412
|
+
// src/route-watcher.ts
|
|
413
|
+
function defaultTarget() {
|
|
414
|
+
const g = globalThis;
|
|
415
|
+
return g.location ? g : null;
|
|
416
|
+
}
|
|
417
|
+
function createRouteWatcher(target = defaultTarget()) {
|
|
418
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
419
|
+
let detach = null;
|
|
420
|
+
const currentPath = () => {
|
|
421
|
+
const p = target?.location?.pathname;
|
|
422
|
+
return typeof p === "string" && p.length > 0 ? p : "/";
|
|
423
|
+
};
|
|
424
|
+
const emit = () => {
|
|
425
|
+
const path = currentPath();
|
|
426
|
+
for (const listener of [...listeners]) {
|
|
427
|
+
try {
|
|
428
|
+
listener(path);
|
|
429
|
+
} catch {
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
const attach = () => {
|
|
434
|
+
if (detach !== null || !target) return;
|
|
435
|
+
const onPop = () => emit();
|
|
436
|
+
target.addEventListener?.("popstate", onPop);
|
|
437
|
+
const history = target.history;
|
|
438
|
+
const originalPush = typeof history?.pushState === "function" ? history.pushState : null;
|
|
439
|
+
const originalReplace = typeof history?.replaceState === "function" ? history.replaceState : null;
|
|
440
|
+
if (history && originalPush) {
|
|
441
|
+
history.pushState = function patched(...args) {
|
|
442
|
+
const out = originalPush.apply(this, args);
|
|
443
|
+
emit();
|
|
444
|
+
return out;
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
if (history && originalReplace) {
|
|
448
|
+
history.replaceState = function patched(...args) {
|
|
449
|
+
const out = originalReplace.apply(this, args);
|
|
450
|
+
emit();
|
|
451
|
+
return out;
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
detach = () => {
|
|
455
|
+
target.removeEventListener?.("popstate", onPop);
|
|
456
|
+
if (history && originalPush) history.pushState = originalPush;
|
|
457
|
+
if (history && originalReplace) history.replaceState = originalReplace;
|
|
458
|
+
};
|
|
459
|
+
};
|
|
460
|
+
return (onPath) => {
|
|
461
|
+
listeners.add(onPath);
|
|
462
|
+
if (listeners.size === 1) attach();
|
|
463
|
+
try {
|
|
464
|
+
onPath(currentPath());
|
|
465
|
+
} catch {
|
|
466
|
+
}
|
|
467
|
+
return () => {
|
|
468
|
+
listeners.delete(onPath);
|
|
469
|
+
if (listeners.size === 0 && detach) {
|
|
470
|
+
detach();
|
|
471
|
+
detach = null;
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
var webRouteWatcher = createRouteWatcher();
|
|
477
|
+
function webRouteStorage() {
|
|
478
|
+
try {
|
|
479
|
+
const s = globalThis.sessionStorage;
|
|
480
|
+
if (!s || typeof s.getItem !== "function" || typeof s.setItem !== "function") return null;
|
|
481
|
+
s.getItem("feedback-kit:probe");
|
|
482
|
+
return s;
|
|
483
|
+
} catch {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// src/picking.ts
|
|
357
489
|
var OWN_UI_ATTR = "data-feedback-kit";
|
|
358
490
|
function defaultDocument() {
|
|
359
491
|
return globalThis.document ?? null;
|
|
@@ -375,23 +507,33 @@ var ElementPickingController = class {
|
|
|
375
507
|
constructor(opts) {
|
|
376
508
|
this.listeners = /* @__PURE__ */ new Set();
|
|
377
509
|
this.active = false;
|
|
510
|
+
/**
|
|
511
|
+
* 일시중지. **저장하지 않는다** — 멈추는 목적이 대개 "링크를 눌러 다른 화면으로 가는 것"
|
|
512
|
+
* 이라, 그 이동이 끝나면 지목이 돌아와 있는 게 목적에 맞다. 같은 이유로 경로가 바뀌면
|
|
513
|
+
* 스스로 풀린다(`syncPath`) — 그래야 SPA 이동과 새로고침이 같게 동작한다.
|
|
514
|
+
*/
|
|
515
|
+
this.paused = false;
|
|
378
516
|
this.attached = false;
|
|
517
|
+
/** 같은 요소 위 mousemove 마다 React 경로·선택자를 다시 만들지 않기 위한 캐시. */
|
|
518
|
+
this.hoveredTarget = null;
|
|
379
519
|
this.hovered = null;
|
|
380
520
|
this.popup = null;
|
|
381
521
|
this.markers = [];
|
|
382
|
-
|
|
522
|
+
/** 경로 감시 해제. 감시 중이 아니면 null. */
|
|
523
|
+
this.unwatchRoutes = null;
|
|
383
524
|
this.saving = false;
|
|
384
525
|
/** 늦게 끝난 캡처가 다음 주석의 그림을 덮지 못하게 하는 세대 번호. */
|
|
385
526
|
this.shotGeneration = 0;
|
|
386
527
|
/** 지금 도는 자동 캡처. Enter 가 캡처보다 빨랐을 때 기다릴 대상. */
|
|
387
528
|
this.capturing = null;
|
|
388
529
|
/**
|
|
389
|
-
* 사용자가 [
|
|
530
|
+
* 사용자가 [이런 건가요? ▾] 를 직접 건드렸는가. 한 번이라도 건드리면 이후 타이핑에 의한 자동
|
|
390
531
|
* 접힘/펼침이 멈춘다 — 모달의 `userToggledHints` 와 같은 규칙. 팝업을 새로 열 때마다 리셋된다.
|
|
391
532
|
*/
|
|
392
533
|
this.userToggledHints = false;
|
|
393
534
|
this.onClick = (event) => this.handleClick(event);
|
|
394
535
|
this.onMouseOver = (event) => this.handleMouseOver(event);
|
|
536
|
+
this.onMouseMove = (event) => this.handleMouseOver(event);
|
|
395
537
|
this.queue = opts.queue;
|
|
396
538
|
this.createReport = opts.createReport;
|
|
397
539
|
this.store = opts.store ?? new MarkerStore();
|
|
@@ -403,6 +545,7 @@ var ElementPickingController = class {
|
|
|
403
545
|
this.reencode = opts.reencode ?? null;
|
|
404
546
|
this.screenshotLimitBytes = opts.screenshotLimitBytes;
|
|
405
547
|
this.rankHintsFor = opts.rankHintsFor ?? null;
|
|
548
|
+
this.watchRoutes = opts.watchRoutes === void 0 ? webRouteWatcher : opts.watchRoutes;
|
|
406
549
|
this.lastPathname = this.getPathname();
|
|
407
550
|
this.markers = this.store.list(this.lastPathname);
|
|
408
551
|
this.unsubscribeQueue = this.queue.subscribe?.(() => {
|
|
@@ -413,6 +556,7 @@ var ElementPickingController = class {
|
|
|
413
556
|
getState() {
|
|
414
557
|
return {
|
|
415
558
|
active: this.active,
|
|
559
|
+
paused: this.paused,
|
|
416
560
|
hovered: this.hovered,
|
|
417
561
|
popup: this.popup,
|
|
418
562
|
markers: this.markers
|
|
@@ -421,6 +565,10 @@ var ElementPickingController = class {
|
|
|
421
565
|
get isActive() {
|
|
422
566
|
return this.active;
|
|
423
567
|
}
|
|
568
|
+
/** 켜져 있지만 클릭을 안 먹는 상태인가. */
|
|
569
|
+
get isPaused() {
|
|
570
|
+
return this.paused;
|
|
571
|
+
}
|
|
424
572
|
subscribe(listener) {
|
|
425
573
|
this.listeners.add(listener);
|
|
426
574
|
listener(this.getState());
|
|
@@ -432,6 +580,7 @@ var ElementPickingController = class {
|
|
|
432
580
|
start() {
|
|
433
581
|
if (this.active) return;
|
|
434
582
|
this.active = true;
|
|
583
|
+
this.paused = false;
|
|
435
584
|
this.store.setPickingActive(true);
|
|
436
585
|
this.attach();
|
|
437
586
|
this.startPathWatch();
|
|
@@ -441,13 +590,41 @@ var ElementPickingController = class {
|
|
|
441
590
|
/** [지목 종료]. 저장된 플래그까지 지워서 새로고침해도 다시 켜지지 않게 한다. */
|
|
442
591
|
stop() {
|
|
443
592
|
this.active = false;
|
|
593
|
+
this.paused = false;
|
|
444
594
|
this.store.setPickingActive(false);
|
|
445
595
|
this.detach();
|
|
446
596
|
this.stopPathWatch();
|
|
597
|
+
this.hoveredTarget = null;
|
|
447
598
|
this.hovered = null;
|
|
448
599
|
this.popup = null;
|
|
449
600
|
this.emit();
|
|
450
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* 잠시 멈춘다 — 페이지 클릭이 원래대로 동작하고, 모드는 켜진 채로 남는다.
|
|
604
|
+
*
|
|
605
|
+
* 팝업이 열려 있으면 **그대로 둔다.** 쓰던 한 줄을 여기서 지우면 「지목 켜면 모달 초안이
|
|
606
|
+
* 날아간다」와 같은 사고를 다른 자리에 만드는 셈이다. 팝업은 위젯 자신의 UI 라
|
|
607
|
+
* 리스너를 떼도 계속 눌린다.
|
|
608
|
+
*/
|
|
609
|
+
pause() {
|
|
610
|
+
if (!this.active || this.paused) return;
|
|
611
|
+
this.paused = true;
|
|
612
|
+
this.detach();
|
|
613
|
+
this.hoveredTarget = null;
|
|
614
|
+
this.hovered = null;
|
|
615
|
+
this.emit();
|
|
616
|
+
}
|
|
617
|
+
/** 다시 지목을 받는다. */
|
|
618
|
+
resume() {
|
|
619
|
+
if (!this.active || !this.paused) return;
|
|
620
|
+
this.paused = false;
|
|
621
|
+
this.attach();
|
|
622
|
+
this.emit();
|
|
623
|
+
}
|
|
624
|
+
togglePause() {
|
|
625
|
+
if (this.paused) this.resume();
|
|
626
|
+
else this.pause();
|
|
627
|
+
}
|
|
451
628
|
/**
|
|
452
629
|
* 저장돼 있던 모드를 되살린다. 새로고침·페이지 이동 직후에 한 번 부른다.
|
|
453
630
|
* @returns 되살아났으면 true.
|
|
@@ -461,12 +638,30 @@ var ElementPickingController = class {
|
|
|
461
638
|
this.start();
|
|
462
639
|
return true;
|
|
463
640
|
}
|
|
641
|
+
/**
|
|
642
|
+
* 이 화면에 쌓인 마커 표시를 지운다.
|
|
643
|
+
*
|
|
644
|
+
* 지워지는 건 **화면 표시뿐**이다. 제보는 이미 큐를 거쳐 수집처로 갔으므로 없어지지 않는다.
|
|
645
|
+
* 마커는 `localStorage` 에 남아 새로고침에도 살아남는데(그게 원래 목적이다) 정작 치울
|
|
646
|
+
* 수단이 없어서, 한 번 보낸 [완료] 배지가 그 화면을 볼 때마다 계속 따라다녔다.
|
|
647
|
+
*
|
|
648
|
+
* 화면에 보이는 목록의 기준은 `lastPathname` 이다(`reconcileMarkerOutcomes` 와 같다).
|
|
649
|
+
* 이동 직후 아직 `syncPath` 가 안 돈 순간에도 "지금 눈에 보이는 것"이 지워져야 한다.
|
|
650
|
+
*/
|
|
651
|
+
clearMarkers() {
|
|
652
|
+
if (this.markers.length === 0) return;
|
|
653
|
+
this.store.clear(this.lastPathname);
|
|
654
|
+
this.markers = [];
|
|
655
|
+
this.emit();
|
|
656
|
+
}
|
|
464
657
|
/** 경로가 바뀌었을 때 그 경로의 마커로 갈아 끼운다. */
|
|
465
658
|
syncPath() {
|
|
659
|
+
if (this.paused) this.resume();
|
|
466
660
|
this.lastPathname = this.getPathname();
|
|
467
661
|
this.markers = this.store.list(this.lastPathname);
|
|
468
662
|
this.reconcileMarkerOutcomes();
|
|
469
663
|
this.popup = null;
|
|
664
|
+
this.hoveredTarget = null;
|
|
470
665
|
this.hovered = null;
|
|
471
666
|
this.emit();
|
|
472
667
|
return this.markers;
|
|
@@ -482,21 +677,30 @@ var ElementPickingController = class {
|
|
|
482
677
|
if (this.attached || !this.doc) return;
|
|
483
678
|
this.doc.addEventListener("click", this.onClick, true);
|
|
484
679
|
this.doc.addEventListener("mouseover", this.onMouseOver, true);
|
|
680
|
+
this.doc.addEventListener("mousemove", this.onMouseMove, true);
|
|
485
681
|
this.attached = true;
|
|
486
682
|
}
|
|
487
683
|
detach() {
|
|
488
684
|
if (!this.attached || !this.doc) return;
|
|
489
685
|
this.doc.removeEventListener("click", this.onClick, true);
|
|
490
686
|
this.doc.removeEventListener("mouseover", this.onMouseOver, true);
|
|
687
|
+
this.doc.removeEventListener("mousemove", this.onMouseMove, true);
|
|
491
688
|
this.attached = false;
|
|
492
689
|
}
|
|
493
690
|
handleMouseOver(event) {
|
|
494
|
-
if (!this.active || isOwnUi(event.target)) return;
|
|
495
|
-
|
|
691
|
+
if (!this.active || this.paused || isOwnUi(event.target)) return;
|
|
692
|
+
const mouse = event;
|
|
693
|
+
const target = resolvePickTarget(event.target, {
|
|
694
|
+
x: mouse.clientX ?? 0,
|
|
695
|
+
y: mouse.clientY ?? 0
|
|
696
|
+
});
|
|
697
|
+
if (target === this.hoveredTarget) return;
|
|
698
|
+
this.hoveredTarget = target;
|
|
699
|
+
this.hovered = describeElement(target);
|
|
496
700
|
this.emit();
|
|
497
701
|
}
|
|
498
702
|
handleClick(event) {
|
|
499
|
-
if (!this.active) return;
|
|
703
|
+
if (!this.active || this.paused) return;
|
|
500
704
|
if (isOwnUi(event.target)) return;
|
|
501
705
|
event.preventDefault();
|
|
502
706
|
event.stopPropagation();
|
|
@@ -513,11 +717,12 @@ var ElementPickingController = class {
|
|
|
513
717
|
/** 클릭 지점 기준으로 주석 팝업을 연다. 좌표는 해상도 무관한 상대값으로 접어 둔다. */
|
|
514
718
|
openAnnotation(element, clientPoint) {
|
|
515
719
|
const point = (0, import_feedback_kit_core.normalizePin)(clientPoint, this.getViewport());
|
|
720
|
+
const target = resolvePickTarget(element, clientPoint);
|
|
516
721
|
if (this.onPick) {
|
|
517
|
-
this.onPick(describeElement(
|
|
722
|
+
this.onPick(describeElement(target), point);
|
|
518
723
|
return;
|
|
519
724
|
}
|
|
520
|
-
const info = describeElement(
|
|
725
|
+
const info = describeElement(target);
|
|
521
726
|
this.userToggledHints = false;
|
|
522
727
|
const hints = this.rankHintsFor ? this.rankHintsFor(info) : [];
|
|
523
728
|
this.popup = {
|
|
@@ -577,7 +782,7 @@ ${hint.draft}`;
|
|
|
577
782
|
};
|
|
578
783
|
this.emit();
|
|
579
784
|
}
|
|
580
|
-
/** [
|
|
785
|
+
/** [이런 건가요? ▾] 토글. 방향과 무관하게 이후 자동 접힘/펼침을 멈춘다. */
|
|
581
786
|
toggleHints() {
|
|
582
787
|
if (!this.popup) return;
|
|
583
788
|
this.userToggledHints = true;
|
|
@@ -732,17 +937,17 @@ ${hint.draft}`;
|
|
|
732
937
|
return outcome;
|
|
733
938
|
}
|
|
734
939
|
startPathWatch() {
|
|
735
|
-
if (this.
|
|
940
|
+
if (this.unwatchRoutes !== null || !this.watchRoutes) return;
|
|
736
941
|
this.lastPathname = this.getPathname();
|
|
737
|
-
this.
|
|
942
|
+
this.unwatchRoutes = this.watchRoutes(() => {
|
|
738
943
|
const pathname = this.getPathname();
|
|
739
944
|
if (pathname !== this.lastPathname) this.syncPath();
|
|
740
|
-
}
|
|
945
|
+
});
|
|
741
946
|
}
|
|
742
947
|
stopPathWatch() {
|
|
743
|
-
if (this.
|
|
744
|
-
|
|
745
|
-
this.
|
|
948
|
+
if (this.unwatchRoutes === null) return;
|
|
949
|
+
this.unwatchRoutes();
|
|
950
|
+
this.unwatchRoutes = null;
|
|
746
951
|
}
|
|
747
952
|
/** 전역 pending 수가 아니라 마커와 같은 clientSubmissionId의 확정 성공만 완료로 바꾼다. */
|
|
748
953
|
reconcileMarkerOutcomes() {
|
|
@@ -1009,7 +1214,7 @@ var import_feedback_kit_core3 = require("@solhun/feedback-kit-core");
|
|
|
1009
1214
|
var import_react = require("react");
|
|
1010
1215
|
|
|
1011
1216
|
// src/version.ts
|
|
1012
|
-
var VERSION = true ? "0.
|
|
1217
|
+
var VERSION = true ? "0.8.0" : "dev";
|
|
1013
1218
|
|
|
1014
1219
|
// src/feedback-kit.tsx
|
|
1015
1220
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -1031,6 +1236,7 @@ var OWN_UI_PROPS = { [OWN_UI_ATTR]: "" };
|
|
|
1031
1236
|
var FONT_STACK = "ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
1032
1237
|
var INITIAL_PICKING_STATE = {
|
|
1033
1238
|
active: false,
|
|
1239
|
+
paused: false,
|
|
1034
1240
|
hovered: null,
|
|
1035
1241
|
popup: null,
|
|
1036
1242
|
markers: []
|
|
@@ -1184,6 +1390,7 @@ var inputStyle = {
|
|
|
1184
1390
|
};
|
|
1185
1391
|
function PickToggle({
|
|
1186
1392
|
active,
|
|
1393
|
+
paused,
|
|
1187
1394
|
disabled,
|
|
1188
1395
|
variant,
|
|
1189
1396
|
focusId,
|
|
@@ -1211,14 +1418,9 @@ function PickToggle({
|
|
|
1211
1418
|
// 항상 값을 준다. 조건부로 빼면 리렌더 때 shorthand(border)와 충돌한다고 React 가 경고한다.
|
|
1212
1419
|
borderColor: active ? TOKENS.accent : TOKENS.line,
|
|
1213
1420
|
...floating ? {
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
transform: "translateX(-50%)",
|
|
1218
|
-
// 오버레이 자체는 클릭을 통과시킨다(pointerEvents:none) — 이 토글만 되살린다.
|
|
1219
|
-
pointerEvents: "auto",
|
|
1220
|
-
background: TOKENS.surface,
|
|
1221
|
-
boxShadow: TOKENS.shadow
|
|
1421
|
+
// 위치는 감싸는 `PickBar` 가 잡는다 — 옆에 [잠시 멈춤] 이 함께 서야 하는데
|
|
1422
|
+
// 토글이 스스로 fixed 면 둘을 나란히 둘 수 없다.
|
|
1423
|
+
background: TOKENS.surface
|
|
1222
1424
|
} : {
|
|
1223
1425
|
width: "100%",
|
|
1224
1426
|
justifyContent: "space-between",
|
|
@@ -1235,8 +1437,12 @@ function PickToggle({
|
|
|
1235
1437
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1236
1438
|
"span",
|
|
1237
1439
|
{
|
|
1238
|
-
style: {
|
|
1239
|
-
|
|
1440
|
+
style: {
|
|
1441
|
+
fontSize: 12,
|
|
1442
|
+
fontWeight: 600,
|
|
1443
|
+
color: active && !paused ? TOKENS.accent : TOKENS.muted
|
|
1444
|
+
},
|
|
1445
|
+
children: active ? paused ? "\uC77C\uC2DC\uC911\uC9C0" : "\uCF1C\uC9D0" : "\uAEBC\uC9D0"
|
|
1240
1446
|
}
|
|
1241
1447
|
),
|
|
1242
1448
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -1248,7 +1454,7 @@ function PickToggle({
|
|
|
1248
1454
|
width: 38,
|
|
1249
1455
|
height: 22,
|
|
1250
1456
|
borderRadius: 11,
|
|
1251
|
-
background: active ? TOKENS.accent : TOKENS.line
|
|
1457
|
+
background: active ? paused ? TOKENS.muted : TOKENS.accent : TOKENS.line
|
|
1252
1458
|
},
|
|
1253
1459
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1254
1460
|
"span",
|
|
@@ -1272,6 +1478,93 @@ function PickToggle({
|
|
|
1272
1478
|
}
|
|
1273
1479
|
);
|
|
1274
1480
|
}
|
|
1481
|
+
function PickBar({
|
|
1482
|
+
paused,
|
|
1483
|
+
markerCount,
|
|
1484
|
+
onToggle,
|
|
1485
|
+
onTogglePause,
|
|
1486
|
+
onClearMarkers
|
|
1487
|
+
}) {
|
|
1488
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1489
|
+
"div",
|
|
1490
|
+
{
|
|
1491
|
+
style: {
|
|
1492
|
+
position: "fixed",
|
|
1493
|
+
top: 16,
|
|
1494
|
+
// `left:50% + translateX(-50%)` 로 가운데를 잡으면 **쓸 수 있는 폭이 화면의 절반**이
|
|
1495
|
+
// 된다(transform 은 레이아웃 계산에 안 들어간다). 버튼이 셋이 되면서 그 절반에
|
|
1496
|
+
// 안 맞아 좁은 화면에서 버튼이 한 줄에 하나씩 세로로 쌓였다(390px 에서 3줄).
|
|
1497
|
+
// 양끝을 물리고 auto 마진으로 가운데를 잡으면 화면 전체 폭을 쓴다 — 같은 화면이 2줄.
|
|
1498
|
+
left: 0,
|
|
1499
|
+
right: 0,
|
|
1500
|
+
marginInline: "auto",
|
|
1501
|
+
width: "fit-content",
|
|
1502
|
+
maxWidth: "calc(100% - 32px)",
|
|
1503
|
+
display: "flex",
|
|
1504
|
+
alignItems: "stretch",
|
|
1505
|
+
justifyContent: "center",
|
|
1506
|
+
// 셋이 한 줄에 안 들어가는 폭에서는 줄을 바꾼다 — 안 그러면 화면 밖으로 나간다.
|
|
1507
|
+
flexWrap: "wrap",
|
|
1508
|
+
gap: 8,
|
|
1509
|
+
// 오버레이 자체는 클릭을 통과시킨다(pointerEvents:none) — 이 막대만 되살린다.
|
|
1510
|
+
pointerEvents: "auto",
|
|
1511
|
+
padding: 6,
|
|
1512
|
+
borderRadius: 12,
|
|
1513
|
+
background: TOKENS.surface,
|
|
1514
|
+
boxShadow: TOKENS.shadow
|
|
1515
|
+
},
|
|
1516
|
+
children: [
|
|
1517
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PickToggle, { variant: "floating", active: true, paused, onToggle }),
|
|
1518
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1519
|
+
"button",
|
|
1520
|
+
{
|
|
1521
|
+
type: "button",
|
|
1522
|
+
"aria-pressed": paused,
|
|
1523
|
+
"data-fk-pick-pause": paused ? "paused" : "running",
|
|
1524
|
+
onClick: onTogglePause,
|
|
1525
|
+
style: {
|
|
1526
|
+
...baseButton,
|
|
1527
|
+
display: "inline-flex",
|
|
1528
|
+
alignItems: "center",
|
|
1529
|
+
gap: 6,
|
|
1530
|
+
whiteSpace: "nowrap",
|
|
1531
|
+
borderColor: paused ? TOKENS.accent : TOKENS.line,
|
|
1532
|
+
color: paused ? TOKENS.accent : TOKENS.ink,
|
|
1533
|
+
fontWeight: 600
|
|
1534
|
+
},
|
|
1535
|
+
children: [
|
|
1536
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: paused ? "\u25B6" : "\u23F8" }),
|
|
1537
|
+
paused ? "\uC9C0\uBAA9 \uC7AC\uAC1C" : "\uC7A0\uC2DC \uBA48\uCDA4"
|
|
1538
|
+
]
|
|
1539
|
+
}
|
|
1540
|
+
),
|
|
1541
|
+
markerCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1542
|
+
"button",
|
|
1543
|
+
{
|
|
1544
|
+
type: "button",
|
|
1545
|
+
"data-fk-pick-clear": markerCount,
|
|
1546
|
+
title: "\uD654\uBA74\uC758 \uD45C\uC2DC\uB9CC \uC9C0\uC6C1\uB2C8\uB2E4. \uBCF4\uB0B8 \uC81C\uBCF4\uB294 \uADF8\uB300\uB85C \uB0A8\uC2B5\uB2C8\uB2E4.",
|
|
1547
|
+
"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)`,
|
|
1548
|
+
onClick: onClearMarkers,
|
|
1549
|
+
style: {
|
|
1550
|
+
...baseButton,
|
|
1551
|
+
display: "inline-flex",
|
|
1552
|
+
alignItems: "center",
|
|
1553
|
+
gap: 6,
|
|
1554
|
+
whiteSpace: "nowrap",
|
|
1555
|
+
color: TOKENS.muted
|
|
1556
|
+
},
|
|
1557
|
+
children: [
|
|
1558
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "\u2715" }),
|
|
1559
|
+
"\uD45C\uC2DC \uC9C0\uC6B0\uAE30 ",
|
|
1560
|
+
markerCount
|
|
1561
|
+
]
|
|
1562
|
+
}
|
|
1563
|
+
) : null
|
|
1564
|
+
]
|
|
1565
|
+
}
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1275
1568
|
function MarkerStatus({ status }) {
|
|
1276
1569
|
const presentation = status === "sending" ? { icon: "\u21BB", text: "\uC804\uC1A1 \uC911", color: TOKENS.accent } : status === "done" ? { icon: "\u2713", text: "\uC644\uB8CC", color: TOKENS.success } : { icon: "\u25F7", text: "\uB300\uAE30", color: TOKENS.warning };
|
|
1277
1570
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "inline-flex", gap: 4, alignItems: "center", color: presentation.color }, children: [
|
|
@@ -1295,47 +1588,7 @@ function HintChips({
|
|
|
1295
1588
|
onToggle
|
|
1296
1589
|
}) {
|
|
1297
1590
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 16 }, children: [
|
|
1298
|
-
|
|
1299
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: "0 0 7px", color: TOKENS.muted, fontSize: 12 }, children: "\uC774\uB7F0 \uAC74\uAC00\uC694?" }),
|
|
1300
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1301
|
-
"div",
|
|
1302
|
-
{
|
|
1303
|
-
role: "group",
|
|
1304
|
-
"aria-label": "\uC790\uC8FC \uB098\uC624\uB294 \uC81C\uBCF4 \uC81C\uC548",
|
|
1305
|
-
style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 7 },
|
|
1306
|
-
children: hints.map((hint) => {
|
|
1307
|
-
const used = usedHintIds.includes(hint.id);
|
|
1308
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1309
|
-
"button",
|
|
1310
|
-
{
|
|
1311
|
-
type: "button",
|
|
1312
|
-
"data-fk-focus-id": `hint:${hint.id}`,
|
|
1313
|
-
disabled: used || disabled,
|
|
1314
|
-
"aria-pressed": used,
|
|
1315
|
-
onClick: () => onApply(hint.id),
|
|
1316
|
-
style: {
|
|
1317
|
-
...baseButton,
|
|
1318
|
-
minHeight: 30,
|
|
1319
|
-
padding: "5px 10px",
|
|
1320
|
-
fontSize: 12,
|
|
1321
|
-
fontWeight: 600,
|
|
1322
|
-
borderRadius: 999,
|
|
1323
|
-
...used ? {
|
|
1324
|
-
background: TOKENS.subtle,
|
|
1325
|
-
color: TOKENS.muted,
|
|
1326
|
-
borderColor: TOKENS.line,
|
|
1327
|
-
cursor: "default"
|
|
1328
|
-
} : {}
|
|
1329
|
-
},
|
|
1330
|
-
children: hint.label
|
|
1331
|
-
},
|
|
1332
|
-
hint.id
|
|
1333
|
-
);
|
|
1334
|
-
})
|
|
1335
|
-
}
|
|
1336
|
-
)
|
|
1337
|
-
] }) : null,
|
|
1338
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1591
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1339
1592
|
"button",
|
|
1340
1593
|
{
|
|
1341
1594
|
type: "button",
|
|
@@ -1343,10 +1596,61 @@ function HintChips({
|
|
|
1343
1596
|
"aria-expanded": hintsExpanded,
|
|
1344
1597
|
disabled,
|
|
1345
1598
|
onClick: onToggle,
|
|
1346
|
-
style: {
|
|
1347
|
-
|
|
1599
|
+
style: {
|
|
1600
|
+
display: "block",
|
|
1601
|
+
margin: "0 0 7px",
|
|
1602
|
+
padding: 0,
|
|
1603
|
+
border: "none",
|
|
1604
|
+
background: "none",
|
|
1605
|
+
font: "inherit",
|
|
1606
|
+
fontSize: 12,
|
|
1607
|
+
color: TOKENS.muted,
|
|
1608
|
+
cursor: disabled ? "default" : "pointer",
|
|
1609
|
+
textAlign: "left"
|
|
1610
|
+
},
|
|
1611
|
+
children: [
|
|
1612
|
+
"\uC774\uB7F0 \uAC74\uAC00\uC694? ",
|
|
1613
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: hintsExpanded ? "\u25B4" : "\u25BE" })
|
|
1614
|
+
]
|
|
1348
1615
|
}
|
|
1349
|
-
)
|
|
1616
|
+
),
|
|
1617
|
+
hintsExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1618
|
+
"div",
|
|
1619
|
+
{
|
|
1620
|
+
role: "group",
|
|
1621
|
+
"aria-label": "\uC790\uC8FC \uB098\uC624\uB294 \uC81C\uBCF4 \uC81C\uC548",
|
|
1622
|
+
style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 7 },
|
|
1623
|
+
children: hints.map((hint) => {
|
|
1624
|
+
const used = usedHintIds.includes(hint.id);
|
|
1625
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1626
|
+
"button",
|
|
1627
|
+
{
|
|
1628
|
+
type: "button",
|
|
1629
|
+
"data-fk-focus-id": `hint:${hint.id}`,
|
|
1630
|
+
disabled: used || disabled,
|
|
1631
|
+
"aria-pressed": used,
|
|
1632
|
+
onClick: () => onApply(hint.id),
|
|
1633
|
+
style: {
|
|
1634
|
+
...baseButton,
|
|
1635
|
+
minHeight: 30,
|
|
1636
|
+
padding: "5px 10px",
|
|
1637
|
+
fontSize: 12,
|
|
1638
|
+
fontWeight: 600,
|
|
1639
|
+
borderRadius: 999,
|
|
1640
|
+
...used ? {
|
|
1641
|
+
background: TOKENS.subtle,
|
|
1642
|
+
color: TOKENS.muted,
|
|
1643
|
+
borderColor: TOKENS.line,
|
|
1644
|
+
cursor: "default"
|
|
1645
|
+
} : {}
|
|
1646
|
+
},
|
|
1647
|
+
children: hint.label
|
|
1648
|
+
},
|
|
1649
|
+
hint.id
|
|
1650
|
+
);
|
|
1651
|
+
})
|
|
1652
|
+
}
|
|
1653
|
+
) }) : null
|
|
1350
1654
|
] });
|
|
1351
1655
|
}
|
|
1352
1656
|
function FeedbackKit(props) {
|
|
@@ -1665,6 +1969,7 @@ function FeedbackKit(props) {
|
|
|
1665
1969
|
{
|
|
1666
1970
|
variant: "inline",
|
|
1667
1971
|
active: pickingActive,
|
|
1972
|
+
paused: pickingState.paused,
|
|
1668
1973
|
disabled: draftLocked,
|
|
1669
1974
|
focusId: import_feedback_kit_core3.MODAL_ACTION_PICK,
|
|
1670
1975
|
onToggle: togglePicking
|
|
@@ -1916,7 +2221,16 @@ function FeedbackKit(props) {
|
|
|
1916
2221
|
}
|
|
1917
2222
|
}
|
|
1918
2223
|
) : null,
|
|
1919
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2224
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2225
|
+
PickBar,
|
|
2226
|
+
{
|
|
2227
|
+
paused: pickingState.paused,
|
|
2228
|
+
markerCount: pickingState.markers.length,
|
|
2229
|
+
onToggle: togglePicking,
|
|
2230
|
+
onTogglePause: () => activeKit.picking.togglePause(),
|
|
2231
|
+
onClearMarkers: () => activeKit.picking.clearMarkers()
|
|
2232
|
+
}
|
|
2233
|
+
),
|
|
1920
2234
|
pickingState.markers.map((marker, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1921
2235
|
"div",
|
|
1922
2236
|
{
|
|
@@ -2140,6 +2454,13 @@ function webContextProviders() {
|
|
|
2140
2454
|
}
|
|
2141
2455
|
};
|
|
2142
2456
|
}
|
|
2457
|
+
function webDiagnosticsOptions(opts = {}) {
|
|
2458
|
+
return {
|
|
2459
|
+
watchRoutes: webRouteWatcher,
|
|
2460
|
+
routeStorage: webRouteStorage(),
|
|
2461
|
+
...opts
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2143
2464
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2144
2465
|
0 && (module.exports = {
|
|
2145
2466
|
COMMENT_MAX_CHARS,
|
|
@@ -2165,6 +2486,7 @@ function webContextProviders() {
|
|
|
2165
2486
|
SUBMIT_PENDING_MESSAGE,
|
|
2166
2487
|
WidgetController,
|
|
2167
2488
|
captureWebScreenshot,
|
|
2489
|
+
createRouteWatcher,
|
|
2168
2490
|
createWebStorage,
|
|
2169
2491
|
createWebWidget,
|
|
2170
2492
|
cssSelectorPath,
|
|
@@ -2181,6 +2503,9 @@ function webContextProviders() {
|
|
|
2181
2503
|
shouldShowWidget,
|
|
2182
2504
|
sourceFromElement,
|
|
2183
2505
|
visibleText,
|
|
2184
|
-
webContextProviders
|
|
2506
|
+
webContextProviders,
|
|
2507
|
+
webDiagnosticsOptions,
|
|
2508
|
+
webRouteStorage,
|
|
2509
|
+
webRouteWatcher
|
|
2185
2510
|
});
|
|
2186
2511
|
//# sourceMappingURL=index.cjs.map
|