@solhun/feedback-kit-web 0.9.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 +199 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +199 -39
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -369,8 +369,12 @@ interface WebWidgetOpts extends Omit<WidgetControllerOpts, "platform" | "initial
|
|
|
369
369
|
* 개발자가 로컬에서 고치는 중일 때 라쏘런 왕복(보내고·트리아지하고·이슈가 되고)이 오히려
|
|
370
370
|
* 느리다. 정작 고칠 사람은 지금 그 화면을 보고 있는 본인이다.
|
|
371
371
|
*
|
|
372
|
-
* **기본은
|
|
373
|
-
*
|
|
372
|
+
* **기본은 꺼짐**이다. 이건 초기값일 뿐이고, 사람이 화면의 톱니(설정)에서 켜거나 끄면
|
|
373
|
+
* 그 선택이 이 값을 이긴다 — 안 그러면 개발 빌드에서 끄려고 또 코드를 고쳐야 한다.
|
|
374
|
+
* 톱니는 **로컬(`localhost`)에서만** 뜬다. 배포된 화면에서 실사용자가 켜면 그 사람 제보는
|
|
375
|
+
* 어디에도 안 남고 본인은 보냈다고 믿는다. 이 값을 `true` 로 주면 톱니도 같이 뜬다.
|
|
376
|
+
*
|
|
377
|
+
* 켜져 있는 동안은 그 사실이 화면에 계속 보인다(조용한 유실을 막는 유일한 방어).
|
|
374
378
|
*
|
|
375
379
|
* 웹 전용이다. 앱에는 DOM 이 없어 선택자·컴포넌트 경로·소스 경로가 성립하지 않는다.
|
|
376
380
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -369,8 +369,12 @@ interface WebWidgetOpts extends Omit<WidgetControllerOpts, "platform" | "initial
|
|
|
369
369
|
* 개발자가 로컬에서 고치는 중일 때 라쏘런 왕복(보내고·트리아지하고·이슈가 되고)이 오히려
|
|
370
370
|
* 느리다. 정작 고칠 사람은 지금 그 화면을 보고 있는 본인이다.
|
|
371
371
|
*
|
|
372
|
-
* **기본은
|
|
373
|
-
*
|
|
372
|
+
* **기본은 꺼짐**이다. 이건 초기값일 뿐이고, 사람이 화면의 톱니(설정)에서 켜거나 끄면
|
|
373
|
+
* 그 선택이 이 값을 이긴다 — 안 그러면 개발 빌드에서 끄려고 또 코드를 고쳐야 한다.
|
|
374
|
+
* 톱니는 **로컬(`localhost`)에서만** 뜬다. 배포된 화면에서 실사용자가 켜면 그 사람 제보는
|
|
375
|
+
* 어디에도 안 남고 본인은 보냈다고 믿는다. 이 값을 `true` 로 주면 톱니도 같이 뜬다.
|
|
376
|
+
*
|
|
377
|
+
* 켜져 있는 동안은 그 사실이 화면에 계속 보인다(조용한 유실을 막는 유일한 방어).
|
|
374
378
|
*
|
|
375
379
|
* 웹 전용이다. 앱에는 DOM 이 없어 선택자·컴포넌트 경로·소스 경로가 성립하지 않는다.
|
|
376
380
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1174,7 +1174,7 @@ import {
|
|
|
1174
1174
|
} from "react";
|
|
1175
1175
|
|
|
1176
1176
|
// src/version.ts
|
|
1177
|
-
var VERSION = true ? "0.
|
|
1177
|
+
var VERSION = true ? "0.10.0" : "dev";
|
|
1178
1178
|
|
|
1179
1179
|
// src/dev-clipboard.ts
|
|
1180
1180
|
import { sourceFromElement } from "@solhun/feedback-kit-core";
|
|
@@ -1222,6 +1222,37 @@ async function writeToClipboard(text) {
|
|
|
1222
1222
|
}
|
|
1223
1223
|
}
|
|
1224
1224
|
|
|
1225
|
+
// src/dev-preference.ts
|
|
1226
|
+
var STORAGE_KEY = "feedback-kit:dev-mode";
|
|
1227
|
+
function isDevSettingsHost(hostname) {
|
|
1228
|
+
const host = hostname.trim().toLowerCase();
|
|
1229
|
+
if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]") return true;
|
|
1230
|
+
return host.endsWith(".localhost") || host.endsWith(".local");
|
|
1231
|
+
}
|
|
1232
|
+
function readDevPreference() {
|
|
1233
|
+
try {
|
|
1234
|
+
const raw = globalThis.localStorage?.getItem(STORAGE_KEY);
|
|
1235
|
+
if (raw === "on") return true;
|
|
1236
|
+
if (raw === "off") return false;
|
|
1237
|
+
return null;
|
|
1238
|
+
} catch {
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
function writeDevPreference(value) {
|
|
1243
|
+
try {
|
|
1244
|
+
const storage = globalThis.localStorage;
|
|
1245
|
+
if (!storage) return;
|
|
1246
|
+
if (value === null) storage.removeItem(STORAGE_KEY);
|
|
1247
|
+
else storage.setItem(STORAGE_KEY, value ? "on" : "off");
|
|
1248
|
+
} catch {
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
function resolveDevMode(propValue, stored) {
|
|
1252
|
+
if (stored !== null) return stored;
|
|
1253
|
+
return propValue === true;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1225
1256
|
// src/feedback-kit.tsx
|
|
1226
1257
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
1227
1258
|
var TOKENS = {
|
|
@@ -1717,7 +1748,11 @@ function FeedbackKit(props) {
|
|
|
1717
1748
|
const [widgetState, setWidgetState] = useState(null);
|
|
1718
1749
|
const [pickingState, setPickingState] = useState(INITIAL_PICKING_STATE);
|
|
1719
1750
|
const [announcement, setAnnouncement] = useState(null);
|
|
1720
|
-
const [devMode] = useState(() => props.devMode
|
|
1751
|
+
const [devMode, setDevMode] = useState(() => resolveDevMode(props.devMode, readDevPreference()));
|
|
1752
|
+
const [devSettingsAvailable] = useState(
|
|
1753
|
+
() => props.devMode === true || isDevSettingsHost(globalThis.location?.hostname ?? "")
|
|
1754
|
+
);
|
|
1755
|
+
const [devSettingsOpen, setDevSettingsOpen] = useState(false);
|
|
1721
1756
|
const devClipsRef = useRef([]);
|
|
1722
1757
|
const [devClipCount, setDevClipCount] = useState(0);
|
|
1723
1758
|
const [devFallbackText, setDevFallbackText] = useState(null);
|
|
@@ -1858,6 +1893,13 @@ function FeedbackKit(props) {
|
|
|
1858
1893
|
const ok = await writeToClipboard(text);
|
|
1859
1894
|
setDevFallbackText(ok ? null : text);
|
|
1860
1895
|
}
|
|
1896
|
+
function commitPopup() {
|
|
1897
|
+
if (devMode) {
|
|
1898
|
+
void copyOne();
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
void activeKit.picking.saveAnnotation();
|
|
1902
|
+
}
|
|
1861
1903
|
async function copyOne() {
|
|
1862
1904
|
const clip = currentClip();
|
|
1863
1905
|
if (!clip || clip.comment.trim() === "") return;
|
|
@@ -1870,6 +1912,16 @@ function FeedbackKit(props) {
|
|
|
1870
1912
|
if (devClipsRef.current.length === 0) return;
|
|
1871
1913
|
await putOnClipboard(renderDevClipList(devClipsRef.current));
|
|
1872
1914
|
}
|
|
1915
|
+
function toggleDevMode(next) {
|
|
1916
|
+
writeDevPreference(next);
|
|
1917
|
+
setDevMode(next);
|
|
1918
|
+
devClipsRef.current = [];
|
|
1919
|
+
setDevClipCount(0);
|
|
1920
|
+
setDevFallbackText(null);
|
|
1921
|
+
setAnnouncement(
|
|
1922
|
+
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."
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1873
1925
|
async function pressFloatingButton() {
|
|
1874
1926
|
if (props.defaultMode !== "report" && !pickingActive) {
|
|
1875
1927
|
setAnnouncement(null);
|
|
@@ -1927,8 +1979,26 @@ function FeedbackKit(props) {
|
|
|
1927
1979
|
}
|
|
1928
1980
|
function submitReport(event) {
|
|
1929
1981
|
event.preventDefault();
|
|
1982
|
+
if (devMode) {
|
|
1983
|
+
void copyFromModal();
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1930
1986
|
void activeKit.widget.submitReport();
|
|
1931
1987
|
}
|
|
1988
|
+
async function copyFromModal() {
|
|
1989
|
+
const modalState = activeKit.widget.getState().modal;
|
|
1990
|
+
if (modalState.comment.trim() === "") return;
|
|
1991
|
+
const clip = {
|
|
1992
|
+
comment: modalState.comment,
|
|
1993
|
+
element: modalState.element ?? null,
|
|
1994
|
+
screen: typeof location === "undefined" ? null : location.pathname,
|
|
1995
|
+
priority: modalState.priority
|
|
1996
|
+
};
|
|
1997
|
+
devClipsRef.current = [...devClipsRef.current, clip];
|
|
1998
|
+
setDevClipCount(devClipsRef.current.length);
|
|
1999
|
+
activeKit.widget.confirmCloseReport();
|
|
2000
|
+
await putOnClipboard(renderDevClipItem(clip));
|
|
2001
|
+
}
|
|
1932
2002
|
const floating = screen === "button" || screen === "picking";
|
|
1933
2003
|
const hoverBox = pickingState.hovered?.boundingBox;
|
|
1934
2004
|
const popup = pickingState.popup;
|
|
@@ -1940,27 +2010,64 @@ function FeedbackKit(props) {
|
|
|
1940
2010
|
style: { fontFamily: FONT_STACK, color: TOKENS.ink, fontSize: 14, lineHeight: 1.45 },
|
|
1941
2011
|
children: [
|
|
1942
2012
|
/* @__PURE__ */ jsx("style", { children: `@keyframes feedback-kit-spin{to{transform:rotate(360deg)}}` }),
|
|
1943
|
-
floating ?
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2013
|
+
floating ? (
|
|
2014
|
+
// 톱니와 피드백 버튼을 한 상자에 넣는다. 각각 fixed 로 두면 버튼 글자 길이가
|
|
2015
|
+
// 바뀔 때마다 좌표를 다시 맞춰야 하고, 그러다 겹친다.
|
|
2016
|
+
/* @__PURE__ */ jsxs(
|
|
2017
|
+
"div",
|
|
2018
|
+
{
|
|
2019
|
+
style: {
|
|
2020
|
+
position: "fixed",
|
|
2021
|
+
right: 24,
|
|
2022
|
+
bottom: 24,
|
|
2023
|
+
zIndex: 2147483600,
|
|
2024
|
+
display: "flex",
|
|
2025
|
+
alignItems: "center",
|
|
2026
|
+
gap: 8
|
|
2027
|
+
},
|
|
2028
|
+
children: [
|
|
2029
|
+
devSettingsAvailable ? /* @__PURE__ */ jsx(
|
|
2030
|
+
"button",
|
|
2031
|
+
{
|
|
2032
|
+
type: "button",
|
|
2033
|
+
"data-fk-dev-settings-toggle": "",
|
|
2034
|
+
"aria-label": "\uD53C\uB4DC\uBC31 \uC124\uC815",
|
|
2035
|
+
"aria-expanded": devSettingsOpen,
|
|
2036
|
+
onClick: () => setDevSettingsOpen((open) => !open),
|
|
2037
|
+
style: {
|
|
2038
|
+
...baseButton,
|
|
2039
|
+
width: 46,
|
|
2040
|
+
minHeight: 46,
|
|
2041
|
+
padding: 0,
|
|
2042
|
+
borderRadius: 23,
|
|
2043
|
+
boxShadow: TOKENS.shadow,
|
|
2044
|
+
fontSize: 17,
|
|
2045
|
+
lineHeight: 1
|
|
2046
|
+
},
|
|
2047
|
+
children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2699" })
|
|
2048
|
+
}
|
|
2049
|
+
) : null,
|
|
2050
|
+
/* @__PURE__ */ jsx(
|
|
2051
|
+
"button",
|
|
2052
|
+
{
|
|
2053
|
+
ref: floatingButtonRef,
|
|
2054
|
+
id: FLOATING_BUTTON_ID,
|
|
2055
|
+
type: "button",
|
|
2056
|
+
"aria-label": "\uD53C\uB4DC\uBC31 \uBCF4\uB0B4\uAE30",
|
|
2057
|
+
onClick: () => void pressFloatingButton(),
|
|
2058
|
+
style: {
|
|
2059
|
+
...primaryButton,
|
|
2060
|
+
minWidth: 112,
|
|
2061
|
+
minHeight: 46,
|
|
2062
|
+
borderRadius: 24,
|
|
2063
|
+
boxShadow: TOKENS.shadow
|
|
2064
|
+
},
|
|
2065
|
+
children: "\uD53C\uB4DC\uBC31"
|
|
2066
|
+
}
|
|
2067
|
+
)
|
|
2068
|
+
]
|
|
2069
|
+
}
|
|
2070
|
+
)
|
|
1964
2071
|
) : null,
|
|
1965
2072
|
floating && devMode ? (
|
|
1966
2073
|
// 켠 줄 모르고 제보하면 "보냈다"고 믿는데 수집처엔 아무것도 안 남는다.
|
|
@@ -2022,23 +2129,80 @@ function FeedbackKit(props) {
|
|
|
2022
2129
|
]
|
|
2023
2130
|
}
|
|
2024
2131
|
) : null,
|
|
2025
|
-
announcement && !modal.open ? /* @__PURE__ */
|
|
2132
|
+
devSettingsOpen || announcement && !modal.open ? /* @__PURE__ */ jsxs(
|
|
2026
2133
|
"div",
|
|
2027
2134
|
{
|
|
2028
|
-
role: "status",
|
|
2029
|
-
"aria-live": "polite",
|
|
2030
2135
|
style: {
|
|
2031
2136
|
position: "fixed",
|
|
2032
2137
|
right: 24,
|
|
2033
2138
|
bottom: 82,
|
|
2034
2139
|
zIndex: 2147483601,
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
boxShadow: TOKENS.shadow
|
|
2140
|
+
display: "flex",
|
|
2141
|
+
flexDirection: "column",
|
|
2142
|
+
alignItems: "flex-end",
|
|
2143
|
+
gap: 8
|
|
2040
2144
|
},
|
|
2041
|
-
children:
|
|
2145
|
+
children: [
|
|
2146
|
+
devSettingsOpen ? /* @__PURE__ */ jsxs(
|
|
2147
|
+
"div",
|
|
2148
|
+
{
|
|
2149
|
+
role: "dialog",
|
|
2150
|
+
"aria-label": "\uD53C\uB4DC\uBC31 \uC124\uC815",
|
|
2151
|
+
"data-fk-dev-settings": "",
|
|
2152
|
+
style: {
|
|
2153
|
+
width: 292,
|
|
2154
|
+
maxWidth: "calc(100vw - 48px)",
|
|
2155
|
+
padding: 14,
|
|
2156
|
+
borderRadius: 12,
|
|
2157
|
+
border: `1px solid ${TOKENS.line}`,
|
|
2158
|
+
background: TOKENS.surface,
|
|
2159
|
+
boxShadow: TOKENS.shadow
|
|
2160
|
+
},
|
|
2161
|
+
children: [
|
|
2162
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "flex-start", gap: 9, cursor: "pointer" }, children: [
|
|
2163
|
+
/* @__PURE__ */ jsx(
|
|
2164
|
+
"input",
|
|
2165
|
+
{
|
|
2166
|
+
type: "checkbox",
|
|
2167
|
+
checked: devMode,
|
|
2168
|
+
onChange: (event) => toggleDevMode(event.currentTarget.checked),
|
|
2169
|
+
style: { marginTop: 3 }
|
|
2170
|
+
}
|
|
2171
|
+
),
|
|
2172
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
2173
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 700 }, children: "\uAC1C\uBC1C\uC6A9 \uBAA8\uB4DC" }),
|
|
2174
|
+
/* @__PURE__ */ jsxs("span", { style: { display: "block", marginTop: 4, color: TOKENS.muted, fontSize: 12.5 }, children: [
|
|
2175
|
+
"\uCF1C\uBA74 \uC81C\uBCF4\uB97C \uC218\uC9D1\uCC98\uB85C \uBCF4\uB0B4\uC9C0 \uC54A\uACE0 ",
|
|
2176
|
+
/* @__PURE__ */ jsx("b", { children: "\uD074\uB9BD\uBCF4\uB4DC\uB85C \uBCF5\uC0AC" }),
|
|
2177
|
+
"\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."
|
|
2178
|
+
] })
|
|
2179
|
+
] })
|
|
2180
|
+
] }),
|
|
2181
|
+
/* @__PURE__ */ jsxs("p", { style: { margin: "12px 0 0", color: TOKENS.muted, fontSize: 12 }, children: [
|
|
2182
|
+
"\uC774 \uC124\uC815\uC740 \uB85C\uCEEC(",
|
|
2183
|
+
/* @__PURE__ */ jsx("code", { children: "localhost" }),
|
|
2184
|
+
")\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."
|
|
2185
|
+
] }),
|
|
2186
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: 12 }, children: /* @__PURE__ */ jsx("button", { type: "button", style: baseButton, onClick: () => setDevSettingsOpen(false), children: "\uB2EB\uAE30" }) })
|
|
2187
|
+
]
|
|
2188
|
+
}
|
|
2189
|
+
) : null,
|
|
2190
|
+
announcement && !modal.open ? /* @__PURE__ */ jsx(
|
|
2191
|
+
"div",
|
|
2192
|
+
{
|
|
2193
|
+
role: "status",
|
|
2194
|
+
"aria-live": "polite",
|
|
2195
|
+
style: {
|
|
2196
|
+
padding: "10px 14px",
|
|
2197
|
+
borderRadius: 10,
|
|
2198
|
+
background: TOKENS.ink,
|
|
2199
|
+
color: TOKENS.surface,
|
|
2200
|
+
boxShadow: TOKENS.shadow
|
|
2201
|
+
},
|
|
2202
|
+
children: announcement
|
|
2203
|
+
}
|
|
2204
|
+
) : null
|
|
2205
|
+
]
|
|
2042
2206
|
}
|
|
2043
2207
|
) : null,
|
|
2044
2208
|
screen === "modal" && modal.open ? /* @__PURE__ */ jsx(
|
|
@@ -2297,7 +2461,7 @@ function FeedbackKit(props) {
|
|
|
2297
2461
|
opacity: modal.canSubmit ? 1 : 0.5,
|
|
2298
2462
|
cursor: modal.canSubmit ? "pointer" : "not-allowed"
|
|
2299
2463
|
},
|
|
2300
|
-
children: modal.submitStatus === "sending" ? "\uC804\uC1A1 \uC911\u2026" : "\uBCF4\uB0B4\uAE30"
|
|
2464
|
+
children: modal.submitStatus === "sending" ? "\uC804\uC1A1 \uC911\u2026" : devMode ? "\uBCF5\uC0AC" : "\uBCF4\uB0B4\uAE30"
|
|
2301
2465
|
}
|
|
2302
2466
|
)
|
|
2303
2467
|
] })
|
|
@@ -2416,11 +2580,7 @@ function FeedbackKit(props) {
|
|
|
2416
2580
|
"aria-label": "\uC694\uC18C \uC8FC\uC11D",
|
|
2417
2581
|
onSubmit: (event) => {
|
|
2418
2582
|
event.preventDefault();
|
|
2419
|
-
|
|
2420
|
-
void copyOne();
|
|
2421
|
-
return;
|
|
2422
|
-
}
|
|
2423
|
-
void kit.picking.saveAnnotation();
|
|
2583
|
+
commitPopup();
|
|
2424
2584
|
},
|
|
2425
2585
|
onPaste: (event) => {
|
|
2426
2586
|
if (!firstImageOf(event.clipboardData)) return;
|
|
@@ -2502,7 +2662,7 @@ function FeedbackKit(props) {
|
|
|
2502
2662
|
const native = event.nativeEvent;
|
|
2503
2663
|
if (native.isComposing) return;
|
|
2504
2664
|
event.preventDefault();
|
|
2505
|
-
|
|
2665
|
+
commitPopup();
|
|
2506
2666
|
},
|
|
2507
2667
|
rows: 2,
|
|
2508
2668
|
placeholder: "\uBB34\uC5C7\uC774 \uBB38\uC81C\uC778\uAC00\uC694? (Enter \uB85C \uBCF4\uB0B4\uAE30)",
|