@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.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
COMMENT_TOO_LONG_MESSAGE,
|
|
8
8
|
denormalizePin,
|
|
9
9
|
FLOATING_BUTTON_ID as FLOATING_BUTTON_ID2,
|
|
10
|
+
MODAL_ACTION_HINT_TOGGLE as MODAL_ACTION_HINT_TOGGLE2,
|
|
10
11
|
MODAL_ACTION_PICK as MODAL_ACTION_PICK2,
|
|
11
12
|
normalizePin as normalizePin2,
|
|
12
13
|
ReportModalController,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
SUBMIT_PENDING_MESSAGE,
|
|
16
17
|
WidgetController as WidgetController2
|
|
17
18
|
} from "@solhun/feedback-kit-core";
|
|
19
|
+
import { HintProvider as HintProvider2, rankHints as rankHints2 } from "@solhun/feedback-kit-core";
|
|
18
20
|
|
|
19
21
|
// src/react-tree.ts
|
|
20
22
|
var FRAMEWORK_INTERNALS = /* @__PURE__ */ new Set([
|
|
@@ -328,6 +330,11 @@ var ElementPickingController = class {
|
|
|
328
330
|
this.shotGeneration = 0;
|
|
329
331
|
/** 지금 도는 자동 캡처. Enter 가 캡처보다 빨랐을 때 기다릴 대상. */
|
|
330
332
|
this.capturing = null;
|
|
333
|
+
/**
|
|
334
|
+
* 사용자가 [제안 ▾] 를 직접 건드렸는가. 한 번이라도 건드리면 이후 타이핑에 의한 자동
|
|
335
|
+
* 접힘/펼침이 멈춘다 — 모달의 `userToggledHints` 와 같은 규칙. 팝업을 새로 열 때마다 리셋된다.
|
|
336
|
+
*/
|
|
337
|
+
this.userToggledHints = false;
|
|
331
338
|
this.onClick = (event) => this.handleClick(event);
|
|
332
339
|
this.onMouseOver = (event) => this.handleMouseOver(event);
|
|
333
340
|
this.queue = opts.queue;
|
|
@@ -340,6 +347,7 @@ var ElementPickingController = class {
|
|
|
340
347
|
this.capture = opts.capture ?? null;
|
|
341
348
|
this.reencode = opts.reencode ?? null;
|
|
342
349
|
this.screenshotLimitBytes = opts.screenshotLimitBytes;
|
|
350
|
+
this.rankHintsFor = opts.rankHintsFor ?? null;
|
|
343
351
|
this.lastPathname = this.getPathname();
|
|
344
352
|
this.markers = this.store.list(this.lastPathname);
|
|
345
353
|
this.unsubscribeQueue = this.queue.subscribe?.(() => {
|
|
@@ -454,14 +462,22 @@ var ElementPickingController = class {
|
|
|
454
462
|
this.onPick(describeElement(element), point);
|
|
455
463
|
return;
|
|
456
464
|
}
|
|
465
|
+
const info = describeElement(element);
|
|
466
|
+
this.userToggledHints = false;
|
|
467
|
+
const hints = this.rankHintsFor ? this.rankHintsFor(info) : [];
|
|
457
468
|
this.popup = {
|
|
458
|
-
element:
|
|
469
|
+
element: info,
|
|
459
470
|
point,
|
|
460
471
|
comment: "",
|
|
461
472
|
canSave: false,
|
|
462
473
|
screenshot: null,
|
|
463
474
|
screenshotStatus: this.capture ? "capturing" : "none",
|
|
464
|
-
saving: false
|
|
475
|
+
saving: false,
|
|
476
|
+
hints,
|
|
477
|
+
// 코멘트가 비어 있을 때만 펼친다 — 모달과 같은 규칙(계약 원문). 방금 연 팝업은 항상 빈다.
|
|
478
|
+
hintsExpanded: true,
|
|
479
|
+
hintsAvailable: hints.length > 0,
|
|
480
|
+
usedHintIds: []
|
|
465
481
|
};
|
|
466
482
|
this.emit();
|
|
467
483
|
if (this.capture) this.capturing = this.runCapture();
|
|
@@ -471,10 +487,48 @@ var ElementPickingController = class {
|
|
|
471
487
|
this.popup = {
|
|
472
488
|
...this.popup,
|
|
473
489
|
comment: value,
|
|
474
|
-
canSave: this.isSavable(value)
|
|
490
|
+
canSave: this.isSavable(value),
|
|
491
|
+
...this.hintsPatchFor(value)
|
|
475
492
|
};
|
|
476
493
|
this.emit();
|
|
477
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* 코멘트가 비었는지에 따라 칩 줄 상태를 갱신하는 조각. `setAnnotationComment`·`applyHint`
|
|
497
|
+
* 양쪽에서 쓴다 — 모달의 `hintsPatchFor` 와 같은 규칙이다.
|
|
498
|
+
*/
|
|
499
|
+
hintsPatchFor(nextComment) {
|
|
500
|
+
if (nextComment.trim().length === 0) {
|
|
501
|
+
return { hintsExpanded: true, usedHintIds: [] };
|
|
502
|
+
}
|
|
503
|
+
return this.userToggledHints ? {} : { hintsExpanded: false };
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* 칩 누름. 이미 눌린 칩이면 아무 일도 하지 않는다. 코멘트가 비어 있으면 초안으로 치환,
|
|
507
|
+
* 아니면 줄바꿈 뒤 이어붙인다 — 모달의 `applyHint` 와 동일한 규칙이다.
|
|
508
|
+
*/
|
|
509
|
+
applyHint(hintId) {
|
|
510
|
+
if (!this.popup || this.popup.saving) return;
|
|
511
|
+
if (this.popup.usedHintIds.includes(hintId)) return;
|
|
512
|
+
const hint = this.popup.hints.find((h) => h.id === hintId);
|
|
513
|
+
if (!hint) return;
|
|
514
|
+
const comment = this.popup.comment.length === 0 ? hint.draft : `${this.popup.comment}
|
|
515
|
+
${hint.draft}`;
|
|
516
|
+
this.popup = {
|
|
517
|
+
...this.popup,
|
|
518
|
+
comment,
|
|
519
|
+
canSave: this.isSavable(comment),
|
|
520
|
+
usedHintIds: [...this.popup.usedHintIds, hintId],
|
|
521
|
+
...this.hintsPatchFor(comment)
|
|
522
|
+
};
|
|
523
|
+
this.emit();
|
|
524
|
+
}
|
|
525
|
+
/** [제안 ▾] 토글. 방향과 무관하게 이후 자동 접힘/펼침을 멈춘다. */
|
|
526
|
+
toggleHints() {
|
|
527
|
+
if (!this.popup) return;
|
|
528
|
+
this.userToggledHints = true;
|
|
529
|
+
this.popup = { ...this.popup, hintsExpanded: !this.popup.hintsExpanded };
|
|
530
|
+
this.emit();
|
|
531
|
+
}
|
|
478
532
|
/**
|
|
479
533
|
* 사용자가 붙여넣기(Cmd+V)나 드래그로 넣은 그림. 자동 캡처 결과를 덮는다.
|
|
480
534
|
*
|
|
@@ -581,7 +635,9 @@ var ElementPickingController = class {
|
|
|
581
635
|
priority: "unset",
|
|
582
636
|
screenshot: settled.screenshot,
|
|
583
637
|
pin: settled.point,
|
|
584
|
-
element: settled.element
|
|
638
|
+
element: settled.element,
|
|
639
|
+
// 눌린 칩 기록. 리포트 모달과 같은 필드 — 들어온 문이 달라도 채택 신호는 같은 값으로 쌓인다.
|
|
640
|
+
hintIds: settled.usedHintIds
|
|
585
641
|
};
|
|
586
642
|
let report;
|
|
587
643
|
try {
|
|
@@ -741,12 +797,105 @@ var reencodeWebScreenshot = async (shot, quality) => {
|
|
|
741
797
|
};
|
|
742
798
|
|
|
743
799
|
// src/widget.ts
|
|
744
|
-
import {
|
|
800
|
+
import {
|
|
801
|
+
WidgetController,
|
|
802
|
+
HintProvider,
|
|
803
|
+
rankHints
|
|
804
|
+
} from "@solhun/feedback-kit-core";
|
|
805
|
+
|
|
806
|
+
// src/storage.ts
|
|
807
|
+
function memoryStorage() {
|
|
808
|
+
const map = /* @__PURE__ */ new Map();
|
|
809
|
+
return {
|
|
810
|
+
getItem: (key) => map.get(key) ?? null,
|
|
811
|
+
setItem: (key, value) => void map.set(key, value),
|
|
812
|
+
removeItem: (key) => void map.delete(key)
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
function pickBacking() {
|
|
816
|
+
try {
|
|
817
|
+
const ls = globalThis.localStorage;
|
|
818
|
+
if (!ls) return memoryStorage();
|
|
819
|
+
const probe = "__feedback_kit_probe__";
|
|
820
|
+
ls.setItem(probe, "1");
|
|
821
|
+
ls.removeItem(probe);
|
|
822
|
+
return ls;
|
|
823
|
+
} catch {
|
|
824
|
+
return memoryStorage();
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
function createWebStorage(backing) {
|
|
828
|
+
const store = backing ?? pickBacking();
|
|
829
|
+
return {
|
|
830
|
+
async get(key) {
|
|
831
|
+
try {
|
|
832
|
+
return store.getItem(key);
|
|
833
|
+
} catch {
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
},
|
|
837
|
+
async set(key, value) {
|
|
838
|
+
store.setItem(key, value);
|
|
839
|
+
},
|
|
840
|
+
async remove(key) {
|
|
841
|
+
try {
|
|
842
|
+
store.removeItem(key);
|
|
843
|
+
} catch {
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/widget.ts
|
|
850
|
+
function stripQueryHash(path) {
|
|
851
|
+
const cut = [path.indexOf("?"), path.indexOf("#")].filter((i) => i >= 0);
|
|
852
|
+
return cut.length > 0 ? path.slice(0, Math.min(...cut)) : path;
|
|
853
|
+
}
|
|
854
|
+
function defaultPathname2() {
|
|
855
|
+
const loc = globalThis.location;
|
|
856
|
+
return loc?.pathname ?? "/";
|
|
857
|
+
}
|
|
858
|
+
function hintSourceFrom(queue) {
|
|
859
|
+
const adapter = queue.adapter;
|
|
860
|
+
if (!adapter || typeof adapter.fetchHints !== "function") return null;
|
|
861
|
+
const cacheKey = adapter.hintsCacheKey ?? "default";
|
|
862
|
+
return { fetchHints: adapter.fetchHints.bind(adapter), cacheKey };
|
|
863
|
+
}
|
|
864
|
+
function resolveHintProvider(opts) {
|
|
865
|
+
if (opts.hintProvider) return opts.hintProvider;
|
|
866
|
+
if (opts.hints) {
|
|
867
|
+
return new HintProvider({ source: null, staticHints: opts.hints, cacheKey: "static" });
|
|
868
|
+
}
|
|
869
|
+
const found = hintSourceFrom(opts.queue);
|
|
870
|
+
if (!found) return null;
|
|
871
|
+
return new HintProvider({
|
|
872
|
+
source: { fetchHints: (etag) => found.fetchHints(etag) },
|
|
873
|
+
storage: createWebStorage(),
|
|
874
|
+
cacheKey: found.cacheKey
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
var POPUP_HINT_DISPLAY_MAX = 3;
|
|
878
|
+
function rankFor(hintProvider, opts, element, limitOverride) {
|
|
879
|
+
const catalog = hintProvider.catalog();
|
|
880
|
+
if (!catalog) return [];
|
|
881
|
+
const input = {
|
|
882
|
+
platform: "web",
|
|
883
|
+
path: stripQueryHash(opts.getPathname ? opts.getPathname() : defaultPathname2()),
|
|
884
|
+
element,
|
|
885
|
+
components: element ? element.attributes["react-components"] ?? null : null
|
|
886
|
+
};
|
|
887
|
+
const limit = limitOverride !== void 0 ? Math.min(limitOverride, catalog.maxDisplay) : catalog.maxDisplay;
|
|
888
|
+
return rankHints(catalog.hints, input, limit);
|
|
889
|
+
}
|
|
745
890
|
function createWebWidget(opts) {
|
|
746
891
|
const store = opts.store ?? new MarkerStore();
|
|
747
892
|
let widgetRef = null;
|
|
748
893
|
const capture = opts.capture === void 0 ? captureWebScreenshot : opts.capture;
|
|
749
894
|
const reencode = opts.reencode === void 0 ? reencodeWebScreenshot : opts.reencode;
|
|
895
|
+
const hintProvider = resolveHintProvider(opts);
|
|
896
|
+
if (hintProvider) {
|
|
897
|
+
void hintProvider.load().then(() => hintProvider.refresh());
|
|
898
|
+
}
|
|
750
899
|
const picking = new ElementPickingController({
|
|
751
900
|
queue: opts.queue,
|
|
752
901
|
createReport: opts.createReport,
|
|
@@ -762,7 +911,8 @@ function createWebWidget(opts) {
|
|
|
762
911
|
// 모드는 어느 쪽이든 켜진 채로 둬서 한 번 켜고 여러 요소를 연달아 지목할 수 있다.
|
|
763
912
|
onPick: opts.pickTarget === "modal" ? (element, point) => {
|
|
764
913
|
void widgetRef?.openReport({ element, pin: point });
|
|
765
|
-
} : void 0
|
|
914
|
+
} : void 0,
|
|
915
|
+
rankHintsFor: hintProvider ? (element) => rankFor(hintProvider, opts, element, POPUP_HINT_DISPLAY_MAX) : null
|
|
766
916
|
});
|
|
767
917
|
const widget = new WidgetController({
|
|
768
918
|
...opts,
|
|
@@ -777,6 +927,13 @@ function createWebWidget(opts) {
|
|
|
777
927
|
}
|
|
778
928
|
});
|
|
779
929
|
widgetRef = widget;
|
|
930
|
+
const originalOpenReport = widget.openReport.bind(widget);
|
|
931
|
+
widget.openReport = async (openOpts = {}) => {
|
|
932
|
+
if (hintProvider) {
|
|
933
|
+
widget.modal.setHints(rankFor(hintProvider, opts, openOpts.element ?? null));
|
|
934
|
+
}
|
|
935
|
+
await originalOpenReport(openOpts);
|
|
936
|
+
};
|
|
780
937
|
const originalSubmit = widget.submitReport.bind(widget);
|
|
781
938
|
widget.submitReport = async () => {
|
|
782
939
|
const outcome = await originalSubmit();
|
|
@@ -801,6 +958,7 @@ import {
|
|
|
801
958
|
COMMENT_MAX_CHARS as COMMENT_MAX_CHARS2,
|
|
802
959
|
FLOATING_BUTTON_ID,
|
|
803
960
|
MODAL_ACTION_CANCEL,
|
|
961
|
+
MODAL_ACTION_HINT_TOGGLE,
|
|
804
962
|
MODAL_ACTION_PICK,
|
|
805
963
|
MODAL_ACTION_REMOVE_SCREENSHOT,
|
|
806
964
|
MODAL_ACTION_RETRY,
|
|
@@ -815,7 +973,7 @@ import {
|
|
|
815
973
|
} from "react";
|
|
816
974
|
|
|
817
975
|
// src/version.ts
|
|
818
|
-
var VERSION = true ? "0.
|
|
976
|
+
var VERSION = true ? "0.6.0" : "dev";
|
|
819
977
|
|
|
820
978
|
// src/feedback-kit.tsx
|
|
821
979
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -973,6 +1131,96 @@ var inputStyle = {
|
|
|
973
1131
|
padding: "10px 11px",
|
|
974
1132
|
font: "inherit"
|
|
975
1133
|
};
|
|
1134
|
+
function PickToggle({
|
|
1135
|
+
active,
|
|
1136
|
+
disabled,
|
|
1137
|
+
variant,
|
|
1138
|
+
focusId,
|
|
1139
|
+
onToggle
|
|
1140
|
+
}) {
|
|
1141
|
+
const floating = variant === "floating";
|
|
1142
|
+
return /* @__PURE__ */ jsxs(
|
|
1143
|
+
"button",
|
|
1144
|
+
{
|
|
1145
|
+
type: "button",
|
|
1146
|
+
role: "switch",
|
|
1147
|
+
"aria-checked": active,
|
|
1148
|
+
"aria-label": "\uC694\uC18C \uC9C0\uBAA9 \uBAA8\uB4DC",
|
|
1149
|
+
"data-fk-pick-toggle": active ? "on" : "off",
|
|
1150
|
+
"data-fk-focus-id": focusId,
|
|
1151
|
+
disabled,
|
|
1152
|
+
onClick: onToggle,
|
|
1153
|
+
style: {
|
|
1154
|
+
...baseButton,
|
|
1155
|
+
display: "flex",
|
|
1156
|
+
alignItems: "center",
|
|
1157
|
+
gap: 10,
|
|
1158
|
+
opacity: disabled ? 0.5 : 1,
|
|
1159
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
1160
|
+
// 항상 값을 준다. 조건부로 빼면 리렌더 때 shorthand(border)와 충돌한다고 React 가 경고한다.
|
|
1161
|
+
borderColor: active ? TOKENS.accent : TOKENS.line,
|
|
1162
|
+
...floating ? {
|
|
1163
|
+
position: "fixed",
|
|
1164
|
+
top: 16,
|
|
1165
|
+
left: "50%",
|
|
1166
|
+
transform: "translateX(-50%)",
|
|
1167
|
+
// 오버레이 자체는 클릭을 통과시킨다(pointerEvents:none) — 이 토글만 되살린다.
|
|
1168
|
+
pointerEvents: "auto",
|
|
1169
|
+
background: TOKENS.surface,
|
|
1170
|
+
boxShadow: TOKENS.shadow
|
|
1171
|
+
} : {
|
|
1172
|
+
width: "100%",
|
|
1173
|
+
justifyContent: "space-between",
|
|
1174
|
+
marginBottom: 16,
|
|
1175
|
+
background: active ? "rgba(49, 93, 115, 0.06)" : TOKENS.surface
|
|
1176
|
+
}
|
|
1177
|
+
},
|
|
1178
|
+
children: [
|
|
1179
|
+
/* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 7 }, children: [
|
|
1180
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { color: active ? TOKENS.accent : TOKENS.muted }, children: "\u25CE" }),
|
|
1181
|
+
"\uC694\uC18C \uC9C0\uBAA9 \uBAA8\uB4DC"
|
|
1182
|
+
] }),
|
|
1183
|
+
/* @__PURE__ */ jsxs("span", { "aria-hidden": "true", style: { display: "inline-flex", alignItems: "center", gap: 8 }, children: [
|
|
1184
|
+
/* @__PURE__ */ jsx(
|
|
1185
|
+
"span",
|
|
1186
|
+
{
|
|
1187
|
+
style: { fontSize: 12, fontWeight: 600, color: active ? TOKENS.accent : TOKENS.muted },
|
|
1188
|
+
children: active ? "\uCF1C\uC9D0" : "\uAEBC\uC9D0"
|
|
1189
|
+
}
|
|
1190
|
+
),
|
|
1191
|
+
/* @__PURE__ */ jsx(
|
|
1192
|
+
"span",
|
|
1193
|
+
{
|
|
1194
|
+
style: {
|
|
1195
|
+
position: "relative",
|
|
1196
|
+
display: "inline-block",
|
|
1197
|
+
width: 38,
|
|
1198
|
+
height: 22,
|
|
1199
|
+
borderRadius: 11,
|
|
1200
|
+
background: active ? TOKENS.accent : TOKENS.line
|
|
1201
|
+
},
|
|
1202
|
+
children: /* @__PURE__ */ jsx(
|
|
1203
|
+
"span",
|
|
1204
|
+
{
|
|
1205
|
+
style: {
|
|
1206
|
+
position: "absolute",
|
|
1207
|
+
top: 3,
|
|
1208
|
+
left: active ? 19 : 3,
|
|
1209
|
+
width: 16,
|
|
1210
|
+
height: 16,
|
|
1211
|
+
borderRadius: "50%",
|
|
1212
|
+
background: TOKENS.surface,
|
|
1213
|
+
boxShadow: "0 1px 3px rgba(23, 32, 42, 0.35)"
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
)
|
|
1217
|
+
}
|
|
1218
|
+
)
|
|
1219
|
+
] })
|
|
1220
|
+
]
|
|
1221
|
+
}
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
976
1224
|
function MarkerStatus({ status }) {
|
|
977
1225
|
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 };
|
|
978
1226
|
return /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", gap: 4, alignItems: "center", color: presentation.color }, children: [
|
|
@@ -987,6 +1235,69 @@ function MarkerStatus({ status }) {
|
|
|
987
1235
|
/* @__PURE__ */ jsx("span", { children: presentation.text })
|
|
988
1236
|
] });
|
|
989
1237
|
}
|
|
1238
|
+
function HintChips({
|
|
1239
|
+
hints,
|
|
1240
|
+
hintsExpanded,
|
|
1241
|
+
usedHintIds,
|
|
1242
|
+
disabled,
|
|
1243
|
+
onApply,
|
|
1244
|
+
onToggle
|
|
1245
|
+
}) {
|
|
1246
|
+
return /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
|
|
1247
|
+
hintsExpanded ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1248
|
+
/* @__PURE__ */ jsx("p", { style: { margin: "0 0 7px", color: TOKENS.muted, fontSize: 12 }, children: "\uC774\uB7F0 \uAC74\uAC00\uC694?" }),
|
|
1249
|
+
/* @__PURE__ */ jsx(
|
|
1250
|
+
"div",
|
|
1251
|
+
{
|
|
1252
|
+
role: "group",
|
|
1253
|
+
"aria-label": "\uC790\uC8FC \uB098\uC624\uB294 \uC81C\uBCF4 \uC81C\uC548",
|
|
1254
|
+
style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 7 },
|
|
1255
|
+
children: hints.map((hint) => {
|
|
1256
|
+
const used = usedHintIds.includes(hint.id);
|
|
1257
|
+
return /* @__PURE__ */ jsx(
|
|
1258
|
+
"button",
|
|
1259
|
+
{
|
|
1260
|
+
type: "button",
|
|
1261
|
+
"data-fk-focus-id": `hint:${hint.id}`,
|
|
1262
|
+
disabled: used || disabled,
|
|
1263
|
+
"aria-pressed": used,
|
|
1264
|
+
onClick: () => onApply(hint.id),
|
|
1265
|
+
style: {
|
|
1266
|
+
...baseButton,
|
|
1267
|
+
minHeight: 30,
|
|
1268
|
+
padding: "5px 10px",
|
|
1269
|
+
fontSize: 12,
|
|
1270
|
+
fontWeight: 600,
|
|
1271
|
+
borderRadius: 999,
|
|
1272
|
+
...used ? {
|
|
1273
|
+
background: TOKENS.subtle,
|
|
1274
|
+
color: TOKENS.muted,
|
|
1275
|
+
borderColor: TOKENS.line,
|
|
1276
|
+
cursor: "default"
|
|
1277
|
+
} : {}
|
|
1278
|
+
},
|
|
1279
|
+
children: hint.label
|
|
1280
|
+
},
|
|
1281
|
+
hint.id
|
|
1282
|
+
);
|
|
1283
|
+
})
|
|
1284
|
+
}
|
|
1285
|
+
)
|
|
1286
|
+
] }) : null,
|
|
1287
|
+
/* @__PURE__ */ jsx(
|
|
1288
|
+
"button",
|
|
1289
|
+
{
|
|
1290
|
+
type: "button",
|
|
1291
|
+
"data-fk-focus-id": MODAL_ACTION_HINT_TOGGLE,
|
|
1292
|
+
"aria-expanded": hintsExpanded,
|
|
1293
|
+
disabled,
|
|
1294
|
+
onClick: onToggle,
|
|
1295
|
+
style: { ...baseButton, minHeight: 28, padding: "4px 10px", fontSize: 12 },
|
|
1296
|
+
children: hintsExpanded ? "\uC81C\uC548 \u25B4" : "\uC81C\uC548 \u25BE"
|
|
1297
|
+
}
|
|
1298
|
+
)
|
|
1299
|
+
] });
|
|
1300
|
+
}
|
|
990
1301
|
function FeedbackKit(props) {
|
|
991
1302
|
const rootRef = useRef(null);
|
|
992
1303
|
const floatingButtonRef = useRef(null);
|
|
@@ -1009,7 +1320,10 @@ function FeedbackKit(props) {
|
|
|
1009
1320
|
store,
|
|
1010
1321
|
getPathname,
|
|
1011
1322
|
doc,
|
|
1012
|
-
getViewport
|
|
1323
|
+
getViewport,
|
|
1324
|
+
pickTarget,
|
|
1325
|
+
hints,
|
|
1326
|
+
hintProvider
|
|
1013
1327
|
} = props;
|
|
1014
1328
|
useEffect(() => {
|
|
1015
1329
|
const created = createWebWidget({
|
|
@@ -1021,7 +1335,10 @@ function FeedbackKit(props) {
|
|
|
1021
1335
|
store,
|
|
1022
1336
|
getPathname,
|
|
1023
1337
|
doc,
|
|
1024
|
-
getViewport
|
|
1338
|
+
getViewport,
|
|
1339
|
+
pickTarget,
|
|
1340
|
+
hints,
|
|
1341
|
+
hintProvider
|
|
1025
1342
|
});
|
|
1026
1343
|
setKit(created);
|
|
1027
1344
|
const unsubscribeWidget = created.widget.subscribe(setWidgetState);
|
|
@@ -1039,6 +1356,9 @@ function FeedbackKit(props) {
|
|
|
1039
1356
|
screenshotLimitBytes,
|
|
1040
1357
|
store,
|
|
1041
1358
|
getPathname,
|
|
1359
|
+
pickTarget,
|
|
1360
|
+
hints,
|
|
1361
|
+
hintProvider,
|
|
1042
1362
|
doc,
|
|
1043
1363
|
getViewport
|
|
1044
1364
|
]);
|
|
@@ -1081,8 +1401,12 @@ function FeedbackKit(props) {
|
|
|
1081
1401
|
return /* @__PURE__ */ jsx("div", { ...OWN_UI_PROPS, "data-feedback-kit-loading": "true" });
|
|
1082
1402
|
}
|
|
1083
1403
|
const activeKit = kit;
|
|
1084
|
-
const { modal, screen } = widgetState;
|
|
1404
|
+
const { modal, screen, pickingActive } = widgetState;
|
|
1085
1405
|
const draftLocked = modal.submitStatus === "sending" || modal.submitStatus === "pending";
|
|
1406
|
+
function togglePicking() {
|
|
1407
|
+
if (pickingActive) activeKit.widget.stopPicking();
|
|
1408
|
+
else activeKit.widget.startPicking();
|
|
1409
|
+
}
|
|
1086
1410
|
async function withOwnUiHidden(action) {
|
|
1087
1411
|
const root = rootRef.current;
|
|
1088
1412
|
const previousVisibility = root?.style.visibility ?? "";
|
|
@@ -1154,8 +1478,7 @@ function FeedbackKit(props) {
|
|
|
1154
1478
|
ref: floatingButtonRef,
|
|
1155
1479
|
id: FLOATING_BUTTON_ID,
|
|
1156
1480
|
type: "button",
|
|
1157
|
-
|
|
1158
|
-
"aria-label": screen === "picking" ? "\uC694\uC18C \uC9C0\uBAA9 \uC911" : "\uD53C\uB4DC\uBC31 \uBCF4\uB0B4\uAE30",
|
|
1481
|
+
"aria-label": "\uD53C\uB4DC\uBC31 \uBCF4\uB0B4\uAE30",
|
|
1159
1482
|
onClick: () => void openReport(),
|
|
1160
1483
|
style: {
|
|
1161
1484
|
...primaryButton,
|
|
@@ -1166,11 +1489,9 @@ function FeedbackKit(props) {
|
|
|
1166
1489
|
minWidth: 112,
|
|
1167
1490
|
minHeight: 46,
|
|
1168
1491
|
borderRadius: 24,
|
|
1169
|
-
boxShadow: TOKENS.shadow
|
|
1170
|
-
opacity: screen === "picking" ? 0.76 : 1,
|
|
1171
|
-
cursor: screen === "picking" ? "default" : "pointer"
|
|
1492
|
+
boxShadow: TOKENS.shadow
|
|
1172
1493
|
},
|
|
1173
|
-
children:
|
|
1494
|
+
children: "\uD53C\uB4DC\uBC31"
|
|
1174
1495
|
}
|
|
1175
1496
|
) : null,
|
|
1176
1497
|
announcement && !modal.open ? /* @__PURE__ */ jsx(
|
|
@@ -1254,7 +1575,17 @@ function FeedbackKit(props) {
|
|
|
1254
1575
|
]
|
|
1255
1576
|
}
|
|
1256
1577
|
),
|
|
1257
|
-
/* @__PURE__ */ jsx("p", { style: { margin: "0 0
|
|
1578
|
+
/* @__PURE__ */ 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." }),
|
|
1579
|
+
/* @__PURE__ */ jsx(
|
|
1580
|
+
PickToggle,
|
|
1581
|
+
{
|
|
1582
|
+
variant: "inline",
|
|
1583
|
+
active: pickingActive,
|
|
1584
|
+
disabled: draftLocked,
|
|
1585
|
+
focusId: MODAL_ACTION_PICK,
|
|
1586
|
+
onToggle: togglePicking
|
|
1587
|
+
}
|
|
1588
|
+
),
|
|
1258
1589
|
/* @__PURE__ */ jsxs("section", { "aria-labelledby": "feedback-kit-screenshot-label", style: { marginBottom: 16 }, children: [
|
|
1259
1590
|
/* @__PURE__ */ jsx("strong", { id: "feedback-kit-screenshot-label", children: "\uC2A4\uD06C\uB9B0\uC0F7" }),
|
|
1260
1591
|
/* @__PURE__ */ jsx(
|
|
@@ -1326,6 +1657,17 @@ function FeedbackKit(props) {
|
|
|
1326
1657
|
)
|
|
1327
1658
|
] })
|
|
1328
1659
|
] }),
|
|
1660
|
+
modal.hintsAvailable ? /* @__PURE__ */ jsx(
|
|
1661
|
+
HintChips,
|
|
1662
|
+
{
|
|
1663
|
+
hints: modal.hints,
|
|
1664
|
+
hintsExpanded: modal.hintsExpanded,
|
|
1665
|
+
usedHintIds: modal.usedHintIds,
|
|
1666
|
+
disabled: draftLocked,
|
|
1667
|
+
onApply: (hintId) => kit.widget.modal.applyHint(hintId),
|
|
1668
|
+
onToggle: () => kit.widget.modal.toggleHints()
|
|
1669
|
+
}
|
|
1670
|
+
) : null,
|
|
1329
1671
|
/* @__PURE__ */ jsxs("label", { htmlFor: "feedback-kit-comment", style: { display: "block", fontWeight: 700 }, children: [
|
|
1330
1672
|
"\uCF54\uBA58\uD2B8 ",
|
|
1331
1673
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "*" })
|
|
@@ -1368,19 +1710,6 @@ function FeedbackKit(props) {
|
|
|
1368
1710
|
]
|
|
1369
1711
|
}
|
|
1370
1712
|
),
|
|
1371
|
-
/* @__PURE__ */ jsx(
|
|
1372
|
-
"button",
|
|
1373
|
-
{
|
|
1374
|
-
type: "button",
|
|
1375
|
-
role: "switch",
|
|
1376
|
-
"aria-checked": "false",
|
|
1377
|
-
disabled: draftLocked,
|
|
1378
|
-
"data-fk-focus-id": MODAL_ACTION_PICK,
|
|
1379
|
-
onClick: () => kit.widget.startPicking(),
|
|
1380
|
-
style: { ...baseButton, width: "100%", marginTop: 16 },
|
|
1381
|
-
children: "\uC694\uC18C \uC9C0\uBAA9"
|
|
1382
|
-
}
|
|
1383
|
-
),
|
|
1384
1713
|
modal.submitStatus !== "idle" || modal.pending > 0 ? /* @__PURE__ */ jsxs(
|
|
1385
1714
|
"div",
|
|
1386
1715
|
{
|
|
@@ -1503,22 +1832,7 @@ function FeedbackKit(props) {
|
|
|
1503
1832
|
}
|
|
1504
1833
|
}
|
|
1505
1834
|
) : null,
|
|
1506
|
-
/* @__PURE__ */ jsx(
|
|
1507
|
-
"button",
|
|
1508
|
-
{
|
|
1509
|
-
type: "button",
|
|
1510
|
-
onClick: () => kit.widget.stopPicking(),
|
|
1511
|
-
style: {
|
|
1512
|
-
...primaryButton,
|
|
1513
|
-
position: "fixed",
|
|
1514
|
-
top: 18,
|
|
1515
|
-
right: 18,
|
|
1516
|
-
pointerEvents: "auto",
|
|
1517
|
-
boxShadow: TOKENS.shadow
|
|
1518
|
-
},
|
|
1519
|
-
children: "\uC9C0\uBAA9 \uC885\uB8CC"
|
|
1520
|
-
}
|
|
1521
|
-
),
|
|
1835
|
+
/* @__PURE__ */ jsx(PickToggle, { variant: "floating", active: true, onToggle: togglePicking }),
|
|
1522
1836
|
pickingState.markers.map((marker, index) => /* @__PURE__ */ jsx(
|
|
1523
1837
|
"div",
|
|
1524
1838
|
{
|
|
@@ -1610,6 +1924,17 @@ function FeedbackKit(props) {
|
|
|
1610
1924
|
]
|
|
1611
1925
|
}
|
|
1612
1926
|
),
|
|
1927
|
+
popup.hintsAvailable ? /* @__PURE__ */ jsx(
|
|
1928
|
+
HintChips,
|
|
1929
|
+
{
|
|
1930
|
+
hints: popup.hints,
|
|
1931
|
+
hintsExpanded: popup.hintsExpanded,
|
|
1932
|
+
usedHintIds: popup.usedHintIds,
|
|
1933
|
+
disabled: popup.saving,
|
|
1934
|
+
onApply: (hintId) => kit.picking.applyHint(hintId),
|
|
1935
|
+
onToggle: () => kit.picking.toggleHints()
|
|
1936
|
+
}
|
|
1937
|
+
) : null,
|
|
1613
1938
|
/* @__PURE__ */ jsx("label", { htmlFor: "feedback-kit-annotation", style: visuallyHidden, children: "\uC774 \uC694\uC18C\uC5D0 \uB0A8\uAE38 \uC758\uACAC" }),
|
|
1614
1939
|
/* @__PURE__ */ jsx(
|
|
1615
1940
|
"textarea",
|
|
@@ -1710,49 +2035,6 @@ function FeedbackKit(props) {
|
|
|
1710
2035
|
);
|
|
1711
2036
|
}
|
|
1712
2037
|
|
|
1713
|
-
// src/storage.ts
|
|
1714
|
-
function memoryStorage() {
|
|
1715
|
-
const map = /* @__PURE__ */ new Map();
|
|
1716
|
-
return {
|
|
1717
|
-
getItem: (key) => map.get(key) ?? null,
|
|
1718
|
-
setItem: (key, value) => void map.set(key, value),
|
|
1719
|
-
removeItem: (key) => void map.delete(key)
|
|
1720
|
-
};
|
|
1721
|
-
}
|
|
1722
|
-
function pickBacking() {
|
|
1723
|
-
try {
|
|
1724
|
-
const ls = globalThis.localStorage;
|
|
1725
|
-
if (!ls) return memoryStorage();
|
|
1726
|
-
const probe = "__feedback_kit_probe__";
|
|
1727
|
-
ls.setItem(probe, "1");
|
|
1728
|
-
ls.removeItem(probe);
|
|
1729
|
-
return ls;
|
|
1730
|
-
} catch {
|
|
1731
|
-
return memoryStorage();
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
function createWebStorage(backing) {
|
|
1735
|
-
const store = backing ?? pickBacking();
|
|
1736
|
-
return {
|
|
1737
|
-
async get(key) {
|
|
1738
|
-
try {
|
|
1739
|
-
return store.getItem(key);
|
|
1740
|
-
} catch {
|
|
1741
|
-
return null;
|
|
1742
|
-
}
|
|
1743
|
-
},
|
|
1744
|
-
async set(key, value) {
|
|
1745
|
-
store.setItem(key, value);
|
|
1746
|
-
},
|
|
1747
|
-
async remove(key) {
|
|
1748
|
-
try {
|
|
1749
|
-
store.removeItem(key);
|
|
1750
|
-
} catch {
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
};
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
2038
|
// src/providers.ts
|
|
1757
2039
|
function webContextProviders() {
|
|
1758
2040
|
return {
|
|
@@ -1782,8 +2064,10 @@ export {
|
|
|
1782
2064
|
ElementPickingController,
|
|
1783
2065
|
FLOATING_BUTTON_ID2 as FLOATING_BUTTON_ID,
|
|
1784
2066
|
FeedbackKit,
|
|
2067
|
+
HintProvider2 as HintProvider,
|
|
1785
2068
|
MARKER_KEY_PREFIX,
|
|
1786
2069
|
MAX_MARKERS_PER_PATH,
|
|
2070
|
+
MODAL_ACTION_HINT_TOGGLE2 as MODAL_ACTION_HINT_TOGGLE,
|
|
1787
2071
|
MODAL_ACTION_PICK2 as MODAL_ACTION_PICK,
|
|
1788
2072
|
MarkerStore,
|
|
1789
2073
|
OWN_UI_ATTR,
|
|
@@ -1804,6 +2088,7 @@ export {
|
|
|
1804
2088
|
isOwnUi2 as isOwnUi,
|
|
1805
2089
|
normalizePin2 as normalizePin,
|
|
1806
2090
|
parseSourceAttr,
|
|
2091
|
+
rankHints2 as rankHints,
|
|
1807
2092
|
reactComponentPath,
|
|
1808
2093
|
reactComponentSummary,
|
|
1809
2094
|
reencodeWebScreenshot,
|