@solhun/feedback-kit-web 0.6.0 → 0.7.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 +228 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -2
- package/dist/index.d.ts +33 -2
- package/dist/index.js +228 -69
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -375,6 +375,12 @@ var ElementPickingController = class {
|
|
|
375
375
|
constructor(opts) {
|
|
376
376
|
this.listeners = /* @__PURE__ */ new Set();
|
|
377
377
|
this.active = false;
|
|
378
|
+
/**
|
|
379
|
+
* 일시중지. **저장하지 않는다** — 멈추는 목적이 대개 "링크를 눌러 다른 화면으로 가는 것"
|
|
380
|
+
* 이라, 그 이동이 끝나면 지목이 돌아와 있는 게 목적에 맞다. 같은 이유로 경로가 바뀌면
|
|
381
|
+
* 스스로 풀린다(`syncPath`) — 그래야 SPA 이동과 새로고침이 같게 동작한다.
|
|
382
|
+
*/
|
|
383
|
+
this.paused = false;
|
|
378
384
|
this.attached = false;
|
|
379
385
|
this.hovered = null;
|
|
380
386
|
this.popup = null;
|
|
@@ -386,7 +392,7 @@ var ElementPickingController = class {
|
|
|
386
392
|
/** 지금 도는 자동 캡처. Enter 가 캡처보다 빨랐을 때 기다릴 대상. */
|
|
387
393
|
this.capturing = null;
|
|
388
394
|
/**
|
|
389
|
-
* 사용자가 [
|
|
395
|
+
* 사용자가 [이런 건가요? ▾] 를 직접 건드렸는가. 한 번이라도 건드리면 이후 타이핑에 의한 자동
|
|
390
396
|
* 접힘/펼침이 멈춘다 — 모달의 `userToggledHints` 와 같은 규칙. 팝업을 새로 열 때마다 리셋된다.
|
|
391
397
|
*/
|
|
392
398
|
this.userToggledHints = false;
|
|
@@ -413,6 +419,7 @@ var ElementPickingController = class {
|
|
|
413
419
|
getState() {
|
|
414
420
|
return {
|
|
415
421
|
active: this.active,
|
|
422
|
+
paused: this.paused,
|
|
416
423
|
hovered: this.hovered,
|
|
417
424
|
popup: this.popup,
|
|
418
425
|
markers: this.markers
|
|
@@ -421,6 +428,10 @@ var ElementPickingController = class {
|
|
|
421
428
|
get isActive() {
|
|
422
429
|
return this.active;
|
|
423
430
|
}
|
|
431
|
+
/** 켜져 있지만 클릭을 안 먹는 상태인가. */
|
|
432
|
+
get isPaused() {
|
|
433
|
+
return this.paused;
|
|
434
|
+
}
|
|
424
435
|
subscribe(listener) {
|
|
425
436
|
this.listeners.add(listener);
|
|
426
437
|
listener(this.getState());
|
|
@@ -432,6 +443,7 @@ var ElementPickingController = class {
|
|
|
432
443
|
start() {
|
|
433
444
|
if (this.active) return;
|
|
434
445
|
this.active = true;
|
|
446
|
+
this.paused = false;
|
|
435
447
|
this.store.setPickingActive(true);
|
|
436
448
|
this.attach();
|
|
437
449
|
this.startPathWatch();
|
|
@@ -441,6 +453,7 @@ var ElementPickingController = class {
|
|
|
441
453
|
/** [지목 종료]. 저장된 플래그까지 지워서 새로고침해도 다시 켜지지 않게 한다. */
|
|
442
454
|
stop() {
|
|
443
455
|
this.active = false;
|
|
456
|
+
this.paused = false;
|
|
444
457
|
this.store.setPickingActive(false);
|
|
445
458
|
this.detach();
|
|
446
459
|
this.stopPathWatch();
|
|
@@ -448,6 +461,31 @@ var ElementPickingController = class {
|
|
|
448
461
|
this.popup = null;
|
|
449
462
|
this.emit();
|
|
450
463
|
}
|
|
464
|
+
/**
|
|
465
|
+
* 잠시 멈춘다 — 페이지 클릭이 원래대로 동작하고, 모드는 켜진 채로 남는다.
|
|
466
|
+
*
|
|
467
|
+
* 팝업이 열려 있으면 **그대로 둔다.** 쓰던 한 줄을 여기서 지우면 「지목 켜면 모달 초안이
|
|
468
|
+
* 날아간다」와 같은 사고를 다른 자리에 만드는 셈이다. 팝업은 위젯 자신의 UI 라
|
|
469
|
+
* 리스너를 떼도 계속 눌린다.
|
|
470
|
+
*/
|
|
471
|
+
pause() {
|
|
472
|
+
if (!this.active || this.paused) return;
|
|
473
|
+
this.paused = true;
|
|
474
|
+
this.detach();
|
|
475
|
+
this.hovered = null;
|
|
476
|
+
this.emit();
|
|
477
|
+
}
|
|
478
|
+
/** 다시 지목을 받는다. */
|
|
479
|
+
resume() {
|
|
480
|
+
if (!this.active || !this.paused) return;
|
|
481
|
+
this.paused = false;
|
|
482
|
+
this.attach();
|
|
483
|
+
this.emit();
|
|
484
|
+
}
|
|
485
|
+
togglePause() {
|
|
486
|
+
if (this.paused) this.resume();
|
|
487
|
+
else this.pause();
|
|
488
|
+
}
|
|
451
489
|
/**
|
|
452
490
|
* 저장돼 있던 모드를 되살린다. 새로고침·페이지 이동 직후에 한 번 부른다.
|
|
453
491
|
* @returns 되살아났으면 true.
|
|
@@ -463,6 +501,7 @@ var ElementPickingController = class {
|
|
|
463
501
|
}
|
|
464
502
|
/** 경로가 바뀌었을 때 그 경로의 마커로 갈아 끼운다. */
|
|
465
503
|
syncPath() {
|
|
504
|
+
if (this.paused) this.resume();
|
|
466
505
|
this.lastPathname = this.getPathname();
|
|
467
506
|
this.markers = this.store.list(this.lastPathname);
|
|
468
507
|
this.reconcileMarkerOutcomes();
|
|
@@ -491,12 +530,12 @@ var ElementPickingController = class {
|
|
|
491
530
|
this.attached = false;
|
|
492
531
|
}
|
|
493
532
|
handleMouseOver(event) {
|
|
494
|
-
if (!this.active || isOwnUi(event.target)) return;
|
|
533
|
+
if (!this.active || this.paused || isOwnUi(event.target)) return;
|
|
495
534
|
this.hovered = describeElement(event.target);
|
|
496
535
|
this.emit();
|
|
497
536
|
}
|
|
498
537
|
handleClick(event) {
|
|
499
|
-
if (!this.active) return;
|
|
538
|
+
if (!this.active || this.paused) return;
|
|
500
539
|
if (isOwnUi(event.target)) return;
|
|
501
540
|
event.preventDefault();
|
|
502
541
|
event.stopPropagation();
|
|
@@ -577,7 +616,7 @@ ${hint.draft}`;
|
|
|
577
616
|
};
|
|
578
617
|
this.emit();
|
|
579
618
|
}
|
|
580
|
-
/** [
|
|
619
|
+
/** [이런 건가요? ▾] 토글. 방향과 무관하게 이후 자동 접힘/펼침을 멈춘다. */
|
|
581
620
|
toggleHints() {
|
|
582
621
|
if (!this.popup) return;
|
|
583
622
|
this.userToggledHints = true;
|
|
@@ -1009,7 +1048,7 @@ var import_feedback_kit_core3 = require("@solhun/feedback-kit-core");
|
|
|
1009
1048
|
var import_react = require("react");
|
|
1010
1049
|
|
|
1011
1050
|
// src/version.ts
|
|
1012
|
-
var VERSION = true ? "0.
|
|
1051
|
+
var VERSION = true ? "0.7.0" : "dev";
|
|
1013
1052
|
|
|
1014
1053
|
// src/feedback-kit.tsx
|
|
1015
1054
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -1031,6 +1070,7 @@ var OWN_UI_PROPS = { [OWN_UI_ATTR]: "" };
|
|
|
1031
1070
|
var FONT_STACK = "ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
1032
1071
|
var INITIAL_PICKING_STATE = {
|
|
1033
1072
|
active: false,
|
|
1073
|
+
paused: false,
|
|
1034
1074
|
hovered: null,
|
|
1035
1075
|
popup: null,
|
|
1036
1076
|
markers: []
|
|
@@ -1072,26 +1112,41 @@ async function transcodeToJpeg(dataUrl) {
|
|
|
1072
1112
|
return null;
|
|
1073
1113
|
}
|
|
1074
1114
|
}
|
|
1115
|
+
function looksLikeImageName(name) {
|
|
1116
|
+
return /\.(png|jpe?g|gif|webp|bmp|avif|heic|heif)$/i.test(name);
|
|
1117
|
+
}
|
|
1118
|
+
function isImageFile(file) {
|
|
1119
|
+
if (file.type.startsWith("image/")) return true;
|
|
1120
|
+
return (file.type === "" || file.type === "application/octet-stream") && looksLikeImageName(file.name);
|
|
1121
|
+
}
|
|
1122
|
+
function contentTypeOf(file) {
|
|
1123
|
+
if (file.type !== "") return file.type;
|
|
1124
|
+
const name = file instanceof File ? file.name : "";
|
|
1125
|
+
if (/\.png$/i.test(name)) return "image/png";
|
|
1126
|
+
if (/\.jpe?g$/i.test(name)) return "image/jpeg";
|
|
1127
|
+
return "";
|
|
1128
|
+
}
|
|
1075
1129
|
async function readImageFile(file) {
|
|
1076
|
-
|
|
1130
|
+
const named = file instanceof File ? file : null;
|
|
1131
|
+
if (!file.type.startsWith("image/") && !(named !== null && isImageFile(named))) return null;
|
|
1077
1132
|
const dataUrl = await readDataUrl(file);
|
|
1078
1133
|
if (!dataUrl) return null;
|
|
1079
|
-
|
|
1134
|
+
const type = contentTypeOf(file);
|
|
1135
|
+
if (type === "image/png" || type === "image/jpeg") {
|
|
1080
1136
|
const base64 = base64Of(dataUrl);
|
|
1081
|
-
return base64 ? { base64, contentType:
|
|
1137
|
+
return base64 ? { base64, contentType: type } : null;
|
|
1082
1138
|
}
|
|
1083
1139
|
return transcodeToJpeg(dataUrl);
|
|
1084
1140
|
}
|
|
1085
1141
|
function firstImageOf(transfer) {
|
|
1086
1142
|
if (!transfer) return null;
|
|
1087
1143
|
for (const item of Array.from(transfer.items ?? [])) {
|
|
1088
|
-
if (item.kind
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
}
|
|
1144
|
+
if (item.kind !== "file") continue;
|
|
1145
|
+
const file = item.getAsFile();
|
|
1146
|
+
if (file && isImageFile(file)) return file;
|
|
1092
1147
|
}
|
|
1093
1148
|
for (const file of Array.from(transfer.files ?? [])) {
|
|
1094
|
-
if (file
|
|
1149
|
+
if (isImageFile(file)) return file;
|
|
1095
1150
|
}
|
|
1096
1151
|
return null;
|
|
1097
1152
|
}
|
|
@@ -1169,6 +1224,7 @@ var inputStyle = {
|
|
|
1169
1224
|
};
|
|
1170
1225
|
function PickToggle({
|
|
1171
1226
|
active,
|
|
1227
|
+
paused,
|
|
1172
1228
|
disabled,
|
|
1173
1229
|
variant,
|
|
1174
1230
|
focusId,
|
|
@@ -1196,14 +1252,9 @@ function PickToggle({
|
|
|
1196
1252
|
// 항상 값을 준다. 조건부로 빼면 리렌더 때 shorthand(border)와 충돌한다고 React 가 경고한다.
|
|
1197
1253
|
borderColor: active ? TOKENS.accent : TOKENS.line,
|
|
1198
1254
|
...floating ? {
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
transform: "translateX(-50%)",
|
|
1203
|
-
// 오버레이 자체는 클릭을 통과시킨다(pointerEvents:none) — 이 토글만 되살린다.
|
|
1204
|
-
pointerEvents: "auto",
|
|
1205
|
-
background: TOKENS.surface,
|
|
1206
|
-
boxShadow: TOKENS.shadow
|
|
1255
|
+
// 위치는 감싸는 `PickBar` 가 잡는다 — 옆에 [잠시 멈춤] 이 함께 서야 하는데
|
|
1256
|
+
// 토글이 스스로 fixed 면 둘을 나란히 둘 수 없다.
|
|
1257
|
+
background: TOKENS.surface
|
|
1207
1258
|
} : {
|
|
1208
1259
|
width: "100%",
|
|
1209
1260
|
justifyContent: "space-between",
|
|
@@ -1220,8 +1271,12 @@ function PickToggle({
|
|
|
1220
1271
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1221
1272
|
"span",
|
|
1222
1273
|
{
|
|
1223
|
-
style: {
|
|
1224
|
-
|
|
1274
|
+
style: {
|
|
1275
|
+
fontSize: 12,
|
|
1276
|
+
fontWeight: 600,
|
|
1277
|
+
color: active && !paused ? TOKENS.accent : TOKENS.muted
|
|
1278
|
+
},
|
|
1279
|
+
children: active ? paused ? "\uC77C\uC2DC\uC911\uC9C0" : "\uCF1C\uC9D0" : "\uAEBC\uC9D0"
|
|
1225
1280
|
}
|
|
1226
1281
|
),
|
|
1227
1282
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -1233,7 +1288,7 @@ function PickToggle({
|
|
|
1233
1288
|
width: 38,
|
|
1234
1289
|
height: 22,
|
|
1235
1290
|
borderRadius: 11,
|
|
1236
|
-
background: active ? TOKENS.accent : TOKENS.line
|
|
1291
|
+
background: active ? paused ? TOKENS.muted : TOKENS.accent : TOKENS.line
|
|
1237
1292
|
},
|
|
1238
1293
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1239
1294
|
"span",
|
|
@@ -1257,6 +1312,58 @@ function PickToggle({
|
|
|
1257
1312
|
}
|
|
1258
1313
|
);
|
|
1259
1314
|
}
|
|
1315
|
+
function PickBar({
|
|
1316
|
+
paused,
|
|
1317
|
+
onToggle,
|
|
1318
|
+
onTogglePause
|
|
1319
|
+
}) {
|
|
1320
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1321
|
+
"div",
|
|
1322
|
+
{
|
|
1323
|
+
style: {
|
|
1324
|
+
position: "fixed",
|
|
1325
|
+
top: 16,
|
|
1326
|
+
left: "50%",
|
|
1327
|
+
transform: "translateX(-50%)",
|
|
1328
|
+
display: "flex",
|
|
1329
|
+
alignItems: "stretch",
|
|
1330
|
+
gap: 8,
|
|
1331
|
+
// 오버레이 자체는 클릭을 통과시킨다(pointerEvents:none) — 이 막대만 되살린다.
|
|
1332
|
+
pointerEvents: "auto",
|
|
1333
|
+
padding: 6,
|
|
1334
|
+
borderRadius: 12,
|
|
1335
|
+
background: TOKENS.surface,
|
|
1336
|
+
boxShadow: TOKENS.shadow
|
|
1337
|
+
},
|
|
1338
|
+
children: [
|
|
1339
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PickToggle, { variant: "floating", active: true, paused, onToggle }),
|
|
1340
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1341
|
+
"button",
|
|
1342
|
+
{
|
|
1343
|
+
type: "button",
|
|
1344
|
+
"aria-pressed": paused,
|
|
1345
|
+
"data-fk-pick-pause": paused ? "paused" : "running",
|
|
1346
|
+
onClick: onTogglePause,
|
|
1347
|
+
style: {
|
|
1348
|
+
...baseButton,
|
|
1349
|
+
display: "inline-flex",
|
|
1350
|
+
alignItems: "center",
|
|
1351
|
+
gap: 6,
|
|
1352
|
+
whiteSpace: "nowrap",
|
|
1353
|
+
borderColor: paused ? TOKENS.accent : TOKENS.line,
|
|
1354
|
+
color: paused ? TOKENS.accent : TOKENS.ink,
|
|
1355
|
+
fontWeight: 600
|
|
1356
|
+
},
|
|
1357
|
+
children: [
|
|
1358
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: paused ? "\u25B6" : "\u23F8" }),
|
|
1359
|
+
paused ? "\uC9C0\uBAA9 \uC7AC\uAC1C" : "\uC7A0\uC2DC \uBA48\uCDA4"
|
|
1360
|
+
]
|
|
1361
|
+
}
|
|
1362
|
+
)
|
|
1363
|
+
]
|
|
1364
|
+
}
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1260
1367
|
function MarkerStatus({ status }) {
|
|
1261
1368
|
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 };
|
|
1262
1369
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "inline-flex", gap: 4, alignItems: "center", color: presentation.color }, children: [
|
|
@@ -1280,47 +1387,7 @@ function HintChips({
|
|
|
1280
1387
|
onToggle
|
|
1281
1388
|
}) {
|
|
1282
1389
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 16 }, children: [
|
|
1283
|
-
|
|
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)(
|
|
1390
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1324
1391
|
"button",
|
|
1325
1392
|
{
|
|
1326
1393
|
type: "button",
|
|
@@ -1328,10 +1395,61 @@ function HintChips({
|
|
|
1328
1395
|
"aria-expanded": hintsExpanded,
|
|
1329
1396
|
disabled,
|
|
1330
1397
|
onClick: onToggle,
|
|
1331
|
-
style: {
|
|
1332
|
-
|
|
1398
|
+
style: {
|
|
1399
|
+
display: "block",
|
|
1400
|
+
margin: "0 0 7px",
|
|
1401
|
+
padding: 0,
|
|
1402
|
+
border: "none",
|
|
1403
|
+
background: "none",
|
|
1404
|
+
font: "inherit",
|
|
1405
|
+
fontSize: 12,
|
|
1406
|
+
color: TOKENS.muted,
|
|
1407
|
+
cursor: disabled ? "default" : "pointer",
|
|
1408
|
+
textAlign: "left"
|
|
1409
|
+
},
|
|
1410
|
+
children: [
|
|
1411
|
+
"\uC774\uB7F0 \uAC74\uAC00\uC694? ",
|
|
1412
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: hintsExpanded ? "\u25B4" : "\u25BE" })
|
|
1413
|
+
]
|
|
1333
1414
|
}
|
|
1334
|
-
)
|
|
1415
|
+
),
|
|
1416
|
+
hintsExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1417
|
+
"div",
|
|
1418
|
+
{
|
|
1419
|
+
role: "group",
|
|
1420
|
+
"aria-label": "\uC790\uC8FC \uB098\uC624\uB294 \uC81C\uBCF4 \uC81C\uC548",
|
|
1421
|
+
style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 7 },
|
|
1422
|
+
children: hints.map((hint) => {
|
|
1423
|
+
const used = usedHintIds.includes(hint.id);
|
|
1424
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1425
|
+
"button",
|
|
1426
|
+
{
|
|
1427
|
+
type: "button",
|
|
1428
|
+
"data-fk-focus-id": `hint:${hint.id}`,
|
|
1429
|
+
disabled: used || disabled,
|
|
1430
|
+
"aria-pressed": used,
|
|
1431
|
+
onClick: () => onApply(hint.id),
|
|
1432
|
+
style: {
|
|
1433
|
+
...baseButton,
|
|
1434
|
+
minHeight: 30,
|
|
1435
|
+
padding: "5px 10px",
|
|
1436
|
+
fontSize: 12,
|
|
1437
|
+
fontWeight: 600,
|
|
1438
|
+
borderRadius: 999,
|
|
1439
|
+
...used ? {
|
|
1440
|
+
background: TOKENS.subtle,
|
|
1441
|
+
color: TOKENS.muted,
|
|
1442
|
+
borderColor: TOKENS.line,
|
|
1443
|
+
cursor: "default"
|
|
1444
|
+
} : {}
|
|
1445
|
+
},
|
|
1446
|
+
children: hint.label
|
|
1447
|
+
},
|
|
1448
|
+
hint.id
|
|
1449
|
+
);
|
|
1450
|
+
})
|
|
1451
|
+
}
|
|
1452
|
+
) }) : null
|
|
1335
1453
|
] });
|
|
1336
1454
|
}
|
|
1337
1455
|
function FeedbackKit(props) {
|
|
@@ -1433,6 +1551,23 @@ function FeedbackKit(props) {
|
|
|
1433
1551
|
document.addEventListener("keydown", onKeyDownCapture, true);
|
|
1434
1552
|
return () => document.removeEventListener("keydown", onKeyDownCapture, true);
|
|
1435
1553
|
}, [kit, widgetState, pickingState.popup]);
|
|
1554
|
+
(0, import_react.useEffect)(() => {
|
|
1555
|
+
if (!kit || !widgetState?.modal.open) return;
|
|
1556
|
+
if (pickingState.popup) return;
|
|
1557
|
+
const locked = widgetState.modal.submitStatus === "sending" || widgetState.modal.submitStatus === "pending";
|
|
1558
|
+
if (locked) return;
|
|
1559
|
+
const controller = kit;
|
|
1560
|
+
async function onPaste(event) {
|
|
1561
|
+
const file = firstImageOf(event.clipboardData);
|
|
1562
|
+
if (!file) return;
|
|
1563
|
+
event.preventDefault();
|
|
1564
|
+
const shot = await readImageFile(file);
|
|
1565
|
+
if (shot) await controller.widget.modal.attachFile(shot);
|
|
1566
|
+
}
|
|
1567
|
+
const handler = (event) => void onPaste(event);
|
|
1568
|
+
document.addEventListener("paste", handler);
|
|
1569
|
+
return () => document.removeEventListener("paste", handler);
|
|
1570
|
+
}, [kit, widgetState, pickingState.popup]);
|
|
1436
1571
|
if (!kit || !widgetState) {
|
|
1437
1572
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ...OWN_UI_PROPS, "data-feedback-kit-loading": "true" });
|
|
1438
1573
|
}
|
|
@@ -1470,6 +1605,14 @@ function FeedbackKit(props) {
|
|
|
1470
1605
|
}
|
|
1471
1606
|
input.value = "";
|
|
1472
1607
|
}
|
|
1608
|
+
async function attachToModal(transfer) {
|
|
1609
|
+
const file = firstImageOf(transfer);
|
|
1610
|
+
if (!file) return false;
|
|
1611
|
+
const screenshot = await readImageFile(file);
|
|
1612
|
+
if (!screenshot) return false;
|
|
1613
|
+
await activeKit.widget.modal.attachFile(screenshot);
|
|
1614
|
+
return true;
|
|
1615
|
+
}
|
|
1473
1616
|
async function attachToPopup(transfer) {
|
|
1474
1617
|
const file = firstImageOf(transfer);
|
|
1475
1618
|
if (!file) return false;
|
|
@@ -1570,6 +1713,14 @@ function FeedbackKit(props) {
|
|
|
1570
1713
|
"aria-modal": "true",
|
|
1571
1714
|
"aria-labelledby": "feedback-kit-dialog-title",
|
|
1572
1715
|
onKeyDown: handleModalKeyDown,
|
|
1716
|
+
onDragOver: (event) => {
|
|
1717
|
+
if (!draftLocked && firstImageOf(event.dataTransfer)) event.preventDefault();
|
|
1718
|
+
},
|
|
1719
|
+
onDrop: (event) => {
|
|
1720
|
+
if (draftLocked || !firstImageOf(event.dataTransfer)) return;
|
|
1721
|
+
event.preventDefault();
|
|
1722
|
+
void attachToModal(event.dataTransfer);
|
|
1723
|
+
},
|
|
1573
1724
|
style: {
|
|
1574
1725
|
width: "min(520px, 100%)",
|
|
1575
1726
|
maxHeight: "min(760px, calc(100vh - 40px))",
|
|
@@ -1617,6 +1768,7 @@ function FeedbackKit(props) {
|
|
|
1617
1768
|
{
|
|
1618
1769
|
variant: "inline",
|
|
1619
1770
|
active: pickingActive,
|
|
1771
|
+
paused: pickingState.paused,
|
|
1620
1772
|
disabled: draftLocked,
|
|
1621
1773
|
focusId: import_feedback_kit_core3.MODAL_ACTION_PICK,
|
|
1622
1774
|
onToggle: togglePicking
|
|
@@ -1868,7 +2020,14 @@ function FeedbackKit(props) {
|
|
|
1868
2020
|
}
|
|
1869
2021
|
}
|
|
1870
2022
|
) : null,
|
|
1871
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2023
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2024
|
+
PickBar,
|
|
2025
|
+
{
|
|
2026
|
+
paused: pickingState.paused,
|
|
2027
|
+
onToggle: togglePicking,
|
|
2028
|
+
onTogglePause: () => activeKit.picking.togglePause()
|
|
2029
|
+
}
|
|
2030
|
+
),
|
|
1872
2031
|
pickingState.markers.map((marker, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1873
2032
|
"div",
|
|
1874
2033
|
{
|