@solhun/feedback-kit-web 0.8.0 → 0.10.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
@@ -30,45 +30,45 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- COMMENT_MAX_CHARS: () => import_feedback_kit_core6.COMMENT_MAX_CHARS,
34
- COMMENT_REQUIRED_MESSAGE: () => import_feedback_kit_core6.COMMENT_REQUIRED_MESSAGE,
35
- COMMENT_TOO_LONG_MESSAGE: () => import_feedback_kit_core6.COMMENT_TOO_LONG_MESSAGE,
33
+ COMMENT_MAX_CHARS: () => import_feedback_kit_core7.COMMENT_MAX_CHARS,
34
+ COMMENT_REQUIRED_MESSAGE: () => import_feedback_kit_core7.COMMENT_REQUIRED_MESSAGE,
35
+ COMMENT_TOO_LONG_MESSAGE: () => import_feedback_kit_core7.COMMENT_TOO_LONG_MESSAGE,
36
36
  ELEMENT_TEXT_MAX_CHARS: () => ELEMENT_TEXT_MAX_CHARS,
37
37
  ElementPickingController: () => ElementPickingController,
38
- FLOATING_BUTTON_ID: () => import_feedback_kit_core6.FLOATING_BUTTON_ID,
38
+ FLOATING_BUTTON_ID: () => import_feedback_kit_core7.FLOATING_BUTTON_ID,
39
39
  FeedbackKit: () => FeedbackKit,
40
- HintProvider: () => import_feedback_kit_core7.HintProvider,
40
+ HintProvider: () => import_feedback_kit_core8.HintProvider,
41
41
  MARKER_KEY_PREFIX: () => MARKER_KEY_PREFIX,
42
42
  MAX_MARKERS_PER_PATH: () => MAX_MARKERS_PER_PATH,
43
- MODAL_ACTION_HINT_TOGGLE: () => import_feedback_kit_core6.MODAL_ACTION_HINT_TOGGLE,
44
- MODAL_ACTION_PICK: () => import_feedback_kit_core6.MODAL_ACTION_PICK,
43
+ MODAL_ACTION_HINT_TOGGLE: () => import_feedback_kit_core7.MODAL_ACTION_HINT_TOGGLE,
44
+ MODAL_ACTION_PICK: () => import_feedback_kit_core7.MODAL_ACTION_PICK,
45
45
  MarkerStore: () => MarkerStore,
46
46
  OWN_UI_ATTR: () => OWN_UI_ATTR,
47
47
  PICKING_MODE_KEY: () => PICKING_MODE_KEY,
48
- ReportModalController: () => import_feedback_kit_core6.ReportModalController,
49
- SCREENSHOT_FAILED_MESSAGE: () => import_feedback_kit_core6.SCREENSHOT_FAILED_MESSAGE,
48
+ ReportModalController: () => import_feedback_kit_core7.ReportModalController,
49
+ SCREENSHOT_FAILED_MESSAGE: () => import_feedback_kit_core7.SCREENSHOT_FAILED_MESSAGE,
50
50
  SELECTOR_MAX_DEPTH: () => SELECTOR_MAX_DEPTH,
51
- SOURCE_ATTR: () => import_feedback_kit_core5.SOURCE_ATTR,
52
- SUBMIT_DONE_MESSAGE: () => import_feedback_kit_core6.SUBMIT_DONE_MESSAGE,
53
- SUBMIT_PENDING_MESSAGE: () => import_feedback_kit_core6.SUBMIT_PENDING_MESSAGE,
54
- WidgetController: () => import_feedback_kit_core6.WidgetController,
51
+ SOURCE_ATTR: () => import_feedback_kit_core6.SOURCE_ATTR,
52
+ SUBMIT_DONE_MESSAGE: () => import_feedback_kit_core7.SUBMIT_DONE_MESSAGE,
53
+ SUBMIT_PENDING_MESSAGE: () => import_feedback_kit_core7.SUBMIT_PENDING_MESSAGE,
54
+ WidgetController: () => import_feedback_kit_core7.WidgetController,
55
55
  captureWebScreenshot: () => captureWebScreenshot,
56
56
  createRouteWatcher: () => createRouteWatcher,
57
57
  createWebStorage: () => createWebStorage,
58
58
  createWebWidget: () => createWebWidget,
59
59
  cssSelectorPath: () => cssSelectorPath,
60
- denormalizePin: () => import_feedback_kit_core6.denormalizePin,
60
+ denormalizePin: () => import_feedback_kit_core7.denormalizePin,
61
61
  describeElement: () => describeElement,
62
62
  isOwnUi: () => isOwnUi2,
63
- normalizePin: () => import_feedback_kit_core6.normalizePin,
64
- parseSourceAttr: () => import_feedback_kit_core5.parseSourceAttr,
65
- rankHints: () => import_feedback_kit_core7.rankHints,
63
+ normalizePin: () => import_feedback_kit_core7.normalizePin,
64
+ parseSourceAttr: () => import_feedback_kit_core6.parseSourceAttr,
65
+ rankHints: () => import_feedback_kit_core8.rankHints,
66
66
  reactComponentPath: () => reactComponentPath,
