@solhun/feedback-kit-web 0.5.0 → 0.6.1

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.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  COMMENT_TOO_LONG_MESSAGE,
8
8
  denormalizePin,
9
9
  FLOATING_BUTTON_ID as FLOATING_BUTTON_ID2,
10
+ MODAL_ACTION_HINT_TOGGLE as MODAL_ACTION_HINT_TOGGLE2,
10
11
  MODAL_ACTION_PICK as MODAL_ACTION_PICK2,
11
12
  normalizePin as normalizePin2,
12
13
  ReportModalController,
@@ -15,6 +16,7 @@ import {
15
16
  SUBMIT_PENDING_MESSAGE,
16
17
  WidgetController as WidgetController2
17
18
  } from "@solhun/feedback-kit-core";
19
+ import { HintProvider as HintProvider2, rankHints as rankHints2 } from "@solhun/feedback-kit-core";
18
20
 
19
21
  // src/react-tree.ts
20
22
  var FRAMEWORK_INTERNALS = /* @__PURE__ */ new Set([
@@ -328,6 +330,11 @@ var ElementPickingController = class {
328
330
  this.shotGeneration = 0;
329
331
  /** 지금 도는 자동 캡처. Enter 가 캡처보다 빨랐을 때 기다릴 대상. */
330
332
  this.capturing = null;
333
+ /**
334
+ * 사용자가 [제안 ▾] 를 직접 건드렸는가. 한 번이라도 건드리면 이후 타이핑에 의한 자동
335
+ * 접힘/펼침이 멈춘다 — 모달의 `userToggledHints` 와 같은 규칙. 팝업을 새로 열 때마다 리셋된다.
336
+ */
337
+ this.userToggledHints = false;
331
338
  this.onClick = (event) => this.handleClick(event);
332
339
  this.onMouseOver = (event) => this.handleMouseOver(event);
333
340
  this.queue = opts.queue;
@@ -340,6 +347,7 @@ var ElementPickingController = class {
340
347
  this.capture = opts.capture ?? null;
341
348
  this.reencode = opts.reencode ?? null;
342
349
  this.screenshotLimitBytes = opts.screenshotLimitBytes;
350
+ this.rankHintsFor = opts.rankHintsFor ?? null;
343
351
  this.lastPathname = this.getPathname();
344
352
  this.markers = this.store.list(this.lastPathname);
345
353
  this.unsubscribeQueue = this.queue.subscribe?.(() => {
@@ -454,14 +462,22 @@ var ElementPickingController = class {
454
462
  this.onPick(describeElement(element), point);
455
463
  return;
456
464
  }
465
+ const info = describeElement(element);
466
+ this.userToggledHints = false;
467
+ const hints = this.rankHintsFor ? this.rankHintsFor(info) : [];
457
468
  this.popup = {
458
- element: describeElement(element),
469
+ element: info,
459
470
  point,
460
471
  comment: "",
461
472
  canSave: false,
462
473
  screenshot: null,
463
474
  screenshotStatus: this.capture ? "capturing" : "none",
464
- saving: false
475
+ saving: false,
476
+ hints,
477
+ // 코멘트가 비어 있을 때만 펼친다 — 모달과 같은 규칙(계약 원문). 방금 연 팝업은 항상 빈다.
478
+ hintsExpanded: true,
479
+ hintsAvailable: hints.length > 0,
480
+ usedHintIds: []
465
481
  };
466
482
  this.emit();
467
483
  if (this.capture) this.capturing = this.runCapture();
@@ -471,10 +487,48 @@ var ElementPickingController = class {
471
487
  this.popup = {
472
488
  ...this.popup,
473
489
  comment: value,
474
- canSave: this.isSavable(value)
490
+ canSave: this.isSavable(value),
491
+ ...this.hintsPatchFor(value)
475
492
  };
476
493
  this.emit();
477
494
  }
495
+ /**
496
+ * 코멘트가 비었는지에 따라 칩 줄 상태를 갱신하는 조각. `setAnnotationComment`·`applyHint`
497
+ * 양쪽에서 쓴다 — 모달의 `hintsPatchFor` 와 같은 규칙이다.
498
+ */
499
+ hintsPatchFor(nextComment) {
500
+ if (nextComment.trim().length === 0) {
501
+ return { hintsExpanded: true, usedHintIds: [] };
502
+ }
503
+ return this.userToggledHints ? {} : { hintsExpanded: false };
504
+ }
505
+ /**
506
+ * 칩 누름. 이미 눌린 칩이면 아무 일도 하지 않는다. 코멘트가 비어 있으면 초안으로 치환,
507
+ * 아니면 줄바꿈 뒤 이어붙인다 — 모달의 `applyHint` 와 동일한 규칙이다.
508
+ */
509
+ applyHint(hintId) {
510
+ if (!this.popup || this.popup.saving) return;
511
+ if (this.popup.usedHintIds.includes(hintId)) return;
512
+ const hint = this.popup.hints.find((h) => h.id === hintId);
513
+ if (!hint) return;
514
+ const comment = this.popup.comment.length === 0 ? hint.draft : `${this.popup.comment}
515
+ ${hint.draft}`;
516
+ this.popup = {
517
+ ...this.popup,
518
+ comment,
519
+ canSave: this.isSavable(comment),
520
+ usedHintIds: [...this.popup.usedHintIds, hintId],
521
+ ...this.hintsPatchFor(comment)
522
+ };
523
+ this.emit();
524
+ }
525
+ /** [제안 ▾] 토글. 방향과 무관하게 이후 자동 접힘/펼침을 멈춘다. */
526
+ toggleHints() {
527
+ if (!this.popup) return;
528
+ this.userToggledHints = true;
529
+ this.popup = { ...this.popup, hintsExpanded: !this.popup.hintsExpanded };
530
+ this.emit();
531
+ }
478
532
  /**
479
533
  * 사용자가 붙여넣기(Cmd+V)나 드래그로 넣은 그림. 자동 캡처 결과를 덮는다.
480
534
  *
@@ -581,7 +635,9 @@ var ElementPickingController = class {
581
635
  priority: "unset",
582
636
  screenshot: settled.screenshot,
583
637
  pin: settled.point,
584
- element: settled.element
638
+ element: settled.element,
639
+ // 눌린 칩 기록. 리포트 모달과 같은 필드 — 들어온 문이 달라도 채택 신호는 같은 값으로 쌓인다.
640
+ hintIds: settled.usedHintIds
585
641
  };
586
642
  let report;
587
643
  try {
@@ -741,12 +797,105 @@ var reencodeWebScreenshot = async (shot, quality) => {
741
797
  };
742
798
 
743
799
  // src/widget.ts
744
- import { WidgetController } from "@solhun/feedback-kit-core";
800
+ import {
801
+ WidgetController,
802
+ HintProvider,
803
+ rankHints
804
+ } from "@solhun/feedback-kit-core";
805
+
806
+ // src/storage.ts
807
+ function memoryStorage() {
808
+ const map = /* @__PURE__ */ new Map();
809
+ return {
810
+ getItem: (key) => map.get(key) ?? null,
811
+ setItem: (key, value) => void map.set(key, value),
812
+ removeItem: (key) => void map.delete(key)
813
+ };
814
+ }
815
+ function pickBacking() {
816
+ try {
817
+ const ls = globalThis.localStorage;
818
+ if (!ls) return memoryStorage();
819
+ const probe = "__feedback_kit_probe__";
820
+ ls.setItem(probe, "1");
821
+ ls.removeItem(probe);
822
+ return ls;
823
+ } catch {
824
+ return memoryStorage();
825
+ }
826
+ }
827
+ function createWebStorage(backing) {
828
+ const store = backing ?? pickBacking();
829
+ return {
830
+ async get(key) {
831
+ try {
832
+ return store.getItem(key);
833
+ } catch {
834
+ return null;
835
+ }
836
+ },
837
+ async set(key, value) {
838
+ store.setItem(key, value);
839
+ },
840
+ async remove(key) {
841
+ try {
842
+ store.removeItem(key);
843
+ } catch {
844
+ }
845
+ }
846
+ };
847
+ }
848
+
849
+ // src/widget.ts
850
+ function stripQueryHash(path) {
851
+ const cut = [path.indexOf("?"), path.indexOf("#")].filter((i) => i >= 0);
852
+ return cut.length > 0 ? path.slice(0, Math.min(...cut)) : path;
853
+ }
854
+ function defaultPathname2() {
855
+ const loc = globalThis.location;
856
+ return loc?.pathname ?? "/";
857
+ }
858
+ function hintSourceFrom(queue) {
859
+ const adapter = queue.adapter;
860
+ if (!adapter || typeof adapter.fetchHints !== "function") return null;
861
+ const cacheKey = adapter.hintsCacheKey ?? "default";
862
+ return { fetchHints: adapter.fetchHints.bind(adapter), cacheKey };
863
+ }
864
+ function resolveHintProvider(opts) {
865
+ if (opts.hintProvider) return opts.hintProvider;
866
+ if (opts.hints) {
867
+ return new HintProvider({ source: null, staticHints: opts.hints, cacheKey: "static" });
868
+ }
869
+ const found = hintSourceFrom(opts.queue);
870
+ if (!found) return null;
871
+ return new HintProvider({
872
+ source: { fetchHints: (etag) => found.fetchHints(etag) },
873
+ storage: createWebStorage(),
874
+ cacheKey: found.cacheKey
875
+ });
876
+ }
877
+ var POPUP_HINT_DISPLAY_MAX = 3;
878
+ function rankFor(hintProvider, opts, element, limitOverride) {
879
+ const catalog = hintProvider.catalog();
880
+ if (!catalog) return [];
881
+ const input = {
882
+ platform: "web",
883
+ path: stripQueryHash(opts.getPathname ? opts.getPathname() : defaultPathname2()),
884
+ element,
885
+ components: element ? element.attributes["react-components"] ?? null : null
886
+ };
887
+ const limit = limitOverride !== void 0 ? Math.min(limitOverride, catalog.maxDisplay) : catalog.maxDisplay;
888
+ return rankHints(catalog.hints, input, limit);
889
+ }
745
890
  function createWebWidget(opts) {
746
891
  const store = opts.store ?? new MarkerStore();
747
892
  let widgetRef = null;
748
893
  const capture = opts.capture === void 0 ? captureWebScreenshot : opts.capture;
749
894
  const reencode = opts.reencode === void 0 ? reencodeWebScreenshot : opts.reencode;
895
+ const hintProvider = resolveHintProvider(opts);
896
+ if (hintProvider) {
897
+ void hintProvider.load().then(() => hintProvider.refresh());
898
+ }
750
899
  const picking = new ElementPickingController({
751
900
  queue: opts.queue,
752
901
  createReport: opts.createReport,
@@ -762,7 +911,8 @@ function createWebWidget(opts) {
762
911
  // 모드는 어느 쪽이든 켜진 채로 둬서 한 번 켜고 여러 요소를 연달아 지목할 수 있다.
763
912
  onPick: opts.pickTarget === "modal" ? (element, point) => {
764
913
  void widgetRef?.openReport({ element, pin: point });
765
- } : void 0
914
+ } : void 0,
915
+ rankHintsFor: hintProvider ? (element) => rankFor(hintProvider, opts, element, POPUP_HINT_DISPLAY_MAX) : null
766
916
  });
767
917
  const widget = new WidgetController({
768
918
  ...opts,
@@ -777,6 +927,13 @@ function createWebWidget(opts) {
777
927
  }
778
928
  });
779
929
  widgetRef = widget;
930
+ const originalOpenReport = widget.openReport.bind(widget);
931
+ widget.openReport = async (openOpts = {}) => {
932
+ if (hintProvider) {
933
+ widget.modal.setHints(rankFor(hintProvider, opts, openOpts.element ?? null));
934
+ }
935
+ await originalOpenReport(openOpts);
936
+ };
780
937
  const originalSubmit = widget.submitReport.bind(widget);
781
938
  widget.submitReport = async () => {
782
939
  const outcome = await originalSubmit();
@@ -801,6 +958,7 @@ import {
801
958
  COMMENT_MAX_CHARS as COMMENT_MAX_CHARS2,
802
959
  FLOATING_BUTTON_ID,
803
960
  MODAL_ACTION_CANCEL,
961
+ MODAL_ACTION_HINT_TOGGLE,
804
962
  MODAL_ACTION_PICK,
805
963
  MODAL_ACTION_REMOVE_SCREENSHOT,
806
964
  MODAL_ACTION_RETRY,
@@ -815,7 +973,7 @@ import {
815
973
  } from "react";
816
974
 
817
975
  // src/version.ts
818
- var VERSION = true ? "0.5.0" : "dev";
976
+ var VERSION = true ? "0.6.1" : "dev";
819
977
 
820
978
  // src/feedback-kit.tsx
821
979
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -878,26 +1036,41 @@ async function transcodeToJpeg(dataUrl) {
878
1036
  return null;
879
1037
  }
880
1038
  }
1039
+ function looksLikeImageName(name) {
1040
+ return /\.(png|jpe?g|gif|webp|bmp|avif|heic|heif)$/i.test(name);
1041
+ }
1042
+ function isImageFile(file) {
1043
+ if (file.type.startsWith("image/")) return true;
1044
+ return (file.type === "" || file.type === "application/octet-stream") && looksLikeImageName(file.name);
1045
+ }
1046
+ function contentTypeOf(file) {
1047
+ if (file.type !== "") return file.type;
1048
+ const name = file instanceof File ? file.name : "";
1049
+ if (/\.png$/i.test(name)) return "image/png";
1050
+ if (/\.jpe?g$/i.test(name)) return "image/jpeg";
1051
+ return "";
1052
+ }
881
1053
  async function readImageFile(file) {
882
- if (!file.type.startsWith("image/")) return null;
1054
+ const named = file instanceof File ? file : null;
1055
+ if (!file.type.startsWith("image/") && !(named !== null && isImageFile(named))) return null;
883
1056
  const dataUrl = await readDataUrl(file);
884
1057
  if (!dataUrl) return null;
885
- if (file.type === "image/png" || file.type === "image/jpeg") {
1058
+ const type = contentTypeOf(file);
1059
+ if (type === "image/png" || type === "image/jpeg") {
886
1060
  const base64 = base64Of(dataUrl);
887
- return base64 ? { base64, contentType: file.type } : null;
1061
+ return base64 ? { base64, contentType: type } : null;
888
1062
  }
889
1063
  return transcodeToJpeg(dataUrl);
890
1064
  }
891
1065
  function firstImageOf(transfer) {
892
1066
  if (!transfer) return null;
893
1067
  for (const item of Array.from(transfer.items ?? [])) {
894
- if (item.kind === "file" && item.type.startsWith("image/")) {
895
- const file = item.getAsFile();
896
- if (file) return file;
897
- }
1068
+ if (item.kind !== "file") continue;
1069
+ const file = item.getAsFile();
1070
+ if (file && isImageFile(file)) return file;
898
1071
  }
899
1072
  for (const file of Array.from(transfer.files ?? [])) {
900
- if (file.type.startsWith("image/")) return file;
1073
+ if (isImageFile(file)) return file;
901
1074
  }
902
1075
  return null;
903
1076
  }
@@ -1077,6 +1250,69 @@ function MarkerStatus({ status }) {
1077
1250
  /* @__PURE__ */ jsx("span", { children: presentation.text })
1078
1251
  ] });
1079
1252
  }
1253
+ function HintChips({
1254
+ hints,
1255
+ hintsExpanded,
1256
+ usedHintIds,
1257
+ disabled,
1258
+ onApply,
1259
+ onToggle
1260
+ }) {
1261
+ return /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
1262
+ hintsExpanded ? /* @__PURE__ */ jsxs(Fragment, { children: [
1263
+ /* @__PURE__ */ jsx("p", { style: { margin: "0 0 7px", color: TOKENS.muted, fontSize: 12 }, children: "\uC774\uB7F0 \uAC74\uAC00\uC694?" }),
1264
+ /* @__PURE__ */ jsx(
1265
+ "div",
1266
+ {
1267
+ role: "group",
1268
+ "aria-label": "\uC790\uC8FC \uB098\uC624\uB294 \uC81C\uBCF4 \uC81C\uC548",
1269
+ style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 7 },
1270
+ children: hints.map((hint) => {
1271
+ const used = usedHintIds.includes(hint.id);
1272
+ return /* @__PURE__ */ jsx(
1273
+ "button",
1274
+ {
1275
+ type: "button",
1276
+ "data-fk-focus-id": `hint:${hint.id}`,
1277
+ disabled: used || disabled,
1278
+ "aria-pressed": used,
1279
+ onClick: () => onApply(hint.id),
1280
+ style: {
1281
+ ...baseButton,
1282
+ minHeight: 30,
1283
+ padding: "5px 10px",
1284
+ fontSize: 12,
1285
+ fontWeight: 600,
1286
+ borderRadius: 999,
1287
+ ...used ? {
1288
+ background: TOKENS.subtle,
1289
+ color: TOKENS.muted,
1290
+ borderColor: TOKENS.line,
1291
+ cursor: "default"
1292
+ } : {}
1293
+ },
1294
+ children: hint.label
1295
+ },
1296
+ hint.id
1297
+ );
1298
+ })
1299
+ }
1300
+ )
1301
+ ] }) : null,
1302
+ /* @__PURE__ */ jsx(
1303
+ "button",
1304
+ {
1305
+ type: "button",
1306
+ "data-fk-focus-id": MODAL_ACTION_HINT_TOGGLE,
1307
+ "aria-expanded": hintsExpanded,
1308
+ disabled,
1309
+ onClick: onToggle,
1310
+ style: { ...baseButton, minHeight: 28, padding: "4px 10px", fontSize: 12 },
1311
+ children: hintsExpanded ? "\uC81C\uC548 \u25B4" : "\uC81C\uC548 \u25BE"
1312
+ }
1313
+ )
1314
+ ] });
1315
+ }
1080
1316
  function FeedbackKit(props) {
1081
1317
  const rootRef = useRef(null);
1082
1318
  const floatingButtonRef = useRef(null);
@@ -1099,7 +1335,10 @@ function FeedbackKit(props) {
1099
1335
  store,
1100
1336
  getPathname,
1101
1337
  doc,
1102
- getViewport
1338
+ getViewport,
1339
+ pickTarget,
1340
+ hints,
1341
+ hintProvider
1103
1342
  } = props;
1104
1343
  useEffect(() => {
1105
1344
  const created = createWebWidget({
@@ -1111,7 +1350,10 @@ function FeedbackKit(props) {
1111
1350
  store,
1112
1351
  getPathname,
1113
1352
  doc,
1114
- getViewport
1353
+ getViewport,
1354
+ pickTarget,
1355
+ hints,
1356
+ hintProvider
1115
1357
  });
1116
1358
  setKit(created);
1117
1359
  const unsubscribeWidget = created.widget.subscribe(setWidgetState);
@@ -1129,6 +1371,9 @@ function FeedbackKit(props) {
1129
1371
  screenshotLimitBytes,
1130
1372
  store,
1131
1373
  getPathname,
1374
+ pickTarget,
1375
+ hints,
1376
+ hintProvider,
1132
1377
  doc,
1133
1378
  getViewport
1134
1379
  ]);
@@ -1167,6 +1412,23 @@ function FeedbackKit(props) {
1167
1412
  document.addEventListener("keydown", onKeyDownCapture, true);
1168
1413
  return () => document.removeEventListener("keydown", onKeyDownCapture, true);
1169
1414
  }, [kit, widgetState, pickingState.popup]);
1415
+ useEffect(() => {
1416
+ if (!kit || !widgetState?.modal.open) return;
1417
+ if (pickingState.popup) return;
1418
+ const locked = widgetState.modal.submitStatus === "sending" || widgetState.modal.submitStatus === "pending";
1419
+ if (locked) return;
1420
+ const controller = kit;
1421
+ async function onPaste(event) {
1422
+ const file = firstImageOf(event.clipboardData);
1423
+ if (!file) return;
1424
+ event.preventDefault();
1425
+ const shot = await readImageFile(file);
1426
+ if (shot) await controller.widget.modal.attachFile(shot);
1427
+ }
1428
+ const handler = (event) => void onPaste(event);
1429
+ document.addEventListener("paste", handler);
1430
+ return () => document.removeEventListener("paste", handler);
1431
+ }, [kit, widgetState, pickingState.popup]);
1170
1432
  if (!kit || !widgetState) {
1171
1433
  return /* @__PURE__ */ jsx("div", { ...OWN_UI_PROPS, "data-feedback-kit-loading": "true" });
1172
1434
  }
@@ -1204,6 +1466,14 @@ function FeedbackKit(props) {
1204
1466
  }
1205
1467
  input.value = "";
1206
1468
  }
1469
+ async function attachToModal(transfer) {
1470
+ const file = firstImageOf(transfer);
1471
+ if (!file) return false;
1472
+ const screenshot = await readImageFile(file);
1473
+ if (!screenshot) return false;
1474
+ await activeKit.widget.modal.attachFile(screenshot);
1475
+ return true;
1476
+ }
1207
1477
  async function attachToPopup(transfer) {
1208
1478
  const file = firstImageOf(transfer);
1209
1479
  if (!file) return false;
@@ -1304,6 +1574,14 @@ function FeedbackKit(props) {
1304
1574
  "aria-modal": "true",
1305
1575
  "aria-labelledby": "feedback-kit-dialog-title",
1306
1576
  onKeyDown: handleModalKeyDown,
1577
+ onDragOver: (event) => {
1578
+ if (!draftLocked && firstImageOf(event.dataTransfer)) event.preventDefault();
1579
+ },
1580
+ onDrop: (event) => {
1581
+ if (draftLocked || !firstImageOf(event.dataTransfer)) return;
1582
+ event.preventDefault();
1583
+ void attachToModal(event.dataTransfer);
1584
+ },
1307
1585
  style: {
1308
1586
  width: "min(520px, 100%)",
1309
1587
  maxHeight: "min(760px, calc(100vh - 40px))",
@@ -1427,6 +1705,17 @@ function FeedbackKit(props) {
1427
1705
  )
1428
1706
  ] })
1429
1707
  ] }),
