@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 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.6.1" : "dev";
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
- position: "fixed",
1215
- top: 16,
1216
- left: "50%",
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: { fontSize: 12, fontWeight: 600, color: active ? TOKENS.accent : TOKENS.muted },
1239
- children: active ? "\uCF1C\uC9D0" : "\uAEBC\uC9D0"
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
- hintsExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
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: { ...baseButton, minHeight: 28, padding: "4px 10px", fontSize: 12 },
1347
- children: hintsExpanded ? "\uC81C\uC548 \u25B4" : "\uC81C\uC548 \u25BE"
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)(PickToggle, { variant: "floating", active: true, onToggle: togglePicking }),
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
  {