67
67
  reactComponentSummary: () => reactComponentSummary,
68
68
  reencodeWebScreenshot: () => reencodeWebScreenshot,
69
- resolveConfig: () => import_feedback_kit_core4.resolveConfig,
70
- shouldShowWidget: () => import_feedback_kit_core4.shouldShowWidget,
71
- sourceFromElement: () => import_feedback_kit_core5.sourceFromElement,
69
+ resolveConfig: () => import_feedback_kit_core5.resolveConfig,
70
+ shouldShowWidget: () => import_feedback_kit_core5.shouldShowWidget,
71
+ sourceFromElement: () => import_feedback_kit_core6.sourceFromElement,
72
72
  visibleText: () => visibleText,
73
73
  webContextProviders: () => webContextProviders,
74
74
  webDiagnosticsOptions: () => webDiagnosticsOptions,
@@ -76,10 +76,10 @@ __export(index_exports, {
76
76
  webRouteWatcher: () => webRouteWatcher
77
77
  });
78
78
  module.exports = __toCommonJS(index_exports);
79
- var import_feedback_kit_core4 = require("@solhun/feedback-kit-core");
80
79
  var import_feedback_kit_core5 = require("@solhun/feedback-kit-core");
81
80
  var import_feedback_kit_core6 = require("@solhun/feedback-kit-core");
82
81
  var import_feedback_kit_core7 = require("@solhun/feedback-kit-core");
82
+ var import_feedback_kit_core8 = require("@solhun/feedback-kit-core");
83
83
 
84
84
  // src/react-tree.ts