1708
+ modal.hintsAvailable ? /* @__PURE__ */ jsx(
1709
+ HintChips,
1710
+ {
1711
+ hints: modal.hints,
1712
+ hintsExpanded: modal.hintsExpanded,
1713
+ usedHintIds: modal.usedHintIds,
1714
+ disabled: draftLocked,
1715
+ onApply: (hintId) => kit.widget.modal.applyHint(hintId),
1716
+ onToggle: () => kit.widget.modal.toggleHints()
1717
+ }
1718
+ ) : null,
1430
1719
  /* @__PURE__ */ jsxs("label", { htmlFor: "feedback-kit-comment", style: { display: "block", fontWeight: 700 }, children: [
1431
1720
  "\uCF54\uBA58\uD2B8 ",
1432
1721
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "*" })
@@ -1683,6 +1972,17 @@ function FeedbackKit(props) {
1683
1972
  ]
1684
1973
  }
1685
1974
  ),
1975
+ popup.hintsAvailable ? /* @__PURE__ */ jsx(
1976
+ HintChips,
1977
+ {
1978
+ hints: popup.hints,
1979
+ hintsExpanded: popup.hintsExpanded,
1980
+ usedHintIds: popup.usedHintIds,
1981
+ disabled: popup.saving,
1982
+ onApply: (hintId) => kit.picking.applyHint(hintId),
1983
+ onToggle: () => kit.picking.toggleHints()
1984
+ }
1985
+ ) : null,
1686
1986
  /* @__PURE__ */ jsx("label", { htmlFor: "feedback-kit-annotation", style: visuallyHidden, children: "\uC774 \uC694\uC18C\uC5D0 \uB0A8\uAE38 \uC758\uACAC" }),
