@solhun/feedback-kit-web 0.4.0 → 0.6.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 +370 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -2
- package/dist/index.d.ts +42 -2
- package/dist/index.js +374 -89
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -37,8 +37,10 @@ __export(index_exports, {
|
|
|
37
37
|
ElementPickingController: () => ElementPickingController,
|
|
38
38
|
FLOATING_BUTTON_ID: () => import_feedback_kit_core6.FLOATING_BUTTON_ID,
|
|
39
39
|
FeedbackKit: () => FeedbackKit,
|
|
40
|
+
HintProvider: () => import_feedback_kit_core7.HintProvider,
|
|
40
41
|
MARKER_KEY_PREFIX: () => MARKER_KEY_PREFIX,
|
|
41
42
|
MAX_MARKERS_PER_PATH: () => MAX_MARKERS_PER_PATH,
|
|
43
|
+
MODAL_ACTION_HINT_TOGGLE: () => import_feedback_kit_core6.MODAL_ACTION_HINT_TOGGLE,
|
|
42
44
|
MODAL_ACTION_PICK: () => import_feedback_kit_core6.MODAL_ACTION_PICK,
|
|
43
45
|
MarkerStore: () => MarkerStore,
|
|
44
46
|
OWN_UI_ATTR: () => OWN_UI_ATTR,
|
|
@@ -59,6 +61,7 @@ __export(index_exports, {
|
|
|
59
61
|
isOwnUi: () => isOwnUi2,
|
|
60
62
|
normalizePin: () => import_feedback_kit_core6.normalizePin,
|
|
61
63
|
parseSourceAttr: () => import_feedback_kit_core5.parseSourceAttr,
|
|
64
|
+
rankHints: () => import_feedback_kit_core7.rankHints,
|
|
62
65
|
reactComponentPath: () => reactComponentPath,
|
|
63
66
|
reactComponentSummary: () => reactComponentSummary,
|
|
64
67
|
reencodeWebScreenshot: () => reencodeWebScreenshot,
|
|
@@ -72,6 +75,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
72
75
|
var import_feedback_kit_core4 = require("@solhun/feedback-kit-core");
|
|
73
76
|
var import_feedback_kit_core5 = require("@solhun/feedback-kit-core");
|
|
74
77
|
var import_feedback_kit_core6 = require("@solhun/feedback-kit-core");
|
|
78
|
+
var import_feedback_kit_core7 = require("@solhun/feedback-kit-core");
|
|
75
79
|
|
|
76
80
|
// src/react-tree.ts
|
|
77
81
|
var FRAMEWORK_INTERNALS = /* @__PURE__ */ new Set([
|
|
@@ -381,6 +385,11 @@ var ElementPickingController = class {
|
|
|
381
385
|
this.shotGeneration = 0;
|
|
382
386
|
/** 지금 도는 자동 캡처. Enter 가 캡처보다 빨랐을 때 기다릴 대상. */
|
|
383
387
|
this.capturing = null;
|
|
388
|
+
/**
|
|
389
|
+
* 사용자가 [제안 ▾] 를 직접 건드렸는가. 한 번이라도 건드리면 이후 타이핑에 의한 자동
|
|
390
|
+
* 접힘/펼침이 멈춘다 — 모달의 `userToggledHints` 와 같은 규칙. 팝업을 새로 열 때마다 리셋된다.
|
|
391
|
+
*/
|
|
392
|
+
this.userToggledHints = false;
|
|
384
393
|
this.onClick = (event) => this.handleClick(event);
|
|
385
394
|
this.onMouseOver = (event) => this.handleMouseOver(event);
|
|
386
395
|
this.queue = opts.queue;
|
|
@@ -393,6 +402,7 @@ var ElementPickingController = class {
|
|
|
393
402
|
this.capture = opts.capture ?? null;
|
|
394
403
|
this.reencode = opts.reencode ?? null;
|
|
395
404
|
this.screenshotLimitBytes = opts.screenshotLimitBytes;
|
|
405
|
+
this.rankHintsFor = opts.rankHintsFor ?? null;
|
|
396
406
|
this.lastPathname = this.getPathname();
|
|
397
407
|
this.markers = this.store.list(this.lastPathname);
|
|
398
408
|
this.unsubscribeQueue = this.queue.subscribe?.(() => {
|
|
@@ -507,14 +517,22 @@ var ElementPickingController = class {
|
|
|
507
517
|
this.onPick(describeElement(element), point);
|
|
508
518
|
return;
|
|
509
519
|
}
|
|
520
|
+
const info = describeElement(element);
|
|
521
|
+
this.userToggledHints = false;
|
|
522
|
+
const hints = this.rankHintsFor ? this.rankHintsFor(info) : [];
|
|
510
523
|
this.popup = {
|
|
511
|
-
element:
|
|
524
|
+
element: info,
|
|
512
525
|
point,
|
|
513
526
|
comment: "",
|
|
514
527
|
canSave: false,
|
|
515
528
|
screenshot: null,
|
|
516
529
|
screenshotStatus: this.capture ? "capturing" : "none",
|
|
517
|
-
saving: false
|
|
530
|
+
saving: false,
|
|
531
|
+
hints,
|
|
532
|
+
// 코멘트가 비어 있을 때만 펼친다 — 모달과 같은 규칙(계약 원문). 방금 연 팝업은 항상 빈다.
|
|
533
|
+
hintsExpanded: true,
|
|
534
|
+
hintsAvailable: hints.length > 0,
|
|
535
|
+
usedHintIds: []
|
|
518
536
|
};
|
|
519
537
|
this.emit();
|
|
520
538
|
if (this.capture) this.capturing = this.runCapture();
|
|
@@ -524,10 +542,48 @@ var ElementPickingController = class {
|
|
|
524
542
|
this.popup = {
|
|
525
543
|
...this.popup,
|
|
526
544
|
comment: value,
|
|
527
|
-
canSave: this.isSavable(value)
|
|
545
|
+
canSave: this.isSavable(value),
|
|
546
|
+
...this.hintsPatchFor(value)
|
|
528
547
|
};
|
|
529
548
|
this.emit();
|
|
530
549
|
}
|
|
550
|
+
/**
|
|
551
|
+
* 코멘트가 비었는지에 따라 칩 줄 상태를 갱신하는 조각. `setAnnotationComment`·`applyHint`
|
|
552
|
+
* 양쪽에서 쓴다 — 모달의 `hintsPatchFor` 와 같은 규칙이다.
|
|
553
|
+
*/
|
|
554
|
+
hintsPatchFor(nextComment) {
|
|
555
|
+
if (nextComment.trim().length === 0) {
|
|
556
|
+
return { hintsExpanded: true, usedHintIds: [] };
|
|
557
|
+
}
|
|
558
|
+
return this.userToggledHints ? {} : { hintsExpanded: false };
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* 칩 누름. 이미 눌린 칩이면 아무 일도 하지 않는다. 코멘트가 비어 있으면 초안으로 치환,
|
|
562
|
+
* 아니면 줄바꿈 뒤 이어붙인다 — 모달의 `applyHint` 와 동일한 규칙이다.
|
|
563
|
+
*/
|
|
564
|
+
applyHint(hintId) {
|
|
565
|
+
if (!this.popup || this.popup.saving) return;
|
|
566
|
+
if (this.popup.usedHintIds.includes(hintId)) return;
|
|
567
|
+
const hint = this.popup.hints.find((h) => h.id === hintId);
|
|
568
|
+
if (!hint) return;
|
|
569
|
+
const comment = this.popup.comment.length === 0 ? hint.draft : `${this.popup.comment}
|
|
570
|
+
${hint.draft}`;
|
|
571
|
+
this.popup = {
|
|
572
|
+
...this.popup,
|
|
573
|
+
comment,
|
|
574
|
+
canSave: this.isSavable(comment),
|
|
575
|
+
usedHintIds: [...this.popup.usedHintIds, hintId],
|
|
576
|
+
...this.hintsPatchFor(comment)
|
|
577
|
+
};
|
|
578
|
+
this.emit();
|
|
579
|
+
}
|
|
580
|
+
/** [제안 ▾] 토글. 방향과 무관하게 이후 자동 접힘/펼침을 멈춘다. */
|
|
581
|
+
toggleHints() {
|
|
582
|
+
if (!this.popup) return;
|
|
583
|
+
this.userToggledHints = true;
|
|
584
|
+
this.popup = { ...this.popup, hintsExpanded: !this.popup.hintsExpanded };
|
|
585
|
+
this.emit();
|
|
586
|
+
}
|
|
531
587
|
/**
|
|
532
588
|
* 사용자가 붙여넣기(Cmd+V)나 드래그로 넣은 그림. 자동 캡처 결과를 덮는다.
|
|
533
589
|
*
|
|
@@ -634,7 +690,9 @@ var ElementPickingController = class {
|
|
|
634
690
|
priority: "unset",
|
|
635
691
|
screenshot: settled.screenshot,
|
|
636
692
|
pin: settled.point,
|
|
637
|
-
element: settled.element
|
|
693
|
+
element: settled.element,
|
|
694
|
+
// 눌린 칩 기록. 리포트 모달과 같은 필드 — 들어온 문이 달라도 채택 신호는 같은 값으로 쌓인다.
|
|
695
|
+
hintIds: settled.usedHintIds
|
|
638
696
|
};
|
|
639
697
|
let report;
|
|
640
698
|
try {
|
|
@@ -795,11 +853,100 @@ var reencodeWebScreenshot = async (shot, quality) => {
|
|
|
795
853
|
|
|
796
854
|
// src/widget.ts
|
|
797
855
|
var import_feedback_kit_core2 = require("@solhun/feedback-kit-core");
|
|
856
|
+
|
|
857
|
+
// src/storage.ts
|
|
858
|
+
function memoryStorage() {
|
|
859
|
+
const map = /* @__PURE__ */ new Map();
|
|
860
|
+
return {
|
|
861
|
+
getItem: (key) => map.get(key) ?? null,
|
|
862
|
+
setItem: (key, value) => void map.set(key, value),
|
|
863
|
+
removeItem: (key) => void map.delete(key)
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
function pickBacking() {
|
|
867
|
+
try {
|
|
868
|
+
const ls = globalThis.localStorage;
|
|
869
|
+
if (!ls) return memoryStorage();
|
|
870
|
+
const probe = "__feedback_kit_probe__";
|
|
871
|
+
ls.setItem(probe, "1");
|
|
872
|
+
ls.removeItem(probe);
|
|
873
|
+
return ls;
|
|
874
|
+
} catch {
|
|
875
|
+
return memoryStorage();
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function createWebStorage(backing) {
|
|
879
|
+
const store = backing ?? pickBacking();
|
|
880
|
+
return {
|
|
881
|
+
async get(key) {
|
|
882
|
+
try {
|
|
883
|
+
return store.getItem(key);
|
|
884
|
+
} catch {
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
async set(key, value) {
|
|
889
|
+
store.setItem(key, value);
|
|
890
|
+
},
|
|
891
|
+
async remove(key) {
|
|
892
|
+
try {
|
|
893
|
+
store.removeItem(key);
|
|
894
|
+
} catch {
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// src/widget.ts
|
|
901
|
+
function stripQueryHash(path) {
|
|
902
|
+
const cut = [path.indexOf("?"), path.indexOf("#")].filter((i) => i >= 0);
|
|
903
|
+
return cut.length > 0 ? path.slice(0, Math.min(...cut)) : path;
|
|
904
|
+
}
|
|
905
|
+
function defaultPathname2() {
|
|
906
|
+
const loc = globalThis.location;
|
|
907
|
+
return loc?.pathname ?? "/";
|
|
908
|
+
}
|
|
909
|
+
function hintSourceFrom(queue) {
|
|
910
|
+
const adapter = queue.adapter;
|
|
911
|
+
if (!adapter || typeof adapter.fetchHints !== "function") return null;
|
|
912
|
+
const cacheKey = adapter.hintsCacheKey ?? "default";
|
|
913
|
+
return { fetchHints: adapter.fetchHints.bind(adapter), cacheKey };
|
|
914
|
+
}
|
|
915
|
+
function resolveHintProvider(opts) {
|
|
916
|
+
if (opts.hintProvider) return opts.hintProvider;
|
|
917
|
+
if (opts.hints) {
|
|
918
|
+
return new import_feedback_kit_core2.HintProvider({ source: null, staticHints: opts.hints, cacheKey: "static" });
|
|
919
|
+
}
|
|
920
|
+
const found = hintSourceFrom(opts.queue);
|
|
921
|
+
if (!found) return null;
|
|
922
|
+
return new import_feedback_kit_core2.HintProvider({
|
|
923
|
+
source: { fetchHints: (etag) => found.fetchHints(etag) },
|
|
924
|
+
storage: createWebStorage(),
|
|
925
|
+
cacheKey: found.cacheKey
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
var POPUP_HINT_DISPLAY_MAX = 3;
|
|
929
|
+
function rankFor(hintProvider, opts, element, limitOverride) {
|
|
930
|
+
const catalog = hintProvider.catalog();
|
|
931
|
+
if (!catalog) return [];
|
|
932
|
+
const input = {
|
|
933
|
+
platform: "web",
|
|
934
|
+
path: stripQueryHash(opts.getPathname ? opts.getPathname() : defaultPathname2()),
|
|
935
|
+
element,
|
|
936
|
+
components: element ? element.attributes["react-components"] ?? null : null
|
|
937
|
+
};
|
|
938
|
+
const limit = limitOverride !== void 0 ? Math.min(limitOverride, catalog.maxDisplay) : catalog.maxDisplay;
|
|
939
|
+
return (0, import_feedback_kit_core2.rankHints)(catalog.hints, input, limit);
|
|
940
|
+
}
|
|
798
941
|
function createWebWidget(opts) {
|
|
799
942
|
const store = opts.store ?? new MarkerStore();
|
|
800
943
|
let widgetRef = null;
|
|
801
944
|
const capture = opts.capture === void 0 ? captureWebScreenshot : opts.capture;
|
|
802
945
|
const reencode = opts.reencode === void 0 ? reencodeWebScreenshot : opts.reencode;
|
|
946
|
+
const hintProvider = resolveHintProvider(opts);
|
|
947
|
+
if (hintProvider) {
|
|
948
|
+
void hintProvider.load().then(() => hintProvider.refresh());
|
|
949
|
+
}
|
|
803
950
|
const picking = new ElementPickingController({
|
|
804
951
|
queue: opts.queue,
|
|
805
952
|
createReport: opts.createReport,
|
|
@@ -815,7 +962,8 @@ function createWebWidget(opts) {
|
|
|
815
962
|
// 모드는 어느 쪽이든 켜진 채로 둬서 한 번 켜고 여러 요소를 연달아 지목할 수 있다.
|
|
816
963
|
onPick: opts.pickTarget === "modal" ? (element, point) => {
|
|
817
964
|
void widgetRef?.openReport({ element, pin: point });
|
|
818
|
-
} : void 0
|
|
965
|
+
} : void 0,
|
|
966
|
+
rankHintsFor: hintProvider ? (element) => rankFor(hintProvider, opts, element, POPUP_HINT_DISPLAY_MAX) : null
|
|
819
967
|
});
|
|
820
968
|
const widget = new import_feedback_kit_core2.WidgetController({
|
|
821
969
|
...opts,
|
|
@@ -830,6 +978,13 @@ function createWebWidget(opts) {
|
|
|
830
978
|
}
|
|
831
979
|
});
|
|
832
980
|
widgetRef = widget;
|
|
981
|
+
const originalOpenReport = widget.openReport.bind(widget);
|
|
982
|
+
widget.openReport = async (openOpts = {}) => {
|
|
983
|
+
if (hintProvider) {
|
|
984
|
+
widget.modal.setHints(rankFor(hintProvider, opts, openOpts.element ?? null));
|
|
985
|
+
}
|
|
986
|
+
await originalOpenReport(openOpts);
|
|
987
|
+
};
|
|
833
988
|
const originalSubmit = widget.submitReport.bind(widget);
|
|
834
989
|
widget.submitReport = async () => {
|
|
835
990
|
const outcome = await originalSubmit();
|
|
@@ -854,7 +1009,7 @@ var import_feedback_kit_core3 = require("@solhun/feedback-kit-core");
|
|
|
854
1009
|
var import_react = require("react");
|
|
855
1010
|
|
|
856
1011
|
// src/version.ts
|
|
857
|
-
var VERSION = true ? "0.
|
|
1012
|
+
var VERSION = true ? "0.6.0" : "dev";
|
|
858
1013
|
|
|
859
1014
|
// src/feedback-kit.tsx
|
|
860
1015
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -1012,6 +1167,96 @@ var inputStyle = {
|
|
|
1012
1167
|
padding: "10px 11px",
|
|
1013
1168
|
font: "inherit"
|
|
1014
1169
|
};
|
|
1170
|
+
function PickToggle({
|
|
1171
|
+
active,
|
|
1172
|
+
disabled,
|
|
1173
|
+
variant,
|
|
1174
|
+
focusId,
|
|
1175
|
+
onToggle
|
|
1176
|
+
}) {
|
|
1177
|
+
const floating = variant === "floating";
|
|
1178
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1179
|
+
"button",
|
|
1180
|
+
{
|
|
1181
|
+
type: "button",
|
|
1182
|
+
role: "switch",
|
|
1183
|
+
"aria-checked": active,
|
|
1184
|
+
"aria-label": "\uC694\uC18C \uC9C0\uBAA9 \uBAA8\uB4DC",
|
|
1185
|
+
"data-fk-pick-toggle": active ? "on" : "off",
|
|
1186
|
+
"data-fk-focus-id": focusId,
|
|
1187
|
+
disabled,
|
|
1188
|
+
onClick: onToggle,
|
|
1189
|
+
style: {
|
|
1190
|
+
...baseButton,
|
|
1191
|
+
display: "flex",
|
|
1192
|
+
alignItems: "center",
|
|
1193
|
+
gap: 10,
|
|
1194
|
+
opacity: disabled ? 0.5 : 1,
|
|
1195
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
1196
|
+
// 항상 값을 준다. 조건부로 빼면 리렌더 때 shorthand(border)와 충돌한다고 React 가 경고한다.
|
|
1197
|
+
borderColor: active ? TOKENS.accent : TOKENS.line,
|
|
1198
|
+
...floating ? {
|
|
1199
|
+
position: "fixed",
|
|
1200
|
+
top: 16,
|
|
1201
|
+
left: "50%",
|
|
1202
|
+
transform: "translateX(-50%)",
|
|
1203
|
+
// 오버레이 자체는 클릭을 통과시킨다(pointerEvents:none) — 이 토글만 되살린다.
|
|
1204
|
+
pointerEvents: "auto",
|
|
1205
|
+
background: TOKENS.surface,
|
|
1206
|
+
boxShadow: TOKENS.shadow
|
|
1207
|
+
} : {
|
|
1208
|
+
width: "100%",
|
|
1209
|
+
justifyContent: "space-between",
|
|
1210
|
+
marginBottom: 16,
|
|
1211
|
+
background: active ? "rgba(49, 93, 115, 0.06)" : TOKENS.surface
|
|
1212
|
+
}
|
|
1213
|
+
},
|
|
1214
|
+
children: [
|
|
1215
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "inline-flex", alignItems: "center", gap: 7 }, children: [
|
|
1216
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: { color: active ? TOKENS.accent : TOKENS.muted }, children: "\u25CE" }),
|
|
1217
|
+
"\uC694\uC18C \uC9C0\uBAA9 \uBAA8\uB4DC"
|
|
1218
|
+
] }),
|
|
1219
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { "aria-hidden": "true", style: { display: "inline-flex", alignItems: "center", gap: 8 }, children: [
|
|
1220
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1221
|
+
"span",
|
|
1222
|
+
{
|
|
1223
|
+
style: { fontSize: 12, fontWeight: 600, color: active ? TOKENS.accent : TOKENS.muted },
|
|
1224
|
+
children: active ? "\uCF1C\uC9D0" : "\uAEBC\uC9D0"
|
|
1225
|
+
}
|
|
1226
|
+
),
|
|
1227
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1228
|
+
"span",
|
|
1229
|
+
{
|
|
1230
|
+
style: {
|
|
1231
|
+
position: "relative",
|
|
1232
|
+
display: "inline-block",
|
|
1233
|
+
width: 38,
|
|
1234
|
+
height: 22,
|
|
1235
|
+
borderRadius: 11,
|
|
1236
|
+
background: active ? TOKENS.accent : TOKENS.line
|
|
1237
|
+
},
|
|
1238
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1239
|
+
"span",
|
|
1240
|
+
{
|
|
1241
|
+
style: {
|
|
1242
|
+
position: "absolute",
|
|
1243
|
+
top: 3,
|
|
1244
|
+
left: active ? 19 : 3,
|
|
1245
|
+
width: 16,
|
|
1246
|
+
height: 16,
|
|
1247
|
+
borderRadius: "50%",
|
|
1248
|
+
background: TOKENS.surface,
|
|
1249
|
+
boxShadow: "0 1px 3px rgba(23, 32, 42, 0.35)"
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
)
|
|
1253
|
+
}
|
|
1254
|
+
)
|
|
1255
|
+
] })
|
|
1256
|
+
]
|
|
1257
|
+
}
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1015
1260
|
function MarkerStatus({ status }) {
|
|
1016
1261
|
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 };
|
|
1017
1262
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "inline-flex", gap: 4, alignItems: "center", color: presentation.color }, children: [
|
|
@@ -1026,6 +1271,69 @@ function MarkerStatus({ status }) {
|
|
|
1026
1271
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: presentation.text })
|
|
1027
1272
|
] });
|
|
1028
1273
|
}
|
|
1274
|
+
function HintChips({
|
|
1275
|
+
hints,
|
|
1276
|
+
hintsExpanded,
|
|
1277
|
+
usedHintIds,
|
|
1278
|
+
disabled,
|
|
1279
|
+
onApply,
|
|
1280
|
+
onToggle
|
|
1281
|
+
}) {
|
|
1282
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 16 }, children: [
|
|
1283
|
+
hintsExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1284
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: "0 0 7px", color: TOKENS.muted, fontSize: 12 }, children: "\uC774\uB7F0 \uAC74\uAC00\uC694?" }),
|
|
1285
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1286
|
+
"div",
|
|
1287
|
+
{
|
|
1288
|
+
role: "group",
|
|
1289
|
+
"aria-label": "\uC790\uC8FC \uB098\uC624\uB294 \uC81C\uBCF4 \uC81C\uC548",
|
|
1290
|
+
style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 7 },
|
|
1291
|
+
children: hints.map((hint) => {
|
|
1292
|
+
const used = usedHintIds.includes(hint.id);
|
|
1293
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1294
|
+
"button",
|
|
1295
|
+
{
|
|
1296
|
+
type: "button",
|
|
1297
|
+
"data-fk-focus-id": `hint:${hint.id}`,
|
|
1298
|
+
disabled: used || disabled,
|
|
1299
|
+
"aria-pressed": used,
|
|
1300
|
+
onClick: () => onApply(hint.id),
|
|
1301
|
+
style: {
|
|
1302
|
+
...baseButton,
|
|
1303
|
+
minHeight: 30,
|
|
1304
|
+
padding: "5px 10px",
|
|
1305
|
+
fontSize: 12,
|
|
1306
|
+
fontWeight: 600,
|
|
1307
|
+
borderRadius: 999,
|
|
1308
|
+
...used ? {
|
|
1309
|
+
background: TOKENS.subtle,
|
|
1310
|
+
color: TOKENS.muted,
|
|
1311
|
+
borderColor: TOKENS.line,
|
|
1312
|
+
cursor: "default"
|
|
1313
|
+
} : {}
|
|
1314
|
+
},
|
|
1315
|
+
children: hint.label
|
|
1316
|
+
},
|
|
1317
|
+
hint.id
|
|
1318
|
+
);
|
|
1319
|
+
})
|
|
1320
|
+
}
|
|
1321
|
+
)
|
|
1322
|
+
] }) : null,
|
|
1323
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1324
|
+
"button",
|
|
1325
|
+
{
|
|
1326
|
+
type: "button",
|
|
1327
|
+
"data-fk-focus-id": import_feedback_kit_core3.MODAL_ACTION_HINT_TOGGLE,
|
|
1328
|
+
"aria-expanded": hintsExpanded,
|
|
1329
|
+
disabled,
|
|
1330
|
+
onClick: onToggle,
|
|
1331
|
+
style: { ...baseButton, minHeight: 28, padding: "4px 10px", fontSize: 12 },
|
|
1332
|
+
children: hintsExpanded ? "\uC81C\uC548 \u25B4" : "\uC81C\uC548 \u25BE"
|
|
1333
|
+
}
|
|
1334
|
+
)
|
|
1335
|
+
] });
|
|
1336
|
+
}
|
|
1029
1337
|
function FeedbackKit(props) {
|
|
1030
1338
|
const rootRef = (0, import_react.useRef)(null);
|
|
1031
1339
|
const floatingButtonRef = (0, import_react.useRef)(null);
|
|
@@ -1048,7 +1356,10 @@ function FeedbackKit(props) {
|
|
|
1048
1356
|
store,
|
|
1049
1357
|
getPathname,
|
|
1050
1358
|
doc,
|
|
1051
|
-
getViewport
|
|
1359
|
+
getViewport,
|
|
1360
|
+
pickTarget,
|
|
1361
|
+
hints,
|
|
1362
|
+
hintProvider
|
|
1052
1363
|
} = props;
|
|
1053
1364
|
(0, import_react.useEffect)(() => {
|
|
1054
1365
|
const created = createWebWidget({
|
|
@@ -1060,7 +1371,10 @@ function FeedbackKit(props) {
|
|
|
1060
1371
|
store,
|
|
1061
1372
|
getPathname,
|
|
1062
1373
|
doc,
|
|
1063
|
-
getViewport
|
|
1374
|
+
getViewport,
|
|
1375
|
+
pickTarget,
|
|
1376
|
+
hints,
|
|
1377
|
+
hintProvider
|
|
1064
1378
|
});
|
|
1065
1379
|
setKit(created);
|
|
1066
1380
|
const unsubscribeWidget = created.widget.subscribe(setWidgetState);
|
|
@@ -1078,6 +1392,9 @@ function FeedbackKit(props) {
|
|
|
1078
1392
|
screenshotLimitBytes,
|
|
1079
1393
|
store,
|
|
1080
1394
|
getPathname,
|
|
1395
|
+
pickTarget,
|
|
1396
|
+
hints,
|
|
1397
|
+
hintProvider,
|
|
1081
1398
|
doc,
|
|
1082
1399
|
getViewport
|
|
1083
1400
|
]);
|
|
@@ -1120,8 +1437,12 @@ function FeedbackKit(props) {
|
|
|
1120
1437
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ...OWN_UI_PROPS, "data-feedback-kit-loading": "true" });
|
|
1121
1438
|
}
|
|
1122
1439
|
const activeKit = kit;
|
|
1123
|
-
const { modal, screen } = widgetState;
|
|
1440
|
+
const { modal, screen, pickingActive } = widgetState;
|
|
1124
1441
|
const draftLocked = modal.submitStatus === "sending" || modal.submitStatus === "pending";
|
|
1442
|
+
function togglePicking() {
|
|
1443
|
+
if (pickingActive) activeKit.widget.stopPicking();
|
|
1444
|
+
else activeKit.widget.startPicking();
|
|
1445
|
+
}
|
|
1125
1446
|
async function withOwnUiHidden(action) {
|
|
1126
1447
|
const root = rootRef.current;
|
|
1127
1448
|
const previousVisibility = root?.style.visibility ?? "";
|
|
@@ -1193,8 +1514,7 @@ function FeedbackKit(props) {
|
|
|
1193
1514
|
ref: floatingButtonRef,
|
|
1194
1515
|
id: import_feedback_kit_core3.FLOATING_BUTTON_ID,
|
|
1195
1516
|
type: "button",
|
|
1196
|
-
|
|
1197
|
-
"aria-label": screen === "picking" ? "\uC694\uC18C \uC9C0\uBAA9 \uC911" : "\uD53C\uB4DC\uBC31 \uBCF4\uB0B4\uAE30",
|
|
1517
|
+
"aria-label": "\uD53C\uB4DC\uBC31 \uBCF4\uB0B4\uAE30",
|
|
1198
1518
|
onClick: () => void openReport(),
|
|
1199
1519
|
style: {
|
|
1200
1520
|
...primaryButton,
|
|
@@ -1205,11 +1525,9 @@ function FeedbackKit(props) {
|
|
|
1205
1525
|
minWidth: 112,
|
|
1206
1526
|
minHeight: 46,
|
|
1207
1527
|
borderRadius: 24,
|
|
1208
|
-
boxShadow: TOKENS.shadow
|
|
1209
|
-
opacity: screen === "picking" ? 0.76 : 1,
|
|
1210
|
-
cursor: screen === "picking" ? "default" : "pointer"
|
|
1528
|
+
boxShadow: TOKENS.shadow
|
|
1211
1529
|
},
|
|
1212
|
-
children:
|
|
1530
|
+
children: "\uD53C\uB4DC\uBC31"
|
|
1213
1531
|
}
|
|
1214
1532
|
) : null,
|
|
1215
1533
|
announcement && !modal.open ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -1293,7 +1611,17 @@ function FeedbackKit(props) {
|
|
|
1293
1611
|
]
|
|
1294
1612
|
}
|
|
1295
1613
|
),
|
|
1296
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: "0 0
|
|
1614
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: "0 0 14px", color: TOKENS.muted }, children: "\uD604\uC7AC \uD654\uBA74\uC758 \uBB38\uC81C\uB098 \uC758\uACAC\uC744 \uB0A8\uACA8\uC8FC\uC138\uC694." }),
|
|
1615
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1616
|
+
PickToggle,
|
|
1617
|
+
{
|
|
1618
|
+
variant: "inline",
|
|
1619
|
+
active: pickingActive,
|
|
1620
|
+
disabled: draftLocked,
|
|
1621
|
+
focusId: import_feedback_kit_core3.MODAL_ACTION_PICK,
|
|
1622
|
+
onToggle: togglePicking
|
|
1623
|
+
}
|
|
1624
|
+
),
|
|
1297
1625
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { "aria-labelledby": "feedback-kit-screenshot-label", style: { marginBottom: 16 }, children: [
|
|
1298
1626
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { id: "feedback-kit-screenshot-label", children: "\uC2A4\uD06C\uB9B0\uC0F7" }),
|
|
1299
1627
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -1365,6 +1693,17 @@ function FeedbackKit(props) {
|
|
|
1365
1693
|
)
|
|
1366
1694
|
] })
|
|
1367
1695
|
] }),
|
|
1696
|
+
modal.hintsAvailable ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1697
|
+
HintChips,
|
|
1698
|
+
{
|
|
1699
|
+
hints: modal.hints,
|
|
1700
|
+
hintsExpanded: modal.hintsExpanded,
|
|
1701
|
+
usedHintIds: modal.usedHintIds,
|
|
1702
|
+
disabled: draftLocked,
|
|
1703
|
+
onApply: (hintId) => kit.widget.modal.applyHint(hintId),
|
|
1704
|
+
onToggle: () => kit.widget.modal.toggleHints()
|
|
1705
|
+
}
|
|
1706
|
+
) : null,
|
|
1368
1707
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor: "feedback-kit-comment", style: { display: "block", fontWeight: 700 }, children: [
|
|
1369
1708
|
"\uCF54\uBA58\uD2B8 ",
|
|
1370
1709
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "*" })
|
|
@@ -1407,19 +1746,6 @@ function FeedbackKit(props) {
|
|
|
1407
1746
|
]
|
|
1408
1747
|
}
|
|
1409
1748
|
),
|
|
1410
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1411
|
-
"button",
|
|
1412
|
-
{
|
|
1413
|
-
type: "button",
|
|
1414
|
-
role: "switch",
|
|
1415
|
-
"aria-checked": "false",
|
|
1416
|
-
disabled: draftLocked,
|
|
1417
|
-
"data-fk-focus-id": import_feedback_kit_core3.MODAL_ACTION_PICK,
|
|
1418
|
-
onClick: () => kit.widget.startPicking(),
|
|
1419
|
-
style: { ...baseButton, width: "100%", marginTop: 16 },
|
|
1420
|
-
children: "\uC694\uC18C \uC9C0\uBAA9"
|
|
1421
|
-
}
|
|
1422
|
-
),
|
|
1423
1749
|
modal.submitStatus !== "idle" || modal.pending > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1424
1750
|
"div",
|
|
1425
1751
|
{
|
|
@@ -1542,22 +1868,7 @@ function FeedbackKit(props) {
|
|
|
1542
1868
|
}
|
|
1543
1869
|
}
|
|
1544
1870
|
) : null,
|
|
1545
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1546
|
-
"button",
|
|
1547
|
-
{
|
|
1548
|
-
type: "button",
|
|
1549
|
-
onClick: () => kit.widget.stopPicking(),
|
|
1550
|
-
style: {
|
|
1551
|
-
...primaryButton,
|
|
1552
|
-
position: "fixed",
|
|
1553
|
-
top: 18,
|
|
1554
|
-
right: 18,
|
|
1555
|
-
pointerEvents: "auto",
|
|
1556
|
-
boxShadow: TOKENS.shadow
|
|
1557
|
-
},
|
|
1558
|
-
children: "\uC9C0\uBAA9 \uC885\uB8CC"
|
|
1559
|
-
}
|
|
1560
|
-
),
|
|
1871
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PickToggle, { variant: "floating", active: true, onToggle: togglePicking }),
|
|
1561
1872
|
pickingState.markers.map((marker, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1562
1873
|
"div",
|
|
1563
1874
|
{
|
|
@@ -1649,6 +1960,17 @@ function FeedbackKit(props) {
|
|
|
1649
1960
|
]
|
|
1650
1961
|
}
|
|
1651
1962
|
),
|
|
1963
|
+
popup.hintsAvailable ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1964
|
+
HintChips,
|
|
1965
|
+
{
|
|
1966
|
+
hints: popup.hints,
|
|
1967
|
+
hintsExpanded: popup.hintsExpanded,
|
|
1968
|
+
usedHintIds: popup.usedHintIds,
|
|
1969
|
+
disabled: popup.saving,
|
|
1970
|
+
onApply: (hintId) => kit.picking.applyHint(hintId),
|
|
1971
|
+
onToggle: () => kit.picking.toggleHints()
|
|
1972
|
+
}
|
|
1973
|
+
) : null,
|
|
1652
1974
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "feedback-kit-annotation", style: visuallyHidden, children: "\uC774 \uC694\uC18C\uC5D0 \uB0A8\uAE38 \uC758\uACAC" }),
|
|
1653
1975
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1654
1976
|
"textarea",
|
|
@@ -1749,49 +2071,6 @@ function FeedbackKit(props) {
|
|
|
1749
2071
|
);
|
|
1750
2072
|
}
|
|
1751
2073
|
|
|
1752
|
-
// src/storage.ts
|
|
1753
|
-
function memoryStorage() {
|
|
1754
|
-
const map = /* @__PURE__ */ new Map();
|
|
1755
|
-
return {
|
|
1756
|
-
getItem: (key) => map.get(key) ?? null,
|
|
1757
|
-
setItem: (key, value) => void map.set(key, value),
|
|
1758
|
-
removeItem: (key) => void map.delete(key)
|
|
1759
|
-
};
|
|
1760
|
-
}
|
|
1761
|
-
function pickBacking() {
|
|
1762
|
-
try {
|
|
1763
|
-
const ls = globalThis.localStorage;
|
|
1764
|
-
if (!ls) return memoryStorage();
|
|
1765
|
-
const probe = "__feedback_kit_probe__";
|
|
1766
|
-
ls.setItem(probe, "1");
|
|
1767
|
-
ls.removeItem(probe);
|
|
1768
|
-
return ls;
|
|
1769
|
-
} catch {
|
|
1770
|
-
return memoryStorage();
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
function createWebStorage(backing) {
|
|
1774
|
-
const store = backing ?? pickBacking();
|
|
1775
|
-
return {
|
|
1776
|
-
async get(key) {
|
|
1777
|
-
try {
|
|
1778
|
-
return store.getItem(key);
|
|
1779
|
-
} catch {
|
|
1780
|
-
return null;
|
|
1781
|
-
}
|
|
1782
|
-
},
|
|
1783
|
-
async set(key, value) {
|
|
1784
|
-
store.setItem(key, value);
|
|
1785
|
-
},
|
|
1786
|
-
async remove(key) {
|
|
1787
|
-
try {
|
|
1788
|
-
store.removeItem(key);
|
|
1789
|
-
} catch {
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
};
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
2074
|
// src/providers.ts
|
|
1796
2075
|
function webContextProviders() {
|
|
1797
2076
|
return {
|
|
@@ -1822,8 +2101,10 @@ function webContextProviders() {
|
|
|
1822
2101
|
ElementPickingController,
|
|
1823
2102
|
FLOATING_BUTTON_ID,
|
|
1824
2103
|
FeedbackKit,
|
|
2104
|
+
HintProvider,
|
|
1825
2105
|
MARKER_KEY_PREFIX,
|
|
1826
2106
|
MAX_MARKERS_PER_PATH,
|
|
2107
|
+
MODAL_ACTION_HINT_TOGGLE,
|
|
1827
2108
|
MODAL_ACTION_PICK,
|
|
1828
2109
|
MarkerStore,
|
|
1829
2110
|
OWN_UI_ATTR,
|
|
@@ -1844,6 +2125,7 @@ function webContextProviders() {
|
|
|
1844
2125
|
isOwnUi,
|
|
1845
2126
|
normalizePin,
|
|
1846
2127
|
parseSourceAttr,
|
|
2128
|
+
rankHints,
|
|
1847
2129
|
reactComponentPath,
|
|
1848
2130
|
reactComponentSummary,
|
|
1849
2131
|
reencodeWebScreenshot,
|