85
85
  var FRAMEWORK_INTERNALS = /* @__PURE__ */ new Set([
@@ -1210,11 +1210,88 @@ function createWebWidget(opts) {
1210
1210
  }
1211
1211
 
1212
1212
  // src/feedback-kit.tsx
1213
- var import_feedback_kit_core3 = require("@solhun/feedback-kit-core");
1213
+ var import_feedback_kit_core4 = require("@solhun/feedback-kit-core");
1214
1214
  var import_react = require("react");
1215
1215
 
1216
1216
  // src/version.ts
1217
- var VERSION = true ? "0.8.0" : "dev";
1217
+ var VERSION = true ? "0.10.0" : "dev";
1218
+
1219
+ // src/dev-clipboard.ts
1220
+ var import_feedback_kit_core3 = require("@solhun/feedback-kit-core");
1221
+ function sourceLine(item) {
1222
+ const mapping = (0, import_feedback_kit_core3.sourceFromElement)(item.element ?? void 0);
1223
+ if (!mapping.sourceFile) return null;
1224
+ return mapping.sourceLine === null ? `\uC18C\uC2A4: ${mapping.sourceFile}` : `\uC18C\uC2A4: ${mapping.sourceFile}:${mapping.sourceLine}`;
1225
+ }
1226
+ function elementLine(element) {
1227
+ if (!element) return null;
1228
+ const selector = element.selector ?? element.tag;
1229
+ const text = element.text?.trim();
1230
+ return text ? `\uC694\uC18C: ${selector} "${text}"` : `\uC694\uC18C: ${selector}`;
1231
+ }
1232
+ function componentLine(element) {
1233
+ const summary = element?.attributes?.["react-components"];
1234
+ return summary ? `\uCEF4\uD3EC\uB10C\uD2B8: ${summary}` : null;
1235
+ }
1236
+ function renderDevClipItem(item) {
1237
+ const lines = [`[\uD53C\uB4DC\uBC31] ${item.comment.trim()}`];
1238
+ const screen = item.screen?.trim();
1239
+ if (screen) lines.push(`\uD654\uBA74: ${screen}`);
1240
+ const element = elementLine(item.element);
1241
+ if (element) lines.push(element);
1242
+ const component = componentLine(item.element);
1243
+ if (component) lines.push(component);
1244
+ const source = sourceLine(item);
1245
+ if (source) lines.push(source);
1246
+ if (item.priority && item.priority !== "unset") lines.push(`\uC6B0\uC120\uC21C\uC704: ${item.priority}`);
1247
+ return lines.join("\n");
1248
+ }
1249
+ function renderDevClipList(items) {
1250
+ if (items.length === 0) return "";
1251
+ if (items.length === 1) return renderDevClipItem(items[0]);
1252
+ return items.map((item, index) => `${index + 1}. ${renderDevClipItem(item).split("\n").join("\n ")}`).join("\n\n");
1253
+ }
1254
+ async function writeToClipboard(text) {
1255
+ const clipboard = globalThis.navigator?.clipboard;
1256
+ if (!clipboard?.writeText) return false;
1257
+ try {
1258
+ await clipboard.writeText(text);
1259
+ return true;
1260
+ } catch {
1261
+ return false;
1262
+ }
1263
+ }
1264
+
1265
+ // src/dev-preference.ts
1266
+ var STORAGE_KEY = "feedback-kit:dev-mode";
1267
+ function isDevSettingsHost(hostname) {
1268
+ const host = hostname.trim().toLowerCase();
1269
+ if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]") return true;
1270
+ return host.endsWith(".localhost") || host.endsWith(".local");
1271
+ }
1272
+ function readDevPreference() {
1273
+ try {
1274
+ const raw = globalThis.localStorage?.getItem(STORAGE_KEY);
1275
+ if (raw === "on") return true;
1276
+ if (raw === "off") return false;
1277
+ return null;
1278
+ } catch {
1279
+ return null;
1280
+ }
1281
+ }
1282
+ function writeDevPreference(value) {
1283
+ try {
1284
+ const storage = globalThis.localStorage;
1285
+ if (!storage) return;
1286
+ if (value === null) storage.removeItem(STORAGE_KEY);
1287
+ else storage.setItem(STORAGE_KEY, value ? "on" : "off");
1288
+ } catch {
1289
+ }
1290
+ }
1291
+ function resolveDevMode(propValue, stored) {
1292
+ if (stored !== null) return stored;
1293
+ return propValue === true;
1294
+ }
1218
1295
 
1219
1296
  // src/feedback-kit.tsx
1220
1297
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -1483,7 +1560,10 @@ function PickBar({
1483
1560
  markerCount,
1484
1561
  onToggle,
1485
1562
  onTogglePause,
1486
- onClearMarkers
1563
+ onClearMarkers,
1564
+ onReportWholeScreen,
1565
+ devClipCount,
1566
+ onCopyAll
1487
1567
  }) {
1488
1568
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1489
1569
  "div",
@@ -1538,6 +1618,48 @@ function PickBar({
1538
1618
  ]
1539
1619
  }
1540
1620
  ),
1621
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1622
+ "button",
1623
+ {
1624
+ type: "button",
1625
+ "data-fk-pick-whole": "",
1626
+ title: "\uAC00\uB9AC\uD0A8 \uC694\uC18C \uC5C6\uC774 \uC774 \uD654\uBA74 \uC804\uCCB4\uC5D0 \uB300\uD574 \uC81C\uBCF4\uD569\uB2C8\uB2E4.",
1627
+ onClick: onReportWholeScreen,
1628
+ style: {
1629
+ ...baseButton,
1630
+ display: "inline-flex",
1631
+ alignItems: "center",
1632
+ gap: 6,
1633
+ whiteSpace: "nowrap"
1634
+ },
1635
+ children: [
1636
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "\u25A3" }),
1637
+ "\uD654\uBA74 \uC804\uCCB4\uB85C \uBCF4\uB0B4\uAE30"
1638
+ ]
1639
+ }
1640
+ ),
1641
+ devClipCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1642
+ "button",
1643
+ {
1644
+ type: "button",
1645
+ "data-fk-copy-all": devClipCount,
1646
+ title: "\uC774\uBC88 \uC138\uC158\uC5D0 \uCC0D\uC740 \uAC83\uC744 \uBC88\uD638 \uB9E4\uAE34 \uBAA9\uB85D \uD558\uB098\uB85C \uBCF5\uC0AC\uD569\uB2C8\uB2E4.",
1647
+ onClick: onCopyAll,
1648
+ style: {
1649
+ ...baseButton,
1650
+ display: "inline-flex",
1651
+ alignItems: "center",
1652
+ gap: 6,
1653
+ whiteSpace: "nowrap",
1654
+ fontWeight: 600
1655
+ },
1656
+ children: [
1657
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "\u29C9" }),
1658
+ "\uC804\uBD80 \uBCF5\uC0AC ",
1659
+ devClipCount
1660
+ ]
1661
+ }
1662
+ ) : null,
1541
1663
  markerCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1542
1664
  "button",
