@solhun/feedback-kit-web 0.6.1 → 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 +172 -61
- 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 +172 -61
- 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: []
|
|
@@ -1184,6 +1224,7 @@ var inputStyle = {
|
|
|
1184
1224
|
};
|
|
1185
1225
|
function PickToggle({
|
|
1186
1226
|
active,
|
|
1227
|
+
paused,
|
|
1187
1228
|
disabled,
|
|
1188
1229
|
variant,
|
|
1189
1230
|
focusId,
|
|
@@ -1211,14 +1252,9 @@ function PickToggle({
|
|
|
1211
1252
|
// 항상 값을 준다. 조건부로 빼면 리렌더 때 shorthand(border)와 충돌한다고 React 가 경고한다.
|
|
1212
1253
|
borderColor: active ? TOKENS.accent : TOKENS.line,
|
|
1213
1254
|
...floating ? {
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
transform: "translateX(-50%)",
|
|
1218
|
-
// 오버레이 자체는 클릭을 통과시킨다(pointerEvents:none) — 이 토글만 되살린다.
|
|
1219
|
-
pointerEvents: "auto",
|
|
1220
|
-
background: TOKENS.surface,
|
|
1221
|
-
boxShadow: TOKENS.shadow
|
|
1255
|
+
// 위치는 감싸는 `PickBar` 가 잡는다 — 옆에 [잠시 멈춤] 이 함께 서야 하는데
|
|
1256
|
+
// 토글이 스스로 fixed 면 둘을 나란히 둘 수 없다.
|
|
1257
|
+
background: TOKENS.surface
|
|
1222
1258
|
} : {
|
|
1223
1259
|
width: "100%",
|
|
1224
1260
|
justifyContent: "space-between",
|
|
@@ -1235,8 +1271,12 @@ function PickToggle({
|
|
|
1235
1271
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1236
1272
|
"span",
|
|
1237
1273
|
{
|
|
1238
|
-
style: {
|
|
1239
|
-
|
|
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"
|
|
1240
1280
|
}
|
|
1241
1281
|
),
|
|
1242
1282
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -1248,7 +1288,7 @@ function PickToggle({
|
|
|
1248
1288
|
width: 38,
|
|
1249
1289
|
height: 22,
|
|
1250
1290
|
borderRadius: 11,
|
|
1251
|
-
background: active ? TOKENS.accent : TOKENS.line
|
|
1291
|
+
background: active ? paused ? TOKENS.muted : TOKENS.accent : TOKENS.line
|
|
1252
1292
|
},
|
|
1253
1293
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1254
1294
|
"span",
|
|
@@ -1272,6 +1312,58 @@ function PickToggle({
|
|
|
1272
1312
|
}
|
|
1273
1313
|
);
|
|
1274
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
|
+
}
|
|
1275
1367
|
function MarkerStatus({ status }) {
|
|
1276
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 };
|
|
1277
1369
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "inline-flex", gap: 4, alignItems: "center", color: presentation.color }, children: [
|
|
@@ -1295,47 +1387,7 @@ function HintChips({
|
|
|
1295
1387
|
onToggle
|
|
1296
1388
|
}) {
|
|
1297
1389
|
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)(
|
|
1390
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1339
1391
|
"button",
|
|
1340
1392
|
{
|
|
1341
1393
|
type: "button",
|
|
@@ -1343,10 +1395,61 @@ function HintChips({
|
|
|
1343
1395
|
"aria-expanded": hintsExpanded,
|
|
1344
1396
|
disabled,
|
|
1345
1397
|
onClick: onToggle,
|
|
1346
|
-
style: {
|
|
1347
|
-
|
|
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
|
+
]
|
|
1348
1414
|
}
|
|
1349
|
-
)
|
|
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
|
|
1350
1453
|
] });
|
|
1351
1454
|
}
|
|
1352
1455
|
function FeedbackKit(props) {
|
|
@@ -1665,6 +1768,7 @@ function FeedbackKit(props) {
|
|
|
1665
1768
|
{
|
|
1666
1769
|
variant: "inline",
|
|
1667
1770
|
active: pickingActive,
|
|
1771
|
+
paused: pickingState.paused,
|
|
1668
1772
|
disabled: draftLocked,
|
|
1669
1773
|
focusId: import_feedback_kit_core3.MODAL_ACTION_PICK,
|
|
1670
1774
|
onToggle: togglePicking
|
|
@@ -1916,7 +2020,14 @@ function FeedbackKit(props) {
|
|
|
1916
2020
|
}
|
|
1917
2021
|
}
|
|
1918
2022
|
) : null,
|
|
1919
|
-
/* @__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
|
+
),
|
|
1920
2031
|
pickingState.markers.map((marker, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1921
2032
|
"div",
|
|
1922
2033
|
{
|