1687
1987
  /* @__PURE__ */ jsx(
1688
1988
  "textarea",
@@ -1783,49 +2083,6 @@ function FeedbackKit(props) {
1783
2083
  );
1784
2084
  }
1785
2085
 
1786
- // src/storage.ts
1787
- function memoryStorage() {
1788
- const map = /* @__PURE__ */ new Map();
1789
- return {
1790
- getItem: (key) => map.get(key) ?? null,
1791
- setItem: (key, value) => void map.set(key, value),
1792
- removeItem: (key) => void map.delete(key)
1793
- };
1794
- }
1795
- function pickBacking() {
1796
- try {
1797
- const ls = globalThis.localStorage;
1798
- if (!ls) return memoryStorage();
1799
- const probe = "__feedback_kit_probe__";
1800
- ls.setItem(probe, "1");
1801
- ls.removeItem(probe);
1802
- return ls;
1803
- } catch {
1804
- return memoryStorage();
1805
- }
1806
- }
1807
- function createWebStorage(backing) {
1808
- const store = backing ?? pickBacking();
1809
- return {
1810
- async get(key) {
1811
- try {
1812
- return store.getItem(key);
1813
- } catch {
1814
- return null;
1815
- }
1816
- },
1817
- async set(key, value) {
1818
- store.setItem(key, value);
1819
- },
1820
- async remove(key) {
1821
- try {
1822
- store.removeItem(key);
1823
- } catch {
1824
- }
1825
- }
1826
- };
1827
- }
1828
-
1829
2086
  // src/providers.ts
1830
2087
  function webContextProviders() {
1831
2088
  return {
@@ -1855,8 +2112,10 @@ export {
1855
2112
  ElementPickingController,
1856
2113
  FLOATING_BUTTON_ID2 as FLOATING_BUTTON_ID,
1857
2114
  FeedbackKit,
2115
+ HintProvider2 as HintProvider,
1858
2116
  MARKER_KEY_PREFIX,
1859
2117
  MAX_MARKERS_PER_PATH,
2118
+ MODAL_ACTION_HINT_TOGGLE2 as MODAL_ACTION_HINT_TOGGLE,
1860
2119
  MODAL_ACTION_PICK2 as MODAL_ACTION_PICK,
1861
2120
  MarkerStore,
1862
2121
  OWN_UI_ATTR,
@@ -1877,6 +2136,7 @@ export {
1877
2136
  isOwnUi2 as isOwnUi,
1878
2137
  normalizePin2 as normalizePin,
1879
2138
  parseSourceAttr,
2139
+ rankHints2 as rankHints,
1880
2140
  reactComponentPath,
1881
2141
  reactComponentSummary,
1882
2142
  reencodeWebScreenshot,