1543
1665
  {
@@ -1592,7 +1714,7 @@ function HintChips({
1592
1714
  "button",
1593
1715
  {
1594
1716
  type: "button",
1595
- "data-fk-focus-id": import_feedback_kit_core3.MODAL_ACTION_HINT_TOGGLE,
1717
+ "data-fk-focus-id": import_feedback_kit_core4.MODAL_ACTION_HINT_TOGGLE,
1596
1718
  "aria-expanded": hintsExpanded,
1597
1719
  disabled,
1598
1720
  onClick: onToggle,
@@ -1666,6 +1788,14 @@ function FeedbackKit(props) {
1666
1788
  const [widgetState, setWidgetState] = (0, import_react.useState)(null);
1667
1789
  const [pickingState, setPickingState] = (0, import_react.useState)(INITIAL_PICKING_STATE);
1668
1790
  const [announcement, setAnnouncement] = (0, import_react.useState)(null);
1791
+ const [devMode, setDevMode] = (0, import_react.useState)(() => resolveDevMode(props.devMode, readDevPreference()));
1792
+ const [devSettingsAvailable] = (0, import_react.useState)(
1793
+ () => props.devMode === true || isDevSettingsHost(globalThis.location?.hostname ?? "")
1794
+ );
1795
+ const [devSettingsOpen, setDevSettingsOpen] = (0, import_react.useState)(false);
1796
+ const devClipsRef = (0, import_react.useRef)([]);
1797
+ const [devClipCount, setDevClipCount] = (0, import_react.useState)(0);
1798
+ const [devFallbackText, setDevFallbackText] = (0, import_react.useState)(null);
1669
1799
  const {
1670
1800
  queue,
1671
1801
  createReport,
@@ -1726,7 +1856,7 @@ function FeedbackKit(props) {
1726
1856
  }
1727
1857
  if (modal2.closeConfirmVisible && !previousConfirmVisibleRef.current) confirmRef.current?.focus();
1728
1858
  const wasModal = previousScreenRef.current === "modal";
1729
- if (wasModal && screen2 === "button" && modal2.restoreFocusTo === import_feedback_kit_core3.FLOATING_BUTTON_ID) {
1859
+ if (wasModal && screen2 === "button" && modal2.restoreFocusTo === import_feedback_kit_core4.FLOATING_BUTTON_ID) {
1730
1860
  void nextPaint().then(() => floatingButtonRef.current?.focus());
1731
1861
  }
1732
1862
  previousScreenRef.current = screen2;
@@ -1790,6 +1920,56 @@ function FeedbackKit(props) {
1790
1920
  if (root) root.style.visibility = previousVisibility;
1791
1921
  }
1792
1922
  }
1923
+ function currentClip() {
1924
+ const state = pickingState.popup;
1925
+ if (!state) return null;
1926
+ return {
1927
+ comment: state.comment,
1928
+ element: state.element ?? null,
1929
+ screen: typeof location === "undefined" ? null : location.pathname
1930
+ };
1931
+ }
1932
+ async function putOnClipboard(text) {
1933
+ const ok = await writeToClipboard(text);
1934
+ setDevFallbackText(ok ? null : text);
1935
+ }
1936
+ function commitPopup() {
1937
+ if (devMode) {
1938
+ void copyOne();
1939
+ return;
1940
+ }
1941
+ void activeKit.picking.saveAnnotation();
1942
+ }
1943
+ async function copyOne() {
1944
+ const clip = currentClip();
1945
+ if (!clip || clip.comment.trim() === "") return;
1946
+ devClipsRef.current = [...devClipsRef.current, clip];
1947
+ setDevClipCount(devClipsRef.current.length);
1948
+ activeKit.picking.cancelAnnotation();
1949
+ await putOnClipboard(renderDevClipItem(clip));
1950
+ }
1951
+ async function copyAll() {
1952
+ if (devClipsRef.current.length === 0) return;
1953
+ await putOnClipboard(renderDevClipList(devClipsRef.current));
1954
+ }
1955
+ function toggleDevMode(next) {
1956
+ writeDevPreference(next);
1957
+ setDevMode(next);
1958
+ devClipsRef.current = [];
1959
+ setDevClipCount(0);
1960
+ setDevFallbackText(null);
1961
+ setAnnouncement(
1962
+ next ? "\uAC1C\uBC1C\uC6A9 \uBAA8\uB4DC\uB97C \uCF30\uC2B5\uB2C8\uB2E4. \uC774\uC81C \uC81C\uBCF4\uB294 \uC218\uC9D1\uCC98\uB85C \uAC00\uC9C0 \uC54A\uACE0 \uD074\uB9BD\uBCF4\uB4DC\uB85C \uBCF5\uC0AC\uB429\uB2C8\uB2E4." : "\uAC1C\uBC1C\uC6A9 \uBAA8\uB4DC\uB97C \uAED0\uC2B5\uB2C8\uB2E4. \uC774\uC81C \uC81C\uBCF4\uAC00 \uC218\uC9D1\uCC98\uB85C \uAC11\uB2C8\uB2E4."
1963
+ );
1964
+ }
1965
+ async function pressFloatingButton() {
1966
+ if (props.defaultMode !== "report" && !pickingActive) {
1967
+ setAnnouncement(null);
1968
+ activeKit.widget.startPicking();
1969
+ return;
1970
+ }
1971
+ await openReport();
1972
+ }
1793
1973
  async function openReport() {
1794
1974
  setAnnouncement(null);
1795
1975
  await withOwnUiHidden(() => activeKit.widget.openReport());
@@ -1839,8 +2019,26 @@ function FeedbackKit(props) {
1839
2019
  }
1840
2020
  function submitReport(event) {
1841
2021
  event.preventDefault();
2022
+ if (devMode) {
2023
+ void copyFromModal();
2024
+ return;
2025
+ }
1842
2026
  void activeKit.widget.submitReport();
1843
2027
  }
2028
+ async function copyFromModal() {
2029
+ const modalState = activeKit.widget.getState().modal;
2030
+ if (modalState.comment.trim() === "") return;
2031
+ const clip = {
2032
+ comment: modalState.comment,
2033
+ element: modalState.element ?? null,
2034
+ screen: typeof location === "undefined" ? null : location.pathname,
2035
+ priority: modalState.priority
2036
+ };
2037
+ devClipsRef.current = [...devClipsRef.current, clip];
2038
+ setDevClipCount(devClipsRef.current.length);
2039
+ activeKit.widget.confirmCloseReport();
2040
+ await putOnClipboard(renderDevClipItem(clip));
2041
+ }
1844
2042
  const floating = screen === "button" || screen === "picking";
1845
2043
  const hoverBox = pickingState.hovered?.boundingBox;
1846
2044
  const popup = pickingState.popup;
@@ -1852,45 +2050,199 @@ function FeedbackKit(props) {
1852
2050
  style: { fontFamily: FONT_STACK, color: TOKENS.ink, fontSize: 14, lineHeight: 1.45 },
1853
2051
  children: [
1854
2052
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes feedback-kit-spin{to{transform:rotate(360deg)}}` }),
1855
- floating ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1856
- "button",
2053
+ floating ? (
2054
+ // 톱니와 피드백 버튼을 한 상자에 넣는다. 각각 fixed 로 두면 버튼 글자 길이가
2055
+ // 바뀔 때마다 좌표를 다시 맞춰야 하고, 그러다 겹친다.
2056
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2057
+ "div",
2058
+ {
2059
+ style: {
2060
+ position: "fixed",
2061
+ right: 24,
2062
+ bottom: 24,
2063
+ zIndex: 2147483600,
2064
+ display: "flex",
2065
+ alignItems: "center",
2066
+ gap: 8
2067
+ },
2068
+ children: [
2069
+ devSettingsAvailable ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2070
+ "button",
2071
+ {
2072
+ type: "button",
2073
+ "data-fk-dev-settings-toggle": "",
2074
+ "aria-label": "\uD53C\uB4DC\uBC31 \uC124\uC815",
2075
+ "aria-expanded": devSettingsOpen,
2076
+ onClick: () => setDevSettingsOpen((open) => !open),
2077
+ style: {
2078
+ ...baseButton,
2079
+ width: 46,
2080
+ minHeight: 46,
2081
+ padding: 0,
2082
+ borderRadius: 23,
2083
+ boxShadow: TOKENS.shadow,
2084
+ fontSize: 17,
2085
+ lineHeight: 1
2086
+ },
2087
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "\u2699" })
2088
+ }
2089
+ ) : null,
2090
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2091
+ "button",
2092
+ {
2093
+ ref: floatingButtonRef,
2094
+ id: import_feedback_kit_core4.FLOATING_BUTTON_ID,
2095
+ type: "button",
2096
+ "aria-label": "\uD53C\uB4DC\uBC31 \uBCF4\uB0B4\uAE30",
2097
+ onClick: () => void pressFloatingButton(),
2098
+ style: {
2099
+ ...primaryButton,
2100
+ minWidth: 112,
2101
+ minHeight: 46,
2102
+ borderRadius: 24,
2103
+ boxShadow: TOKENS.shadow
2104
+ },
2105
+ children: "\uD53C\uB4DC\uBC31"
2106
+ }
2107
+ )
2108
+ ]
2109
+ }
2110
+ )
2111
+ ) : null,
2112
+ floating && devMode ? (
2113
+ // 켠 줄 모르고 제보하면 "보냈다"고 믿는데 수집처엔 아무것도 안 남는다.
2114
+ // 조용한 유실이라 이 표시가 유일한 방어다. 버튼 **밖**에 두는 이유는 버튼의
2115
+ // 접근성 이름을 오염시키지 않기 위해서다.
2116
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2117
+ "span",
2118
+ {
2119
+ "data-fk-dev-badge": "",
2120
+ "aria-label": "\uAC1C\uBC1C\uC6A9 \uBAA8\uB4DC\uAC00 \uCF1C\uC838 \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uC81C\uBCF4\uAC00 \uC218\uC9D1\uCC98\uB85C \uAC00\uC9C0 \uC54A\uACE0 \uD074\uB9BD\uBCF4\uB4DC\uB85C \uBCF5\uC0AC\uB429\uB2C8\uB2E4",
2121
+ style: {
2122
+ position: "fixed",
2123
+ right: 24,
2124
+ bottom: 62,
2125
+ zIndex: 2147483601,
2126
+ padding: "2px 8px",
2127
+ borderRadius: 6,
2128
+ background: TOKENS.warning,
2129
+ color: "#1f2933",
2130
+ fontSize: 11,
2131
+ fontWeight: 700,
2132
+ pointerEvents: "none"
2133
+ },
2134
+ children: "\uAC1C\uBC1C\uC6A9 \u2014 \uD074\uB9BD\uBCF4\uB4DC\uB85C \uBCF5\uC0AC"
2135
+ }
2136
+ )
2137
+ ) : null,
2138
+ devFallbackText !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2139
+ "div",
1857
2140
  {
1858
- ref: floatingButtonRef,
1859
- id: import_feedback_kit_core3.FLOATING_BUTTON_ID,
1860
- type: "button",
1861
- "aria-label": "\uD53C\uB4DC\uBC31 \uBCF4\uB0B4\uAE30",
1862
- onClick: () => void openReport(),
2141
+ role: "dialog",
2142
+ "aria-label": "\uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBABB \uC368\uC11C \uB300\uC2E0 \uBCF4\uC5EC\uC90D\uB2C8\uB2E4",
2143
+ "data-fk-dev-fallback": "",
1863
2144
  style: {
1864
- ...primaryButton,
1865
2145
  position: "fixed",
1866
2146
  right: 24,
1867
- bottom: 24,
1868
- zIndex: 2147483600,
1869
- minWidth: 112,
1870
- minHeight: 46,
1871
- borderRadius: 24,
1872
- boxShadow: TOKENS.shadow
2147
+ bottom: 92,
2148
+ zIndex: 2147483602,
2149
+ width: "min(420px, calc(100vw - 48px))",
2150
+ border: `1px solid ${TOKENS.line}`,
2151
+ borderRadius: 12,
2152
+ background: TOKENS.surface,
2153
+ padding: 12,
2154
+ boxShadow: TOKENS.shadow,
2155
+ pointerEvents: "auto"
1873
2156
  },
1874
- children: "\uD53C\uB4DC\uBC31"
2157
+ children: [
2158
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: 6, color: TOKENS.muted, fontSize: 12 }, children: "\uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uC4F0\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC544\uB798\uC5D0\uC11C \uC9C1\uC811 \uBCF5\uC0AC\uD558\uC138\uC694." }),
2159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2160
+ "textarea",
2161
+ {
2162
+ readOnly: true,
2163
+ value: devFallbackText,
2164
+ onFocus: (event) => event.currentTarget.select(),
2165
+ style: { ...inputStyle, height: 140, fontFamily: "ui-monospace, monospace", fontSize: 12 }
2166
+ }
2167
+ ),
2168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => setDevFallbackText(null), style: baseButton, children: "\uB2EB\uAE30" }) })
2169
+ ]
1875
2170
  }
1876
2171
  ) : null,
1877
- announcement && !modal.open ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2172
+ devSettingsOpen || announcement && !modal.open ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1878
2173
  "div",
1879
2174
  {
1880
- role: "status",
1881
- "aria-live": "polite",
1882
2175
  style: {
1883
2176
  position: "fixed",
1884
2177
  right: 24,
1885
2178
  bottom: 82,
1886
2179
  zIndex: 2147483601,
1887
- padding: "10px 14px",
1888
- borderRadius: 10,
1889
- background: TOKENS.ink,
1890
- color: TOKENS.surface,
1891
- boxShadow: TOKENS.shadow
2180
+ display: "flex",
2181
+ flexDirection: "column",
2182
+ alignItems: "flex-end",
2183
+ gap: 8
1892
2184
  },
1893
- children: announcement
2185
+ children: [
2186
+ devSettingsOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2187
+ "div",
2188
+ {
2189
+ role: "dialog",
2190
+ "aria-label": "\uD53C\uB4DC\uBC31 \uC124\uC815",
2191
+ "data-fk-dev-settings": "",
2192
+ style: {
2193
+ width: 292,
2194
+ maxWidth: "calc(100vw - 48px)",
2195
+ padding: 14,
2196
+ borderRadius: 12,
2197
+ border: `1px solid ${TOKENS.line}`,
2198
+ background: TOKENS.surface,
2199
+ boxShadow: TOKENS.shadow
2200
+ },
2201
+ children: [
2202
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", alignItems: "flex-start", gap: 9, cursor: "pointer" }, children: [
2203
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2204
+ "input",
2205
+ {
2206
+ type: "checkbox",
2207
+ checked: devMode,
2208
+ onChange: (event) => toggleDevMode(event.currentTarget.checked),
2209
+ style: { marginTop: 3 }
2210
+ }
2211
+ ),
2212
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
2213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: 700 }, children: "\uAC1C\uBC1C\uC6A9 \uBAA8\uB4DC" }),
2214
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "block", marginTop: 4, color: TOKENS.muted, fontSize: 12.5 }, children: [
2215
+ "\uCF1C\uBA74 \uC81C\uBCF4\uB97C \uC218\uC9D1\uCC98\uB85C \uBCF4\uB0B4\uC9C0 \uC54A\uACE0 ",
2216
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "\uD074\uB9BD\uBCF4\uB4DC\uB85C \uBCF5\uC0AC" }),
2217
+ "\uD569\uB2C8\uB2E4. \uBD99\uC5EC\uB123\uC73C\uBA74 \uD654\uBA74\xB7\uC694\uC18C\xB7 \uCEF4\uD3EC\uB10C\uD2B8\xB7\uC18C\uC2A4 \uACBD\uB85C\uAC00 \uAC19\uC774 \uB098\uC635\uB2C8\uB2E4."
2218
+ ] })
2219
+ ] })
2220
+ ] }),
2221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { margin: "12px 0 0", color: TOKENS.muted, fontSize: 12 }, children: [
2222
+ "\uC774 \uC124\uC815\uC740 \uB85C\uCEEC(",
2223
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "localhost" }),
2224
+ ")\uC5D0\uC11C\uB9CC \uBCF4\uC785\uB2C8\uB2E4. \uBC30\uD3EC\uB41C \uD654\uBA74\uC5D0\uC11C\uB294 \uC2E4\uC0AC\uC6A9\uC790\uAC00 \uCF24 \uC218 \uC5C6\uC5B4\uC57C \uD574\uC11C \uC544\uC608 \uB744\uC6B0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."
2225
+ ] }),
2226
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: baseButton, onClick: () => setDevSettingsOpen(false), children: "\uB2EB\uAE30" }) })
2227
+ ]
2228
+ }
2229
+ ) : null,
2230
+ announcement && !modal.open ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2231
+ "div",
2232
+ {
2233
+ role: "status",
2234
+ "aria-live": "polite",
2235
+ style: {
2236
+ padding: "10px 14px",
2237
+ borderRadius: 10,
2238
+ background: TOKENS.ink,
2239
+ color: TOKENS.surface,
2240
+ boxShadow: TOKENS.shadow
2241
+ },
2242
+ children: announcement
2243
+ }
2244
+ ) : null
2245
+ ]
1894
2246
  }
1895
2247
  ) : null,
1896
2248
  screen === "modal" && modal.open ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1971,7 +2323,7 @@ function FeedbackKit(props) {
1971
2323
  active: pickingActive,
1972
2324
  paused: pickingState.paused,
1973
2325
  disabled: draftLocked,
1974
- focusId: import_feedback_kit_core3.MODAL_ACTION_PICK,
2326
+ focusId: import_feedback_kit_core4.MODAL_ACTION_PICK,
1975
2327
  onToggle: togglePicking
1976
2328
  }
1977
2329
  ),
@@ -2005,7 +2357,7 @@ function FeedbackKit(props) {
2005
2357
  "button",
2006
2358
  {
2007
2359
  type: "button",
2008
- "data-fk-focus-id": import_feedback_kit_core3.MODAL_ACTION_REMOVE_SCREENSHOT,
2360
+ "data-fk-focus-id": import_feedback_kit_core4.MODAL_ACTION_REMOVE_SCREENSHOT,
2009
2361
  disabled: draftLocked,
2010
2362
  onClick: () => kit.widget.modal.removeScreenshot(),
2011
2363
  style: baseButton,
@@ -2065,7 +2417,7 @@ function FeedbackKit(props) {
2065
2417
  "textarea",
2066
2418
  {
2067
2419
  id: "feedback-kit-comment",
2068
- "data-fk-focus-id": import_feedback_kit_core3.MODAL_FIELD_COMMENT,
2420
+ "data-fk-focus-id": import_feedback_kit_core4.MODAL_FIELD_COMMENT,
2069
2421
  value: modal.comment,
2070
2422
  disabled: draftLocked,
2071
2423
  "aria-required": "true",
@@ -2086,7 +2438,7 @@ function FeedbackKit(props) {
2086
2438
  "select",
2087
2439
  {
2088
2440
  id: "feedback-kit-priority",
2089
- "data-fk-focus-id": import_feedback_kit_core3.MODAL_FIELD_PRIORITY,
2441
+ "data-fk-focus-id": import_feedback_kit_core4.MODAL_FIELD_PRIORITY,
2090
2442
  value: modal.priority,
2091
2443
  disabled: draftLocked,
2092
2444
  onChange: (event) => kit.widget.modal.setPriority(event.currentTarget.value),
@@ -2118,7 +2470,7 @@ function FeedbackKit(props) {
2118
2470
  "button",
2119
2471
  {
2120
2472
  type: "button",
2121
- "data-fk-focus-id": import_feedback_kit_core3.MODAL_ACTION_RETRY,
2473
+ "data-fk-focus-id": import_feedback_kit_core4.MODAL_ACTION_RETRY,
2122
2474
  onClick: () => void kit.widget.retryReport(),
2123
2475
  style: { ...baseButton, marginLeft: 10 },
2124
2476
  children: "\uB2E4\uC2DC \uBCF4\uB0B4\uAE30"
@@ -2132,7 +2484,7 @@ function FeedbackKit(props) {
2132
2484
  "button",
2133
2485
  {
2134
2486
  type: "button",
2135
- "data-fk-focus-id": import_feedback_kit_core3.MODAL_ACTION_CANCEL,
2487
+ "data-fk-focus-id": import_feedback_kit_core4.MODAL_ACTION_CANCEL,
2136
2488
  onClick: closeReport,
2137
2489
  style: baseButton,
2138
2490
  children: "\uCDE8\uC18C"
@@ -2142,14 +2494,14 @@ function FeedbackKit(props) {
2142
2494
  "button",
2143
2495
  {
2144
2496
  type: "submit",
2145
- "data-fk-focus-id": import_feedback_kit_core3.MODAL_ACTION_SEND,
2497
+ "data-fk-focus-id": import_feedback_kit_core4.MODAL_ACTION_SEND,
2146
2498
  disabled: !modal.canSubmit,
2147
2499
  style: {
2148
2500
  ...primaryButton,
2149
2501
  opacity: modal.canSubmit ? 1 : 0.5,
2150
2502
  cursor: modal.canSubmit ? "pointer" : "not-allowed"
2151
2503
  },
2152
- children: modal.submitStatus === "sending" ? "\uC804\uC1A1 \uC911\u2026" : "\uBCF4\uB0B4\uAE30"
2504
+ children: modal.submitStatus === "sending" ? "\uC804\uC1A1 \uC911\u2026" : devMode ? "\uBCF5\uC0AC" : "\uBCF4\uB0B4\uAE30"
2153
2505
  }
2154
2506
  )
2155
2507
  ] })
@@ -2228,7 +2580,10 @@ function FeedbackKit(props) {
2228
2580
  markerCount: pickingState.markers.length,
2229
2581
  onToggle: togglePicking,
2230
2582
  onTogglePause: () => activeKit.picking.togglePause(),
2231
- onClearMarkers: () => activeKit.picking.clearMarkers()
2583
+ onClearMarkers: () => activeKit.picking.clearMarkers(),
2584
+ onReportWholeScreen: () => void openReport(),
2585
+ devClipCount: devMode ? devClipCount : 0,
2586
+ onCopyAll: () => void copyAll()
2232
2587
  }
2233
2588
  ),
2234
2589
  pickingState.markers.map((marker, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -2265,7 +2620,7 @@ function FeedbackKit(props) {
2265
2620
  "aria-label": "\uC694\uC18C \uC8FC\uC11D",
2266
2621
  onSubmit: (event) => {
2267
2622
  event.preventDefault();
2268
- void kit.picking.saveAnnotation();
2623
+ commitPopup();
2269
2624
  },
2270
2625
  onPaste: (event) => {
2271
2626
  if (!firstImageOf(event.clipboardData)) return;
@@ -2347,14 +2702,14 @@ function FeedbackKit(props) {
2347
2702
  const native = event.nativeEvent;
2348
2703
  if (native.isComposing) return;
2349
2704
  event.preventDefault();
2350
- void kit.picking.saveAnnotation();
2705
+ commitPopup();
2351
2706
  },
2352
2707
  rows: 2,
2353
2708
  placeholder: "\uBB34\uC5C7\uC774 \uBB38\uC81C\uC778\uAC00\uC694? (Enter \uB85C \uBCF4\uB0B4\uAE30)",
2354
2709
  style: { ...inputStyle, resize: "vertical", minHeight: 58 }
2355
2710
  }
2356
2711
  ),
2357
- popup.comment.length > import_feedback_kit_core3.COMMENT_MAX_CHARS ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "alert", style: { marginTop: 4, color: TOKENS.danger }, children: "4,000\uC790 \uC774\uD558\uB85C \uC785\uB825\uD574\uC8FC\uC138\uC694" }) : null,
2712
+ popup.comment.length > import_feedback_kit_core4.COMMENT_MAX_CHARS ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "alert", style: { marginTop: 4, color: TOKENS.danger }, children: "4,000\uC790 \uC774\uD558\uB85C \uC785\uB825\uD574\uC8FC\uC138\uC694" }) : null,
2358
2713
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2359
2714
  "div",
2360
2715
  {
@@ -2418,7 +2773,7 @@ function FeedbackKit(props) {
2418
2773
  opacity: popup.canSave && !popup.saving ? 1 : 0.5,
2419
2774
  cursor: popup.canSave && !popup.saving ? "pointer" : "not-allowed"
2420
2775
  },
2421
- children: popup.saving ? "\uBCF4\uB0B4\uB294 \uC911\u2026" : "\uBCF4\uB0B4\uAE30"
2776
+ children: popup.saving ? "\uBCF4\uB0B4\uB294 \uC911\u2026" : devMode ? "\uBCF5\uC0AC" : "\uBCF4\uB0B4\uAE30"
2422
2777
  }
2423
2778
  )
2424
2779
  ] })